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
161    /* shared_free must be valid before we call zend_shared_alloc()
162     * - make it temporarily point to a local variable
163     */
164    smm_shared_globals = &tmp_shared_globals;
165    ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */
166
167    zend_shared_alloc_create_lock();
168
169    if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) {
170        char *model = ZCG(accel_directives).memory_model;
171        /* "cgi" is really "shm"... */
172        if (strncmp(ZCG(accel_directives).memory_model, "cgi", sizeof("cgi")) == 0) {
173            model = "shm";
174        }
175
176        for (he = handler_table; he->name; he++) {
177            if (strcmp(model, he->name) == 0) {
178                res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
179                if (res) {
180                    /* this model works! */
181                }
182                break;
183            }
184        }
185    }
186
187    if (res == FAILED_REATTACHED) {
188        smm_shared_globals = NULL;
189        return res;
190    }
191
192    if (!g_shared_alloc_handler) {
193        /* try memory handlers in order */
194        for (he = handler_table; he->name; he++) {
195            res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
196            if (res) {
197                /* this model works! */
198                break;
199            }
200        }
201    }
202
203    if (!g_shared_alloc_handler) {
204        no_memory_bailout(requested_size, error_in);
205        return ALLOC_FAILURE;
206    }
207
208    if (res == SUCCESSFULLY_REATTACHED) {
209        return res;
210    }
211
212    shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)();
213
214    /* move shared_segments and shared_free to shared memory */
215    ZCG(locked) = 1; /* no need to perform a real lock at this point */
216    p_tmp_shared_globals = (zend_smm_shared_globals *) zend_shared_alloc(sizeof(zend_smm_shared_globals));
217    if (!p_tmp_shared_globals) {
218        zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
219        return ALLOC_FAILURE;;
220    }
221
222    tmp_shared_segments = zend_shared_alloc(shared_segments_array_size + ZSMMG(shared_segments_count) * sizeof(void *));
223    if (!tmp_shared_segments) {
224        zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
225        return ALLOC_FAILURE;;
226    }
227
228    copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
229
230    *p_tmp_shared_globals = tmp_shared_globals;
231    smm_shared_globals = p_tmp_shared_globals;
232
233    free(ZSMMG(shared_segments));
234    ZSMMG(shared_segments) = tmp_shared_segments;
235
236    ZSMMG(shared_memory_state).positions = (int *)zend_shared_alloc(sizeof(int) * ZSMMG(shared_segments_count));
237    if (!ZSMMG(shared_memory_state).positions) {
238        zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
239        return ALLOC_FAILURE;;
240    }
241
242    ZCG(locked) = 0;
243
244    return res;
245}
246
247void zend_shared_alloc_shutdown(void)
248{
249    zend_shared_segment **tmp_shared_segments;
250    size_t shared_segments_array_size;
251    zend_smm_shared_globals tmp_shared_globals;
252    int i;
253
254    tmp_shared_globals = *smm_shared_globals;
255    smm_shared_globals = &tmp_shared_globals;
256    shared_segments_array_size = ZSMMG(shared_segments_count) * (S_H(segment_type_size)() + sizeof(void *));
257    tmp_shared_segments = emalloc(shared_segments_array_size);
258    copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
259    ZSMMG(shared_segments) = tmp_shared_segments;
260
261    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
262        S_H(detach_segment)(ZSMMG(shared_segments)[i]);
263    }
264    efree(ZSMMG(shared_segments));
265    ZSMMG(shared_segments) = NULL;
266    g_shared_alloc_handler = NULL;
267#ifndef ZEND_WIN32
268    close(lock_file);
269#endif
270}
271
272static size_t zend_shared_alloc_get_largest_free_block(void)
273{
274    int i;
275    size_t largest_block_size = 0;
276
277    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
278        size_t block_size = ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos;
279
280        if (block_size>largest_block_size) {
281            largest_block_size = block_size;
282        }
283    }
284    return largest_block_size;
285}
286
287#define MIN_FREE_MEMORY 64*1024
288
289#define SHARED_ALLOC_FAILED() do {      \
290        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)); \
291        if (zend_shared_alloc_get_largest_free_block() < MIN_FREE_MEMORY) { \
292            ZSMMG(memory_exhausted) = 1; \
293        } \
294    } while (0)
295
296void *zend_shared_alloc(size_t size)
297{
298    int i;
299    unsigned int block_size = ZEND_ALIGNED_SIZE(size);
300
301#if 1
302    if (!ZCG(locked)) {
303        zend_accel_error(ACCEL_LOG_ERROR, "Shared memory lock not obtained");
304    }
305#endif
306    if (block_size > ZSMMG(shared_free)) { /* No hope to find a big-enough block */
307        SHARED_ALLOC_FAILED();
308        return NULL;
309    }
310    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
311        if (ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos >= block_size) { /* found a valid block */
312            void *retval = (void *) (((char *) ZSMMG(shared_segments)[i]->p) + ZSMMG(shared_segments)[i]->pos);
313
314            ZSMMG(shared_segments)[i]->pos += block_size;
315            ZSMMG(shared_free) -= block_size;
316            memset(retval, 0, block_size);
317            return retval;
318        }
319    }
320    SHARED_ALLOC_FAILED();
321    return NULL;
322}
323
324int zend_shared_memdup_size(void *source, size_t size)
325{
326    void *old_p;
327
328    if ((old_p = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)source)) != NULL) {
329        /* we already duplicated this pointer */
330        return 0;
331    }
332    zend_shared_alloc_register_xlat_entry(source, source);
333    return ZEND_ALIGNED_SIZE(size);
334}
335
336void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source)
337{
338    void *old_p, *retval;
339
340    if ((old_p = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)source)) != NULL) {
341        /* we already duplicated this pointer */
342        return old_p;
343    }
344    retval = ZCG(mem);
345    ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size));
346    memcpy(retval, source, size);
347    if (free_source) {
348        efree(source);
349    }
350    zend_shared_alloc_register_xlat_entry(source, retval);
351    return retval;
352}
353
354void zend_shared_alloc_safe_unlock(void)
355{
356    if (ZCG(locked)) {
357        zend_shared_alloc_unlock();
358    }
359}
360
361#ifndef ZEND_WIN32
362/* name l_type l_whence l_start l_len */
363static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1);
364static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1);
365#endif
366
367void zend_shared_alloc_lock(void)
368{
369#ifndef ZEND_WIN32
370
371#ifdef ZTS
372    tsrm_mutex_lock(zts_lock);
373#endif
374
375#if 0
376    /* this will happen once per process, and will un-globalize mem_write_lock */
377    if (mem_write_lock.l_pid == -1) {
378        mem_write_lock.l_pid = getpid();
379    }
380#endif
381
382    while (1) {
383        if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) {
384            if (errno == EINTR) {
385                continue;
386            }
387            zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno);
388        }
389        break;
390    }
391#else
392    zend_shared_alloc_lock_win32();
393#endif
394
395    ZCG(locked) = 1;
396
397    /* Prepare translation table
398     *
399     * Make it persistent so that it uses malloc() and allocated blocks
400     * won't be taken from space which is freed by efree in memdup.
401     * Otherwise it leads to false matches in memdup check.
402     */
403    zend_hash_init(&xlat_table, 128, NULL, NULL, 1);
404}
405
406void zend_shared_alloc_unlock(void)
407{
408    /* Destroy translation table */
409    zend_hash_destroy(&xlat_table);
410
411    ZCG(locked) = 0;
412
413#ifndef ZEND_WIN32
414    if (fcntl(lock_file, F_SETLK, &mem_write_unlock) == -1) {
415        zend_accel_error(ACCEL_LOG_ERROR, "Cannot remove lock - %s (%d)", strerror(errno), errno);
416    }
417#ifdef ZTS
418    tsrm_mutex_unlock(zts_lock);
419#endif
420#else
421    zend_shared_alloc_unlock_win32();
422#endif
423}
424
425void zend_shared_alloc_clear_xlat_table(void)
426{
427    zend_hash_clean(&xlat_table);
428}
429
430void zend_shared_alloc_register_xlat_entry(const void *old, const void *new)
431{
432    zend_hash_index_update_ptr(&xlat_table, (zend_ulong)old, (void*)new);
433}
434
435void *zend_shared_alloc_get_xlat_entry(const void *old)
436{
437    void *retval;
438
439    if ((retval = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)old)) == NULL) {
440        return NULL;
441    }
442    return retval;
443}
444
445size_t zend_shared_alloc_get_free_memory(void)
446{
447    return ZSMMG(shared_free);
448}
449
450void zend_shared_alloc_save_state(void)
451{
452    int i;
453
454    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
455        ZSMMG(shared_memory_state).positions[i] = ZSMMG(shared_segments)[i]->pos;
456    }
457    ZSMMG(shared_memory_state).shared_free = ZSMMG(shared_free);
458}
459
460void zend_shared_alloc_restore_state(void)
461{
462    int i;
463
464    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
465        ZSMMG(shared_segments)[i]->pos = ZSMMG(shared_memory_state).positions[i];
466    }
467    ZSMMG(shared_free) = ZSMMG(shared_memory_state).shared_free;
468    ZSMMG(memory_exhausted) = 0;
469    ZSMMG(wasted_shared_memory) = 0;
470}
471
472const char *zend_accel_get_shared_model(void)
473{
474    return g_shared_model;
475}
476
477void zend_accel_shared_protect(int mode)
478{
479#ifdef HAVE_MPROTECT
480    int i;
481
482    if (mode) {
483        mode = PROT_READ;
484    } else {
485        mode = PROT_READ|PROT_WRITE;
486    }
487
488    for (i = 0; i < ZSMMG(shared_segments_count); i++) {
489        mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->size, mode);
490    }
491#endif
492}
493