xref: /PHP-5.5/ext/session/mod_mm.c (revision 73c1be26)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2015 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    | Author: Sascha Schumann <sascha@schumann.cx>                         |
16    +----------------------------------------------------------------------+
17  */
18 
19 /* $Id$ */
20 
21 #include "php.h"
22 
23 #ifdef HAVE_LIBMM
24 
25 #include <unistd.h>
26 #include <mm.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <fcntl.h>
31 
32 #include "php_session.h"
33 #include "mod_mm.h"
34 #include "SAPI.h"
35 
36 #ifdef ZTS
37 # error mm is not thread-safe
38 #endif
39 
40 #define PS_MM_FILE "session_mm_"
41 
42 /* For php_uint32 */
43 #include "ext/standard/basic_functions.h"
44 
45 /* This list holds all data associated with one session. */
46 
47 typedef struct ps_sd {
48 	struct ps_sd *next;
49 	php_uint32 hv;		/* hash value of key */
50 	time_t ctime;		/* time of last change */
51 	void *data;
52 	size_t datalen;		/* amount of valid data */
53 	size_t alloclen;	/* amount of allocated memory for data */
54 	char key[1];		/* inline key */
55 } ps_sd;
56 
57 typedef struct {
58 	MM *mm;
59 	ps_sd **hash;
60 	php_uint32 hash_max;
61 	php_uint32 hash_cnt;
62 	pid_t owner;
63 } ps_mm;
64 
65 static ps_mm *ps_mm_instance = NULL;
66 
67 #if 0
68 # define ps_mm_debug(a) printf a
69 #else
70 # define ps_mm_debug(a)
71 #endif
72 
ps_sd_hash(const char * data,int len)73 static inline php_uint32 ps_sd_hash(const char *data, int len)
74 {
75 	php_uint32 h;
76 	const char *e = data + len;
77 
78 	for (h = 2166136261U; data < e; ) {
79 		h *= 16777619;
80 		h ^= *data++;
81 	}
82 
83 	return h;
84 }
85 
hash_split(ps_mm * data)86 static void hash_split(ps_mm *data)
87 {
88 	php_uint32 nmax;
89 	ps_sd **nhash;
90 	ps_sd **ohash, **ehash;
91 	ps_sd *ps, *next;
92 
93 	nmax = ((data->hash_max + 1) << 1) - 1;
94 	nhash = mm_calloc(data->mm, nmax + 1, sizeof(*data->hash));
95 
96 	if (!nhash) {
97 		/* no further memory to expand hash table */
98 		return;
99 	}
100 
101 	ehash = data->hash + data->hash_max + 1;
102 	for (ohash = data->hash; ohash < ehash; ohash++) {
103 		for (ps = *ohash; ps; ps = next) {
104 			next = ps->next;
105 			ps->next = nhash[ps->hv & nmax];
106 			nhash[ps->hv & nmax] = ps;
107 		}
108 	}
109 	mm_free(data->mm, data->hash);
110 
111 	data->hash = nhash;
112 	data->hash_max = nmax;
113 }
114 
ps_sd_new(ps_mm * data,const char * key)115 static ps_sd *ps_sd_new(ps_mm *data, const char *key)
116 {
117 	php_uint32 hv, slot;
118 	ps_sd *sd;
119 	int keylen;
120 
121 	keylen = strlen(key);
122 
123 	sd = mm_malloc(data->mm, sizeof(ps_sd) + keylen);
124 	if (!sd) {
125 		TSRMLS_FETCH();
126 
127 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "mm_malloc failed, avail %ld, err %s", mm_available(data->mm), mm_error());
128 		return NULL;
129 	}
130 
131 	hv = ps_sd_hash(key, keylen);
132 	slot = hv & data->hash_max;
133 
134 	sd->ctime = 0;
135 	sd->hv = hv;
136 	sd->data = NULL;
137 	sd->alloclen = sd->datalen = 0;
138 
139 	memcpy(sd->key, key, keylen + 1);
140 
141 	sd->next = data->hash[slot];
142 	data->hash[slot] = sd;
143 
144 	data->hash_cnt++;
145 
146 	if (!sd->next) {
147 		if (data->hash_cnt >= data->hash_max) {
148 			hash_split(data);
149 		}
150 	}
151 
152 	ps_mm_debug(("inserting %s(%p) into slot %d\n", key, sd, slot));
153 
154 	return sd;
155 }
156 
ps_sd_destroy(ps_mm * data,ps_sd * sd)157 static void ps_sd_destroy(ps_mm *data, ps_sd *sd)
158 {
159 	php_uint32 slot;
160 
161 	slot = ps_sd_hash(sd->key, strlen(sd->key)) & data->hash_max;
162 
163 	if (data->hash[slot] == sd) {
164 		data->hash[slot] = sd->next;
165 	} else {
166 		ps_sd *prev;
167 
168 		/* There must be some entry before the one we want to delete */
169 		for (prev = data->hash[slot]; prev->next != sd; prev = prev->next);
170 		prev->next = sd->next;
171 	}
172 
173 	data->hash_cnt--;
174 
175 	if (sd->data) {
176 		mm_free(data->mm, sd->data);
177 	}
178 
179 	mm_free(data->mm, sd);
180 }
181 
ps_sd_lookup(ps_mm * data,const char * key,int rw)182 static ps_sd *ps_sd_lookup(ps_mm *data, const char *key, int rw)
183 {
184 	php_uint32 hv, slot;
185 	ps_sd *ret, *prev;
186 
187 	hv = ps_sd_hash(key, strlen(key));
188 	slot = hv & data->hash_max;
189 
190 	for (prev = NULL, ret = data->hash[slot]; ret; prev = ret, ret = ret->next) {
191 		if (ret->hv == hv && !strcmp(ret->key, key)) {
192 			break;
193 		}
194 	}
195 
196 	if (ret && rw && ret != data->hash[slot]) {
197 		/* Move the entry to the top of the linked list */
198 		if (prev) {
199 			prev->next = ret->next;
200 		}
201 
202 		ret->next = data->hash[slot];
203 		data->hash[slot] = ret;
204 	}
205 
206 	ps_mm_debug(("lookup(%s): ret=%p,hv=%u,slot=%d\n", key, ret, hv, slot));
207 
208 	return ret;
209 }
210 
ps_mm_key_exists(ps_mm * data,const char * key TSRMLS_DC)211 static int ps_mm_key_exists(ps_mm *data, const char *key TSRMLS_DC)
212 {
213 	ps_sd *sd;
214 
215 	if (!key) {
216 		return FAILURE;
217 	}
218 	sd = ps_sd_lookup(data, key, 0);
219 	if (sd) {
220 		return SUCCESS;
221 	}
222 	return FAILURE;
223 }
224 
225 ps_module ps_mod_mm = {
226 	PS_MOD_SID(mm)
227 };
228 
229 #define PS_MM_DATA ps_mm *data = PS_GET_MOD_DATA()
230 
ps_mm_initialize(ps_mm * data,const char * path)231 static int ps_mm_initialize(ps_mm *data, const char *path)
232 {
233 	data->owner = getpid();
234 	data->mm = mm_create(0, path);
235 	if (!data->mm) {
236 		return FAILURE;
237 	}
238 
239 	data->hash_cnt = 0;
240 	data->hash_max = 511;
241 	data->hash = mm_calloc(data->mm, data->hash_max + 1, sizeof(ps_sd *));
242 	if (!data->hash) {
243 		mm_destroy(data->mm);
244 		return FAILURE;
245 	}
246 
247 	return SUCCESS;
248 }
249 
ps_mm_destroy(ps_mm * data)250 static void ps_mm_destroy(ps_mm *data)
251 {
252 	int h;
253 	ps_sd *sd, *next;
254 
255 	/* This function is called during each module shutdown,
256 	   but we must not release the shared memory pool, when
257 	   an Apache child dies! */
258 	if (data->owner != getpid()) {
259 		return;
260 	}
261 
262 	for (h = 0; h < data->hash_max + 1; h++) {
263 		for (sd = data->hash[h]; sd; sd = next) {
264 			next = sd->next;
265 			ps_sd_destroy(data, sd);
266 		}
267 	}
268 
269 	mm_free(data->mm, data->hash);
270 	mm_destroy(data->mm);
271 	free(data);
272 }
273 
PHP_MINIT_FUNCTION(ps_mm)274 PHP_MINIT_FUNCTION(ps_mm)
275 {
276 	int save_path_len = strlen(PS(save_path));
277 	int mod_name_len = strlen(sapi_module.name);
278 	int euid_len;
279 	char *ps_mm_path, euid[30];
280 	int ret;
281 
282 	ps_mm_instance = calloc(sizeof(*ps_mm_instance), 1);
283 	if (!ps_mm_instance) {
284 		return FAILURE;
285 	}
286 
287 	if (!(euid_len = slprintf(euid, sizeof(euid), "%d", geteuid()))) {
288 		free(ps_mm_instance);
289 		ps_mm_instance = NULL;
290 		return FAILURE;
291 	}
292 
293 	/* Directory + '/' + File + Module Name + Effective UID + \0 */
294 	ps_mm_path = emalloc(save_path_len + 1 + (sizeof(PS_MM_FILE) - 1) + mod_name_len + euid_len + 1);
295 
296 	memcpy(ps_mm_path, PS(save_path), save_path_len);
297 	if (save_path_len && PS(save_path)[save_path_len - 1] != DEFAULT_SLASH) {
298 		ps_mm_path[save_path_len] = DEFAULT_SLASH;
299 		save_path_len++;
300 	}
301 	memcpy(ps_mm_path + save_path_len, PS_MM_FILE, sizeof(PS_MM_FILE) - 1);
302 	save_path_len += sizeof(PS_MM_FILE) - 1;
303 	memcpy(ps_mm_path + save_path_len, sapi_module.name, mod_name_len);
304 	save_path_len += mod_name_len;
305 	memcpy(ps_mm_path + save_path_len, euid, euid_len);
306 	ps_mm_path[save_path_len + euid_len] = '\0';
307 
308 	ret = ps_mm_initialize(ps_mm_instance, ps_mm_path);
309 
310 	efree(ps_mm_path);
311 
312 	if (ret != SUCCESS) {
313 		free(ps_mm_instance);
314 		ps_mm_instance = NULL;
315 		return FAILURE;
316 	}
317 
318 	php_session_register_module(&ps_mod_mm);
319 	return SUCCESS;
320 }
321 
PHP_MSHUTDOWN_FUNCTION(ps_mm)322 PHP_MSHUTDOWN_FUNCTION(ps_mm)
323 {
324 	if (ps_mm_instance) {
325 		ps_mm_destroy(ps_mm_instance);
326 		return SUCCESS;
327 	}
328 	return FAILURE;
329 }
330 
PS_OPEN_FUNC(mm)331 PS_OPEN_FUNC(mm)
332 {
333 	ps_mm_debug(("open: ps_mm_instance=%p\n", ps_mm_instance));
334 
335 	if (!ps_mm_instance) {
336 		return FAILURE;
337 	}
338 	PS_SET_MOD_DATA(ps_mm_instance);
339 
340 	return SUCCESS;
341 }
342 
PS_CLOSE_FUNC(mm)343 PS_CLOSE_FUNC(mm)
344 {
345 	PS_SET_MOD_DATA(NULL);
346 
347 	return SUCCESS;
348 }
349 
PS_READ_FUNC(mm)350 PS_READ_FUNC(mm)
351 {
352 	PS_MM_DATA;
353 	ps_sd *sd;
354 	int ret = FAILURE;
355 
356 	mm_lock(data->mm, MM_LOCK_RD);
357 
358 	/* If there is an ID and strict mode, verify existence */
359 	if (PS(use_strict_mode)
360 		&& ps_mm_key_exists(data, key TSRMLS_CC) == FAILURE) {
361 		/* key points to PS(id), but cannot change here. */
362 		if (key) {
363 			efree(PS(id));
364 			PS(id) = NULL;
365 		}
366 		PS(id) = PS(mod)->s_create_sid((void **)&data, NULL TSRMLS_CC);
367 		if (!PS(id)) {
368 			return FAILURE;
369 		}
370 		if (PS(use_cookies)) {
371 			PS(send_cookie) = 1;
372 		}
373 		php_session_reset_id(TSRMLS_C);
374 		PS(session_status) = php_session_active;
375 	}
376 
377 	sd = ps_sd_lookup(data, PS(id), 0);
378 	if (sd) {
379 		*vallen = sd->datalen;
380 		*val = emalloc(sd->datalen + 1);
381 		memcpy(*val, sd->data, sd->datalen);
382 		(*val)[sd->datalen] = '\0';
383 		ret = SUCCESS;
384 	}
385 
386 	mm_unlock(data->mm);
387 
388 	return ret;
389 }
390 
PS_WRITE_FUNC(mm)391 PS_WRITE_FUNC(mm)
392 {
393 	PS_MM_DATA;
394 	ps_sd *sd;
395 
396 	mm_lock(data->mm, MM_LOCK_RW);
397 
398 	sd = ps_sd_lookup(data, key, 1);
399 	if (!sd) {
400 		sd = ps_sd_new(data, key);
401 		ps_mm_debug(("new entry for %s\n", key));
402 	}
403 
404 	if (sd) {
405 		if (vallen >= sd->alloclen) {
406 			if (data->mm) {
407 				mm_free(data->mm, sd->data);
408 			}
409 			sd->alloclen = vallen + 1;
410 			sd->data = mm_malloc(data->mm, sd->alloclen);
411 
412 			if (!sd->data) {
413 				ps_sd_destroy(data, sd);
414 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot allocate new data segment");
415 				sd = NULL;
416 			}
417 		}
418 		if (sd) {
419 			sd->datalen = vallen;
420 			memcpy(sd->data, val, vallen);
421 			time(&sd->ctime);
422 		}
423 	}
424 
425 	mm_unlock(data->mm);
426 
427 	return sd ? SUCCESS : FAILURE;
428 }
429 
PS_DESTROY_FUNC(mm)430 PS_DESTROY_FUNC(mm)
431 {
432 	PS_MM_DATA;
433 	ps_sd *sd;
434 
435 	mm_lock(data->mm, MM_LOCK_RW);
436 
437 	sd = ps_sd_lookup(data, key, 0);
438 	if (sd) {
439 		ps_sd_destroy(data, sd);
440 	}
441 
442 	mm_unlock(data->mm);
443 
444 	return SUCCESS;
445 }
446 
PS_GC_FUNC(mm)447 PS_GC_FUNC(mm)
448 {
449 	PS_MM_DATA;
450 	time_t limit;
451 	ps_sd **ohash, **ehash;
452 	ps_sd *sd, *next;
453 
454 	*nrdels = 0;
455 	ps_mm_debug(("gc\n"));
456 
457 	time(&limit);
458 
459 	limit -= maxlifetime;
460 
461 	mm_lock(data->mm, MM_LOCK_RW);
462 
463 	ehash = data->hash + data->hash_max + 1;
464 	for (ohash = data->hash; ohash < ehash; ohash++) {
465 		for (sd = *ohash; sd; sd = next) {
466 			next = sd->next;
467 			if (sd->ctime < limit) {
468 				ps_mm_debug(("purging %s\n", sd->key));
469 				ps_sd_destroy(data, sd);
470 				(*nrdels)++;
471 			}
472 		}
473 	}
474 
475 	mm_unlock(data->mm);
476 
477 	return SUCCESS;
478 }
479 
PS_CREATE_SID_FUNC(mm)480 PS_CREATE_SID_FUNC(mm)
481 {
482 	char *sid;
483 	int maxfail = 3;
484 	PS_MM_DATA;
485 
486 	do {
487 		sid = php_session_create_id((void **)&data, newlen TSRMLS_CC);
488 		/* Check collision */
489 		if (ps_mm_key_exists(data, sid TSRMLS_CC) == SUCCESS) {
490 			if (sid) {
491 				efree(sid);
492 				sid = NULL;
493 			}
494 			if (!(maxfail--)) {
495 				return NULL;
496 			}
497 		}
498 	} while(!sid);
499 
500 	return sid;
501 }
502 
503 #endif
504 
505 /*
506  * Local variables:
507  * tab-width: 4
508  * c-basic-offset: 4
509  * End:
510  * vim600: sw=4 ts=4 fdm=marker
511  * vim<600: sw=4 ts=4
512  */
513