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