root/ext/opcache/shared_alloc_win32.c

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

DEFINITIONS

This source file includes following definitions.
  1. zend_win_error_message
  2. accel_gen_system_id
  3. create_name_with_username
  4. get_mmap_base_file
  5. zend_shared_alloc_create_lock
  6. zend_shared_alloc_lock_win32
  7. zend_shared_alloc_unlock_win32
  8. zend_shared_alloc_reattach
  9. create_segments
  10. detach_segment
  11. segment_type_size

   1 /*
   2    +----------------------------------------------------------------------+
   3    | Zend OPcache                                                         |
   4    +----------------------------------------------------------------------+
   5    | Copyright (c) 1998-2016 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 "ZendAccelerator.h"
  23 #include "zend_shared_alloc.h"
  24 #include "zend_accelerator_util_funcs.h"
  25 #include <winbase.h>
  26 #include <process.h>
  27 #include <LMCONS.H>
  28 
  29 #include "main/php.h"
  30 #include "ext/standard/md5.h"
  31 
  32 #define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
  33 #define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
  34 #define ACCEL_FILEMAP_BASE_DEFAULT 0x01000000
  35 #define ACCEL_FILEMAP_BASE "ZendOPcache.MemoryBase"
  36 #define ACCEL_EVENT_SOURCE "Zend OPcache"
  37 
  38 static HANDLE memfile = NULL, memory_mutex = NULL;
  39 static void *mapping_base;
  40 
  41 #define MAX_MAP_RETRIES 25
  42 
  43 static void zend_win_error_message(int type, char *msg, int err)
  44 {
  45         LPVOID lpMsgBuf;
  46         HANDLE h;
  47         char *ev_msgs[2];
  48 
  49         FormatMessage(
  50                 FORMAT_MESSAGE_ALLOCATE_BUFFER |
  51                 FORMAT_MESSAGE_FROM_SYSTEM |
  52                 FORMAT_MESSAGE_IGNORE_INSERTS,
  53                 NULL,
  54                 err,
  55                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  56                 (LPTSTR) &lpMsgBuf,
  57                 0,
  58                 NULL
  59         );
  60 
  61         h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
  62         ev_msgs[0] = msg;
  63         ev_msgs[1] = lpMsgBuf;
  64         ReportEvent(h,                            // event log handle
  65             EVENTLOG_ERROR_TYPE,  // event type
  66             0,                    // category zero
  67             err,                                  // event identifier
  68             NULL,                 // no user security identifier
  69             2,                    // one substitution string
  70             0,                    // no data
  71             ev_msgs,              // pointer to string array
  72             NULL);                // pointer to data
  73         DeregisterEventSource(h);
  74 
  75         LocalFree( lpMsgBuf );
  76 
  77         zend_accel_error(type, msg);
  78 }
  79 
  80 /* Backported from 7.0, the way no globals are touched which means win32
  81    only where it's essentially needed. */
  82 #define ZEND_BIN_ID "BIN_" ZEND_TOSTR(SIZEOF_CHAR) ZEND_TOSTR(SIZEOF_INT) ZEND_TOSTR(SIZEOF_LONG) ZEND_TOSTR(SIZEOF_SIZE_T) ZEND_TOSTR(SIZEOF_ZEND_LONG) ZEND_TOSTR(ZEND_MM_ALIGNMENT)
  83 static char *accel_gen_system_id(void)
  84 {
  85         PHP_MD5_CTX context;
  86         unsigned char digest[16], c;
  87         int i;
  88         static char md5str[32];
  89         static zend_bool done = 0;
  90 
  91         if (done) {
  92                 return md5str;
  93         }
  94 
  95         PHP_MD5Init(&context);
  96         PHP_MD5Update(&context, PHP_VERSION, sizeof(PHP_VERSION)-1);
  97         PHP_MD5Update(&context, ZEND_EXTENSION_BUILD_ID, sizeof(ZEND_EXTENSION_BUILD_ID)-1);
  98         PHP_MD5Update(&context, ZEND_BIN_ID, sizeof(ZEND_BIN_ID)-1);
  99         if (strstr(PHP_VERSION, "-dev") != 0) {
 100                 /* Development versions may be changed from build to build */
 101                 PHP_MD5Update(&context, __DATE__, sizeof(__DATE__)-1);
 102                 PHP_MD5Update(&context, __TIME__, sizeof(__TIME__)-1);
 103         }
 104         PHP_MD5Final(digest, &context);
 105         for (i = 0; i < 16; i++) {
 106                 c = digest[i] >> 4;
 107                 c = (c <= 9) ? c + '0' : c - 10 + 'a';
 108                 md5str[i * 2] = c;
 109                 c = digest[i] &  0x0f;
 110                 c = (c <= 9) ? c + '0' : c - 10 + 'a';
 111                 md5str[(i * 2) + 1] = c;
 112         }
 113 
 114         done = 1;
 115 
 116         return md5str;
 117 }
 118 
 119 static char *create_name_with_username(char *name)
 120 {
 121         static char newname[MAXPATHLEN + UNLEN + 4 + 1 + 32];
 122         char uname[UNLEN + 1];
 123         DWORD unsize = UNLEN;
 124 
 125         GetUserName(uname, &unsize);
 126         snprintf(newname, sizeof(newname) - 1, "%s@%s@%.32s", name, uname, accel_gen_system_id());
 127         return newname;
 128 }
 129 
 130 static char *get_mmap_base_file(void)
 131 {
 132         static char windir[MAXPATHLEN+UNLEN + 3 + sizeof("\\\\@") + 1 + 32];
 133         char uname[UNLEN + 1];
 134         DWORD unsize = UNLEN;
 135         int l;
 136 
 137         GetTempPath(MAXPATHLEN, windir);
 138         GetUserName(uname, &unsize);
 139         l = strlen(windir);
 140         snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s@%.32s", ACCEL_FILEMAP_BASE, uname, accel_gen_system_id());
 141         return windir;
 142 }
 143 
 144 void zend_shared_alloc_create_lock(void)
 145 {
 146         memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
 147         if (!memory_mutex) {
 148                 zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex");
 149                 return;
 150         }
 151         ReleaseMutex(memory_mutex);
 152 }
 153 
 154 void zend_shared_alloc_lock_win32(void)
 155 {
 156         DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
 157 
 158         if (waitRes == WAIT_FAILED) {
 159                 zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
 160         }
 161 }
 162 
 163 void zend_shared_alloc_unlock_win32(void)
 164 {
 165         ReleaseMutex(memory_mutex);
 166 }
 167 
 168 static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
 169 {
 170         int err;
 171         void *wanted_mapping_base;
 172         char *mmap_base_file = get_mmap_base_file();
 173         FILE *fp = fopen(mmap_base_file, "r");
 174         MEMORY_BASIC_INFORMATION info;
 175 
 176         err = GetLastError();
 177         if (!fp) {
 178                 zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
 179                 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open base address file", err);
 180                 *error_in="fopen";
 181                 return ALLOC_FAILURE;
 182         }
 183         if (!fscanf(fp, "%p", &wanted_mapping_base)) {
 184                 err = GetLastError();
 185                 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
 186                 *error_in="read mapping base";
 187                 fclose(fp);
 188                 return ALLOC_FAILURE;
 189         }
 190         fclose(fp);
 191 
 192         /* Check if the requested address space is free */
 193         if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
 194             info.State != MEM_FREE ||
 195             info.RegionSize < requested_size) {
 196             err = ERROR_INVALID_ADDRESS;
 197                 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
 198                 return ALLOC_FAILURE;
 199         }
 200 
 201         mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
 202         err = GetLastError();
 203 
 204         if (mapping_base == NULL) {
 205                 if (err == ERROR_INVALID_ADDRESS) {
 206                         zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
 207                         return ALLOC_FAILURE;
 208                 }
 209                 return ALLOC_FAIL_MAPPING;
 210         }
 211         smm_shared_globals = (zend_smm_shared_globals *) mapping_base;
 212 
 213         return SUCCESSFULLY_REATTACHED;
 214 }
 215 
 216 static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
 217 {
 218         int err, ret;
 219         zend_shared_segment *shared_segment;
 220         int map_retries = 0;
 221         void *default_mapping_base_set[] = { 0, 0 };
 222         /* TODO: 
 223           improve fixed addresses on x64. It still makes no sense to do it as Windows addresses are virtual per se and can or should be randomized anyway 
 224           through Address Space Layout Radomization (ASLR). We can still let the OS do its job and be sure that each process gets the same address if
 225           desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
 226         */
 227 #if defined(_WIN64)
 228         void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
 229 #else
 230         void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
 231 #endif
 232         void **wanted_mapping_base = default_mapping_base_set;
 233         TSRMLS_FETCH();
 234 
 235         zend_shared_alloc_lock_win32();
 236         /* Mapping retries: When Apache2 restarts, the parent process startup routine
 237            can be called before the child process is killed. In this case, the map will fail
 238            and we have to sleep some time (until the child releases the mapping object) and retry.*/
 239         do {
 240                 memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
 241                 err = GetLastError();
 242                 if (memfile == NULL) {
 243                         break;
 244                 }
 245 
 246                 ret =  zend_shared_alloc_reattach(requested_size, error_in);
 247                 err = GetLastError();
 248                 if (ret == ALLOC_FAIL_MAPPING) {
 249                         /* Mapping failed, wait for mapping object to get freed and retry */
 250                         CloseHandle(memfile);
 251                         memfile = NULL;
 252                         if (++map_retries >= MAX_MAP_RETRIES) {
 253                                 break;
 254                         }
 255                         zend_shared_alloc_unlock_win32();
 256                         Sleep(1000 * (map_retries + 1));
 257                         zend_shared_alloc_lock_win32();
 258                 } else {
 259                         zend_shared_alloc_unlock_win32();
 260                         return ret;
 261                 }
 262         } while (1);
 263 
 264         if (map_retries == MAX_MAP_RETRIES) {
 265                 zend_shared_alloc_unlock_win32();
 266                 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
 267                 *error_in = "OpenFileMapping";
 268                 return ALLOC_FAILURE;
 269         }
 270 
 271         /* creating segment here */
 272         *shared_segments_count = 1;
 273         *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
 274         if (!*shared_segments_p) {
 275                 zend_shared_alloc_unlock_win32();
 276                 zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", GetLastError());
 277                 *error_in = "calloc";
 278                 return ALLOC_FAILURE;
 279         }
 280         shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
 281         (*shared_segments_p)[0] = shared_segment;
 282 
 283         memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, requested_size,
 284                                                                 create_name_with_username(ACCEL_FILEMAP_NAME));
 285         err = GetLastError();
 286         if (memfile == NULL) {
 287                 zend_shared_alloc_unlock_win32();
 288                 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
 289                 *error_in = "CreateFileMapping";
 290                 return ALLOC_FAILURE;
 291         }
 292 
 293         /* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
 294            be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
 295            in high memory. */
 296         if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
 297                 do {
 298                         OSVERSIONINFOEX osvi;
 299                         SYSTEM_INFO si;
 300 
 301                         ZeroMemory(&si, sizeof(SYSTEM_INFO));
 302                         ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
 303 
 304                         osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
 305 
 306                         if (! GetVersionEx ((OSVERSIONINFO *) &osvi)) {
 307                                 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
 308                                 if (!GetVersionEx((OSVERSIONINFO *)&osvi)) {
 309                                         break;
 310                                 }
 311                         }
 312 
 313                         GetSystemInfo(&si);
 314 
 315                         /* Are we running Vista ? */
 316                         if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion >= 6) {
 317                                 wanted_mapping_base = vista_mapping_base_set;
 318                         }
 319                 } while (0);
 320         } else {
 321                 char *s = ZCG(accel_directives).mmap_base;
 322 
 323                 /* skip leading 0x, %p assumes hexdeciaml format anyway */
 324                 if (*s == '0' && *(s + 1) == 'x') {
 325                         s += 2;
 326                 }
 327                 if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
 328                         zend_shared_alloc_unlock_win32();
 329                         zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
 330                         return ALLOC_FAILURE;
 331                 }
 332         }
 333 
 334         do {
 335                 shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
 336                 if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
 337                         break;
 338                 }
 339                 wanted_mapping_base++;
 340         } while (!mapping_base);
 341 
 342         err = GetLastError();
 343         if (mapping_base == NULL) {
 344                 zend_shared_alloc_unlock_win32();
 345                 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
 346                 *error_in = "MapViewOfFile";
 347                 return ALLOC_FAILURE;
 348         } else {
 349                 char *mmap_base_file = get_mmap_base_file();
 350                 FILE *fp = fopen(mmap_base_file, "w");
 351                 err = GetLastError();
 352                 if (!fp) {
 353                         zend_shared_alloc_unlock_win32();
 354                         zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
 355                         zend_win_error_message(ACCEL_LOG_FATAL, "Unable to write base address", err);
 356                         return ALLOC_FAILURE;
 357                 }
 358                 fprintf(fp, "%p\n", mapping_base);
 359                 fclose(fp);
 360         }
 361 
 362         shared_segment->pos = 0;
 363         shared_segment->size = requested_size;
 364 
 365         zend_shared_alloc_unlock_win32();
 366 
 367         return ALLOC_SUCCESS;
 368 }
 369 
 370 static int detach_segment(zend_shared_segment *shared_segment)
 371 {
 372         zend_shared_alloc_lock_win32();
 373         if (mapping_base) {
 374                 UnmapViewOfFile(mapping_base);
 375         }
 376         CloseHandle(memfile);
 377         zend_shared_alloc_unlock_win32();
 378         CloseHandle(memory_mutex);
 379         return 0;
 380 }
 381 
 382 static size_t segment_type_size(void)
 383 {
 384         return sizeof(zend_shared_segment);
 385 }
 386 
 387 zend_shared_memory_handlers zend_alloc_win32_handlers = {
 388         create_segments,
 389         detach_segment,
 390         segment_type_size
 391 };

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