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
47typedef 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
57typedef 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
65static 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
73static 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
86static 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
115static 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
157static 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
182static 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
211static 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
225ps_module ps_mod_mm = {
226 PS_MOD_SID(mm)
227};
228
229#define PS_MM_DATA ps_mm *data = PS_GET_MOD_DATA()
230
231static 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
250static 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
274PHP_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
322PHP_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
331PS_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
343PS_CLOSE_FUNC(mm)
344{
345 PS_SET_MOD_DATA(NULL);
346
347 return SUCCESS;
348}
349
350PS_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
391PS_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
430PS_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
447PS_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
480PS_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