root/ext/opcache/zend_shared_alloc.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. zend_shared_alloc_create_lock
  2. no_memory_bailout
  3. copy_shared_segments
  4. zend_shared_alloc_try
  5. zend_shared_alloc_startup
  6. zend_shared_alloc_shutdown
  7. zend_shared_alloc_get_largest_free_block
  8. zend_shared_alloc
  9. zend_shared_memdup_size
  10. _zend_shared_memdup
  11. zend_shared_alloc_safe_unlock
  12. zend_shared_alloc_lock
  13. zend_shared_alloc_unlock
  14. zend_shared_alloc_clear_xlat_table
  15. zend_shared_alloc_register_xlat_entry
  16. zend_shared_alloc_get_xlat_entry
  17. zend_shared_alloc_get_free_memory
  18. zend_shared_alloc_save_state
  19. zend_shared_alloc_restore_state
  20. zend_accel_get_shared_model
  21. zend_accel_shared_protect

/*
   +----------------------------------------------------------------------+
   | Zend OPcache                                                         |
   +----------------------------------------------------------------------+
   | Copyright (c) 1998-2015 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Andi Gutmans <andi@zend.com>                                |
   |          Zeev Suraski <zeev@zend.com>                                |
   |          Stanislav Malyshev <stas@zend.com>                          |
   |          Dmitry Stogov <dmitry@zend.com>                             |
   +----------------------------------------------------------------------+
*/

#include <errno.h>
#include "ZendAccelerator.h"
#include "zend_shared_alloc.h"
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <fcntl.h>
#ifndef ZEND_WIN32
# include <sys/types.h>
# include <dirent.h>
# include <signal.h>
# include <sys/stat.h>
# include <stdio.h>
#endif

#ifdef HAVE_MPROTECT
# include "sys/mman.h"
#endif

#define TMP_DIR "/tmp"
#define SEM_FILENAME_PREFIX ".ZendSem."
#define S_H(s) g_shared_alloc_handler->s

/* True globals */
/* old/new mapping. We can use true global even for ZTS because its usage
   is wrapped with exclusive lock anyway */
static HashTable xlat_table;
static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL;
static const char *g_shared_model;
/* pointer to globals allocated in SHM and shared across processes */
zend_smm_shared_globals *smm_shared_globals;

#ifndef ZEND_WIN32
#ifdef ZTS
static MUTEX_T zts_lock;
#endif
int lock_file;
static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8];
#endif

static const zend_shared_memory_handler_entry handler_table[] = {
#ifdef USE_MMAP
        { "mmap", &zend_alloc_mmap_handlers },
#endif
#ifdef USE_SHM
        { "shm", &zend_alloc_shm_handlers },
#endif
#ifdef USE_SHM_OPEN
        { "posix", &zend_alloc_posix_handlers },
#endif
#ifdef ZEND_WIN32
        { "win32", &zend_alloc_win32_handlers },
#endif
        { NULL, NULL}
};

#ifndef ZEND_WIN32
void zend_shared_alloc_create_lock(void)
{
        int val;

#ifdef ZTS
    zts_lock = tsrm_mutex_alloc();
#endif

        sprintf(lockfile_name, "%s/%sXXXXXX", TMP_DIR, SEM_FILENAME_PREFIX);
        lock_file = mkstemp(lockfile_name);
        fchmod(lock_file, 0666);

        if (lock_file == -1) {
                zend_accel_error(ACCEL_LOG_FATAL, "Unable to create lock file: %s (%d)", strerror(errno), errno);
        }
        val = fcntl(lock_file, F_GETFD, 0);
        val |= FD_CLOEXEC;
        fcntl(lock_file, F_SETFD, val);

        unlink(lockfile_name);
}
#endif

static void no_memory_bailout(size_t allocate_size, char *error)
{
        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 );
}

static void copy_shared_segments(void *to, void *from, int count, int size)
{
        zend_shared_segment **shared_segments_v = (zend_shared_segment **)to;
        void *shared_segments_to_p = ((char *)to + count*(sizeof(void *)));
        void *shared_segments_from_p = from;
        int i;

        for (i = 0; i < count; i++) {
                shared_segments_v[i] =  shared_segments_to_p;
                memcpy(shared_segments_to_p, shared_segments_from_p, size);
                shared_segments_to_p = ((char *)shared_segments_to_p + size);
                shared_segments_from_p = ((char *)shared_segments_from_p + size);
        }
}

static 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)
{
        int res;
        g_shared_alloc_handler = he->handler;
        g_shared_model = he->name;
        ZSMMG(shared_segments) = NULL;
        ZSMMG(shared_segments_count) = 0;

        res = S_H(create_segments)(requested_size, shared_segments_p, shared_segments_count, error_in);

        if (res) {
                /* this model works! */
                return res;
        }
        if (*shared_segments_p) {
                int i;
                /* cleanup */
                for (i = 0; i < *shared_segments_count; i++) {
                        if ((*shared_segments_p)[i]->p && (*shared_segments_p)[i]->p != (void *)-1) {
                                S_H(detach_segment)((*shared_segments_p)[i]);
                        }
                }
                free(*shared_segments_p);
                *shared_segments_p = NULL;
        }
        g_shared_alloc_handler = NULL;
        return ALLOC_FAILURE;
}

