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