1/*
2   +----------------------------------------------------------------------+
3   | Zend OPcache                                                         |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Andi Gutmans <andi@zend.com>                                |
16   |          Zeev Suraski <zeev@zend.com>                                |
17   |          Stanislav Malyshev <stas@zend.com>                          |
18   |          Dmitry Stogov <dmitry@zend.com>                             |
19   +----------------------------------------------------------------------+
20*/
21
22#include <errno.h>
23#include "ZendAccelerator.h"
24#include "zend_shared_alloc.h"
25#ifdef HAVE_UNISTD_H
26# include <unistd.h>
27#endif
28#include <fcntl.h>
29#ifndef ZEND_WIN32
30# include <sys/types.h>
31# include <dirent.h>
32# include <signal.h>
33# include <sys/stat.h>
34# include <stdio.h>
35#endif
36
37#ifdef HAVE_MPROTECT
38# include "sys/mman.h"
39#endif
40
41#define TMP_DIR "/tmp"
42#define SEM_FILENAME_PREFIX ".ZendSem."
43#define S_H(s) g_shared_alloc_handler->s
44
45/* True globals */
46/* old/new mapping. We can use true global even for ZTS because its usage
47   is wrapped with exclusive lock anyway */
48static HashTable xlat_table;
49static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL;
50static const char *g_shared_model;
51/* pointer to globals allocated in SHM and shared across processes */
52zend_smm_shared_globals *smm_shared_globals;
53
54#ifndef ZEND_WIN32
55#ifdef ZTS
56static MUTEX_T zts_lock;
57#endif
58int lock_file;
59static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8];
60#endif
61
62static const zend_shared_memory_handler_entry handler_table[] = {
63#ifdef USE_MMAP
64    { "mmap", &zend_alloc_mmap_handlers },
65#endif
66#ifdef USE_SHM
67    { "shm", &zend_alloc_shm_handlers },
68#endif
69#ifdef USE_SHM_OPEN
70    { "posix", &zend_alloc_posix_handlers },
71#endif
72#ifdef ZEND_WIN32
73    { "win32", &zend_alloc_win32_handlers },
74#endif
75    { NULL, NULL}
76};
77
78#ifndef ZEND_WIN32
79void zend_shared_alloc_create_lock(void)
80{
81    int val;
82
83#ifdef ZTS
84    zts_lock = tsrm_mutex_alloc();
85#endif
86
87    sprintf(lockfile_name, "%s/%sXXXXXX", TMP_DIR, SEM_FILENAME_PREFIX);
88    lock_file = mkstemp(lockfile_name);
89    fchmod(lock_file, 0666);
90
91    if (lock_file == -1) {
92        zend_accel_error(ACCEL_LOG_FATAL, "Unable to create lock file: %s (%d)", strerror(errno), errno);
93    }
94    val = fcntl(lock_file, F_GETFD, 0);
95    val |= FD_CLOEXEC;
96    fcntl(lock_file, F_SETFD, val);
97
98    unlink(lockfile_name);
99}
100#endif
101
102static void no_memory_bailout(size_t allocate_size, char *error)
103{
104    zend_accel_error(ACCEL_LOG_FATAL, "Unable to allocate shared memory segment of %ld bytes: %s: %s (%d)", allocate_size, error?error:"unknown", strerror(errno), errno );
105}
106
107static void copy_shared_segments(void *to, void *from, int count, int size)
108{
109    zend_shared_segment **shared_segments_v = (zend_shared_segment **)to;
110    void *shared_segments_to_p = ((char *)to + count*(sizeof(void *)));
111    void *shared_segments_from_p = from;
112    int i;
113
114    for (i = 0; i < count; i++) {
115        shared_segments_v[i] =  shared_segments_to_p;
116        memcpy(shared_segments_to_p, shared_segments_from_p, size);
117        shared_segments_to_p = ((char *)shared_segments_to_p + size);
118        shared_segments_from_p = ((char *)shared_segments_from_p + size);
119    }
120}
121
122static int zend_shared_alloc_try(const zend_shared_memory_handler_entry *he, size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
123{
124    int res;
125    g_shared_alloc_handler = he->handler;
126    g_shared_model = he->name;
127    ZSMMG(shared_segments) = NULL;
128    ZSMMG(shared_segments_count) = 0;
129
130    res = S_H(create_segments)(requested_size, shared_segments_p, shared_segments_count, error_in);
131
132    if (res) {
133        /* this model works! */
134        return res;
135    }
136    if (*shared_segments_p) {
137        int i;
138        /* cleanup */
139        for (i = 0; i < *shared_segments_count; i++) {
140            if ((*shared_segments_p)[i]->p && (*shared_segments_p)[i]->p != (void *)-1) {
141                S_H(detach_segment)((*shared_segments_p)[i]);
142            }
143        }
144        free(*shared_segments_p);
145        *shared_segments_p = NULL;
146    }
147    g_shared_alloc_handler = NULL;
148    return ALLOC_FAILURE;
149}
150
151int zend_shared_alloc_startup(size_t requested_size)
152{
153    zend_shared_segment **tmp_shared_segments;
154    size_t shared_segments_array_size;
155    zend_smm_shared_globals tmp_shared_globals, *p_tmp_shared_globals;
156    char *error_in = NULL;
157    const zend_shared_memory_handler_entry *he;
158    int res = ALLOC_FAILURE;
159
160    TSRMLS_FETCH();
161
162    /* shared_free must be valid before we call zend_shared_alloc()
163     * - make it temporarily point to a local variable
164     */
165    smm_shared_globals = &tmp_shared_globals;
166    ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */
167
168    zend_shared_alloc_create_lock();
169
170    if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) {
171        char *model = ZCG(accel_directives).memory_model;
172        /* "cgi" is really "shm"... */
173        if (strncmp(ZCG(accel_directives).memory_model, "cgi", sizeof("cgi")) == 0) {
174            model = "shm";
175        }
176
177        for (he = handler_table; he->name; he++) {
178            if (strcmp(model, he->name) == 0) {
179                res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
180                if (res) {
181                    /* this model works! */
182                }
183                break;
184            }
185        }
186    }
187
188    if (res == FAILED_REATTACHED) {
189        smm_shared_globals = NULL;
190        return res;
191    }
192
193    if (!g_shared_alloc_handler) {
194        /* try memory handlers in order */
195        for (he = handler_table; he->name; he++) {
196            res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
197            if (res) {
198                /* this model works! */
199                break;
200            }
201        }
202    }
203
204    if (!g_shared_alloc_handler) {
205        no_memory_bailout(requested_size, error_in);
206        return ALLOC_FAILURE;
207    }
208
209    if (res == SUCCESSFULLY_REATTACHED) {
210        return res;
211    }
212
213    shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)();
214
215    /* move shared_segments and shared_free to shared memory */
216    ZCG(locked) = 1; /* no need to perform a real lock at this point */
217    p_tmp_shared_globals = (zend_smm_shared_globals *) zend_shared_alloc(sizeof(zend_smm_shared_globals));
218    if (!p_tmp_shared_globals) {
219        zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
220        return ALLOC_FAILURE;;
221    }
222
223    tmp_shared_segments = zend_shared_alloc(shared_segments_array_size + ZSMMG(shared_segments_count) * sizeof(void *));
224    if (!tmp_shared_segments) {
225        zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
226        return ALLOC_FAILURE;;
227    }
228
229    copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
230
231    *p_tmp_shared_globals = tmp_shared_globals;
232    smm_shared_globals = p_tmp_shared_globals;
233
234    free(ZSMMG(shared_segments));
235    ZSMMG(shared_segments) = tmp_shared_segments;
236
237    ZSMMG(shared_memory_state).positions = (int *)zend_shared_alloc(sizeof(int) * ZSMMG(shared_segments_count));
238    if (!ZSMMG(shared_memory_state).positions) {
239        zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
240        return ALLOC_FAILURE;;
241    }
242
243    ZCG(locked) = 0;
244
245    return res;
246}
247
248void zend_shared_alloc_shutdown(void)
249{
250    zend_shared_segment **tmp_shared_segments;
251    size_t shared_segments_array_size;
252    zend_smm_shared_globals tmp_shared_globals;
253    int i;
254
255    tmp_shared_globals = *smm_shared_globals;
256    smm_shared_globals = &tmp_shared_globals;
257    shared_segments_array_size = ZSMMG(shared_segments_count) * (S_H(segment_type_size)() + sizeof(void *));
258    tmp_shared_segments = emalloc(shared_segments_array_size);
259    copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
260    ZSMMG(shared_segments) = tmp_shared_segments;
261
262    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
263        S_H(detach_segment)(ZSMMG(shared_segments)[i]);
264    }
265    efree(ZSMMG(shared_segments));
266    ZSMMG(shared_segments) = NULL;
267    g_shared_alloc_handler = NULL;
268#ifndef ZEND_WIN32
269    close(lock_file);
270#endif
271}
272
273static size_t zend_shared_alloc_get_largest_free_block(void)
274{
275    int i;
276    size_t largest_block_size = 0;
277
278    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
279        size_t block_size = ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos;
280
281        if (block_size>largest_block_size) {
282            largest_block_size = block_size;
283        }
284    }
285    return largest_block_size;
286}
287
288#define MIN_FREE_MEMORY 64*1024
289
290#define SHARED_ALLOC_FAILED() do {      \
291        zend_accel_error(ACCEL_LOG_WARNING, "Not enough free shared space to allocate %pd bytes (%pd bytes free)", (zend_long)size, (zend_long)ZSMMG(shared_free)); \
292        if (zend_shared_alloc_get_largest_free_block() < MIN_FREE_MEMORY) { \
293            ZSMMG(memory_exhausted) = 1; \
294        } \
295    } while (0)
296
297void *zend_shared_alloc(size_t size)
298{
299    int i;
300    unsigned int block_size = ZEND_ALIGNED_SIZE(size);
301    TSRMLS_FETCH();
302
303#if 1
304    if (!ZCG(locked)) {
305        zend_accel_error(ACCEL_LOG_ERROR, "Shared memory lock not obtained");
306    }
307#endif
308    if (block_size > ZSMMG(shared_free)) { /* No hope to find a big-enough block */
309        SHARED_ALLOC_FAILED();
310        return NULL;
311    }
312    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
313        if (ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos >= block_size) { /* found a valid block */
314            void *retval = (void *) (((char *) ZSMMG(shared_segments)[i]->p) + ZSMMG(shared_segments)[i]->pos);
315
316            ZSMMG(shared_segments)[i]->pos += block_size;
317            ZSMMG(shared_free) -= block_size;
318            memset(retval, 0, block_size);
319            return retval;
320        }
321    }
322    SHARED_ALLOC_FAILED();
323    return NULL;
324}
325
326int zend_shared_memdup_size(void *source, size_t size)
327{
328    void *old_p;
329
330    if ((old_p = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)source)) != NULL) {
331        /* we already duplicated this pointer */
332        return 0;
333    }
334    zend_shared_alloc_register_xlat_entry(source, source);
335    return ZEND_ALIGNED_SIZE(size);
336}
337
338void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC)
339{
340    void *old_p, *retval;
341
342    if ((old_p = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)source)) != NULL) {
343        /* we already duplicated this pointer */
344        return old_p;
345    }
346    retval = ZCG(mem);
347    ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size));
348    memcpy(retval, source, size);
349    if (free_source) {
350        efree(source);
351    }
352    zend_shared_alloc_register_xlat_entry(source, retval);
353    return retval;
354}
355
356void zend_shared_alloc_safe_unlock(TSRMLS_D)
357{
358    if (ZCG(locked)) {
359        zend_shared_alloc_unlock(TSRMLS_C);
360    }
361}
362
363#ifndef ZEND_WIN32
364/* name l_type l_whence l_start l_len */
365static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1);
366static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1);
367#endif
368
369void zend_shared_alloc_lock(TSRMLS_D)
370{
371#ifndef ZEND_WIN32
372
373#ifdef ZTS
374    tsrm_mutex_lock(zts_lock);
375#endif
376
377#if 0
378    /* this will happen once per process, and will un-globalize mem_write_lock */
379    if (mem_write_lock.l_pid == -1) {
380        mem_write_lock.l_pid = getpid();
381    }
382#endif
383
384    while (1) {
385        if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) {
386            if (errno == EINTR) {
387                continue;
388            }
389            zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno);
390        }
391        break;
392    }
393#else
394    zend_shared_alloc_lock_win32();
395#endif
396
397    ZCG(locked) = 1;
398
399    /* Prepare translation table
400     *
401     * Make it persistent so that it uses malloc() and allocated blocks
402     * won't be taken from space which is freed by efree in memdup.
403     * Otherwise it leads to false matches in memdup check.
404     */
405    zend_hash_init(&xlat_table, 128, NULL, NULL, 1);
406}
407
408void zend_shared_alloc_unlock(TSRMLS_D)
409{
410    /* Destroy translation table */
411    zend_hash_destroy(&xlat_table);
412
413    ZCG(locked) = 0;
414
415#ifndef ZEND_WIN32
416    if (fcntl(lock_file, F_SETLK, &mem_write_unlock) == -1) {
417        zend_accel_error(ACCEL_LOG_ERROR, "Cannot remove lock - %s (%d)", strerror(errno), errno);
418    }
419#ifdef ZTS
420    tsrm_mutex_unlock(zts_lock);
421#endif
422#else
423    zend_shared_alloc_unlock_win32();
424#endif
425}
426
427void zend_shared_alloc_clear_xlat_table(void)
428{
429    zend_hash_clean(&xlat_table);
430}
431
432void zend_shared_alloc_register_xlat_entry(const void *old, const void *new)
433{
434    zend_hash_index_update_ptr(&xlat_table, (zend_ulong)old, (void*)new);
435}
436
437void *zend_shared_alloc_get_xlat_entry(const void *old)
438{
439    void *retval;
440
441    if ((retval = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)old)) == NULL) {
442        return NULL;
443    }
444    return retval;
445}
446
447size_t zend_shared_alloc_get_free_memory(void)
448{
449    return ZSMMG(shared_free);
450}
451
452void zend_shared_alloc_save_state(void)
453{
454    int i;
455
456    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
457        ZSMMG(shared_memory_state).positions[i] = ZSMMG(shared_segments)[i]->pos;
458    }
459    ZSMMG(shared_memory_state).shared_free = ZSMMG(shared_free);
460}
461
462void zend_shared_alloc_restore_state(void)
463{
464    int i;
465
466    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
467        ZSMMG(shared_segments)[i]->pos = ZSMMG(shared_memory_state).positions[i];
468    }
469    ZSMMG(shared_free) = ZSMMG(shared_memory_state).shared_free;
470    ZSMMG(memory_exhausted) = 0;
471    ZSMMG(wasted_shared_memory) = 0;
472}
473
474const char *zend_accel_get_shared_model(void)
475{
476    return g_shared_model;
477}
478
479void zend_accel_shared_protect(int mode TSRMLS_DC)
480{
481#ifdef HAVE_MPROTECT
482    int i;
483
484    if (mode) {
485        mode = PROT_READ;
486    } else {
487        mode = PROT_READ|PROT_WRITE;
488    }
489
490    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
491        mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->size, mode);
492    }
493#endif
494}
495