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 <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 */
48 static HashTable xlat_table;
49 static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL;
50 static const char *g_shared_model;
51 /* pointer to globals allocated in SHM and shared across processes */
52 zend_smm_shared_globals *smm_shared_globals;
53
54 #ifndef ZEND_WIN32
55 #ifdef ZTS
56 static MUTEX_T zts_lock;
57 #endif
58 int lock_file;
59 static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8];
60 #endif
61
62 static 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
zend_shared_alloc_create_lock(void)79 void 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
no_memory_bailout(size_t allocate_size,char * error)102 static 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
copy_shared_segments(void * to,void * from,int count,int size)107 static 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
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 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)
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
zend_shared_alloc_startup(size_t requested_size)151 int 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
zend_shared_alloc_shutdown(void)248 void 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
zend_shared_alloc_get_largest_free_block(void)273 static 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 %ld bytes (%ld bytes free)", (long)size, (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
zend_shared_alloc(size_t size)297 void *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
zend_shared_memdup_size(void * source,size_t size)326 int zend_shared_memdup_size(void *source, size_t size)
327 {
328 void **old_p;
329
330 if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) {
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
_zend_shared_memdup(void * source,size_t size,zend_bool free_source TSRMLS_DC)338 void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC)
339 {
340 void **old_p, *retval;
341
342 if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) {
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 zend_shared_alloc_register_xlat_entry(source, retval);
350 if (free_source) {
351 interned_efree((char*)source);
352 }
353 return retval;
354 }
355
zend_shared_alloc_safe_unlock(TSRMLS_D)356 void 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 */
365 static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1);
366 static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1);
367 #endif
368
zend_shared_alloc_lock(TSRMLS_D)369 void 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, 100, NULL, NULL, 1);
406 }
407
zend_shared_alloc_unlock(TSRMLS_D)408 void 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
zend_shared_alloc_clear_xlat_table(void)427 void zend_shared_alloc_clear_xlat_table(void)
428 {
429 zend_hash_clean(&xlat_table);
430 }
431
zend_shared_alloc_register_xlat_entry(const void * old,const void * new)432 void zend_shared_alloc_register_xlat_entry(const void *old, const void *new)
433 {
434 zend_hash_index_update(&xlat_table, (ulong)old, (void*)&new, sizeof(void *), NULL);
435 }
436
zend_shared_alloc_get_xlat_entry(const void * old)437 void *zend_shared_alloc_get_xlat_entry(const void *old)
438 {
439 void **retval;
440
441 if (zend_hash_index_find(&xlat_table, (ulong)old, (void **)&retval) == FAILURE) {
442 return NULL;
443 }
444 return *retval;
445 }
446
zend_shared_alloc_get_free_memory(void)447 size_t zend_shared_alloc_get_free_memory(void)
448 {
449 return ZSMMG(shared_free);
450 }
451
zend_shared_alloc_save_state(void)452 void 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
zend_shared_alloc_restore_state(void)462 void 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
zend_accel_get_shared_model(void)474 const char *zend_accel_get_shared_model(void)
475 {
476 return g_shared_model;
477 }
478
zend_accel_shared_protect(int mode TSRMLS_DC)479 void 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