int zend_shared_alloc_startup(size_t requested_size)
{
        zend_shared_segment **tmp_shared_segments;
        size_t shared_segments_array_size;
        zend_smm_shared_globals tmp_shared_globals, *p_tmp_shared_globals;
        char *error_in = NULL;
        const zend_shared_memory_handler_entry *he;
        int res = ALLOC_FAILURE;

        TSRMLS_FETCH();

        /* shared_free must be valid before we call zend_shared_alloc()
         * - make it temporarily point to a local variable
         */
        smm_shared_globals = &tmp_shared_globals;
        ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */

        zend_shared_alloc_create_lock();

        if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) {
                char *model = ZCG(accel_directives).memory_model;
                /* "cgi" is really "shm"... */
                if (strncmp(ZCG(accel_directives).memory_model, "cgi", sizeof("cgi")) == 0) {
                        model = "shm";
                }

                for (he = handler_table; he->name; he++) {
                        if (strcmp(model, he->name) == 0) {
                                res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
                                if (res) {
                                        /* this model works! */
                                }
                                break;
                        }
                }
        }

        if (res == FAILED_REATTACHED) {
                smm_shared_globals = NULL;
                return res;
        }

        if (!g_shared_alloc_handler) {
                /* try memory handlers in order */
                for (he = handler_table; he->name; he++) {
                        res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
                        if (res) {
                                /* this model works! */
                                break;
                        }
                }
        }

        if (!g_shared_alloc_handler) {
                no_memory_bailout(requested_size, error_in);
                return ALLOC_FAILURE;
        }

        if (res == SUCCESSFULLY_REATTACHED) {
                return res;
        }

        shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)();

        /* move shared_segments and shared_free to shared memory */
        ZCG(locked) = 1; /* no need to perform a real lock at this point */
        p_tmp_shared_globals = (zend_smm_shared_globals *) zend_shared_alloc(sizeof(zend_smm_shared_globals));
        if (!p_tmp_shared_globals) {
                zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
                return ALLOC_FAILURE;;
        }

        tmp_shared_segments = zend_shared_alloc(shared_segments_array_size + ZSMMG(shared_segments_count) * sizeof(void *));
        if (!tmp_shared_segments) {
                zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
                return ALLOC_FAILURE;;
        }

        copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());

        *p_tmp_shared_globals = tmp_shared_globals;
        smm_shared_globals = p_tmp_shared_globals;

        free(ZSMMG(shared_segments));
        ZSMMG(shared_segments) = tmp_shared_segments;

        ZSMMG(shared_memory_state).positions = (int *)zend_shared_alloc(sizeof(int) * ZSMMG(shared_segments_count));
        if (!ZSMMG(shared_memory_state).positions) {
                zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
                return ALLOC_FAILURE;;
        }

        ZCG(locked) = 0;

        return res;
}

void zend_shared_alloc_shutdown(void)
{
        zend_shared_segment **tmp_shared_segments;
        size_t shared_segments_array_size;
        zend_smm_shared_globals tmp_shared_globals;
        int i;

        tmp_shared_globals = *smm_shared_globals;
        smm_shared_globals = &tmp_shared_globals;
        shared_segments_array_size = ZSMMG(shared_segments_count) * (S_H(segment_type_size)() + sizeof(void *));
        tmp_shared_segments = emalloc(shared_segments_array_size);
        copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
        ZSMMG(shared_segments) = tmp_shared_segments;

        for (i = 0; i < ZSMMG(shared_segments_count); i++) {
                S_H(detach_segment)(ZSMMG(shared_segments)[i]);
        }
        efree(ZSMMG(shared_segments));
        ZSMMG(shared_segments) = NULL;
        g_shared_alloc_handler = NULL;
#ifndef ZEND_WIN32
        close(lock_file);
#endif
}

static size_t zend_shared_alloc_get_largest_free_block(void)
{
        int i;
        size_t largest_block_size = 0;

        for (i = 0; i < ZSMMG(shared_segments_count); i++) {
                size_t block_size = ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos;

                if (block_size>largest_block_size) {
                        largest_block_size = block_size;
                }
        }
        return largest_block_size;
}

#define MIN_FREE_MEMORY 64*1024

#define SHARED_ALLOC_FAILED() do {              \
                zend_accel_error(ACCEL_LOG_WARNING, "Not enough free shared space to allocate %ld bytes (%ld bytes free)", (long)size, (long)ZSMMG(shared_free)); \
                if (zend_shared_alloc_get_largest_free_block() < MIN_FREE_MEMORY) { \
                        ZSMMG(memory_exhausted) = 1; \
                } \
        } while (0)

void *zend_shared_alloc(size_t size)
{
        int i;
        unsigned int block_size = ZEND_ALIGNED_SIZE(size);
        TSRMLS_FETCH();

#if 1
        if (!ZCG(locked)) {
                zend_accel_error(ACCEL_LOG_ERROR, "Shared memory lock not obtained");
        }
#endif
        if (block_size > ZSMMG(shared_free)) { /* No hope to find a big-enough block */
                SHARED_ALLOC_FAILED();
                return NULL;
        }
        for (i = 0; i < ZSMMG(shared_segments_count); i++) {
                if (ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos >= block_size) { /* found a valid block */
                        void *retval = (void *) (((char *) ZSMMG(shared_segments)[i]->p) + ZSMMG(shared_segments)[i]->pos);

                        ZSMMG(shared_segments)[i]->pos += block_size;
                        ZSMMG(shared_free) -= block_size;
                        memset(retval, 0, block_size);
                        return retval;
                }
        }
        SHARED_ALLOC_FAILED();
        return NULL;
}

int zend_shared_memdup_size(void *source, size_t size)
{
        void **old_p;

        if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) {
                /* we already duplicated this pointer */
                return 0;
        }
        zend_shared_alloc_register_xlat_entry(source, source);
        return ZEND_ALIGNED_SIZE(size);
}

void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC)
{
        void **old_p, *retval;

        if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) {
                /* we already duplicated this pointer */
                return *old_p;
        }
        retval = ZCG(mem);;
        ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size));
        memcpy(retval, source, size);
        zend_shared_alloc_register_xlat_entry(source, retval);
        if (free_source) {
                interned_efree((char*)source);
        }
        return retval;
}

void zend_shared_alloc_safe_unlock(TSRMLS_D)
{
        if (ZCG(locked)) {
                zend_shared_alloc_unlock(TSRMLS_C);
        }
}

#ifndef ZEND_WIN32
/* name l_type l_whence l_start l_len */
static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1);
static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1);
#endif

void zend_shared_alloc_lock(TSRMLS_D)
{
#ifndef ZEND_WIN32

#ifdef ZTS
        tsrm_mutex_lock(zts_lock);
#endif

#if 0
        /* this will happen once per process, and will un-globalize mem_write_lock */
        if (mem_write_lock.l_pid == -1) {
                mem_write_lock.l_pid = getpid();
        }
#endif

        while (1) {
                if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) {
                        if (errno == EINTR) {
                                continue;
                        }
                        zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno);
                }
                break;
        }
#else
        zend_shared_alloc_lock_win32();
#endif

        ZCG(locked) = 1;

        /* Prepare translation table
         *
         * Make it persistent so that it uses malloc() and allocated blocks
         * won't be taken from space which is freed by efree in memdup.
         * Otherwise it leads to false matches in memdup check.
         */
        zend_hash_init(&xlat_table, 100, NULL, NULL, 1);
}

void zend_shared_alloc_unlock(TSRMLS_D)
{
        /* Destroy translation table */
        zend_hash_destroy(&xlat_table);

        ZCG(locked) = 0;

#ifndef ZEND_WIN32
        if (fcntl(lock_file, F_SETLK, &mem_write_unlock) == -1) {
                zend_accel_error(ACCEL_LOG_ERROR, "Cannot remove lock - %s (%d)", strerror(errno), errno);
        }
#ifdef ZTS
        tsrm_mutex_unlock(zts_lock);
#endif
#else
        zend_shared_alloc_unlock_win32();
#endif
}

void zend_shared_alloc_clear_xlat_table(void)
{
        zend_hash_clean(&xlat_table);
}

void zend_shared_alloc_register_xlat_entry(const void *old, const void *new)
{
        zend_hash_index_update(&xlat_table, (ulong)old, (void*)&new, sizeof(void *), NULL);
}

void *zend_shared_alloc_get_xlat_entry(const void *old)
{
        void **retval;

        if (zend_hash_index_find(&xlat_table, (ulong)old, (void **)&retval) == FAILURE) {
                return NULL;
        }
        return *retval;
}

size_t zend_shared_alloc_get_free_memory(void)
{
        return ZSMMG(shared_free);
}

void zend_shared_alloc_save_state(void)
{
        int i;

        for (i = 0; i < ZSMMG(shared_segments_count); i++) {
                ZSMMG(shared_memory_state).positions[i] = ZSMMG(shared_segments)[i]->pos;
        }
        ZSMMG(shared_memory_state).shared_free = ZSMMG(shared_free);
}

void zend_shared_alloc_restore_state(void)
{
        int i;

        for (i = 0; i < ZSMMG(shared_segments_count); i++) {
                ZSMMG(shared_segments)[i]->pos = ZSMMG(shared_memory_state).positions[i];
        }
        ZSMMG(shared_free) = ZSMMG(shared_memory_state).shared_free;
        ZSMMG(memory_exhausted) = 0;
        ZSMMG(wasted_shared_memory) = 0;
}

const char *zend_accel_get_shared_model(void)
{
        return g_shared_model;
}

void zend_accel_shared_protect(int mode TSRMLS_DC)
{
#ifdef HAVE_MPROTECT
        int i;

        if (mode) {
                mode = PROT_READ;
        } else {
                mode = PROT_READ|PROT_WRITE;
        }

        for (i = 0; i < ZSMMG(shared_segments_count); i++) {
                mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->size, mode);
        }
#endif
}

/* [<][>][^][v][top][bottom][index][help] */