Skip to content

Commit e6c8640

Browse files
author
Yasuo Ohgaki
committed
WIP - test passes
1 parent ebb60ac commit e6c8640

21 files changed

+1256
-162
lines changed

ext/session/mod_files.c

+217-45
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,41 @@
1616
+----------------------------------------------------------------------+
1717
*/
1818

19-
/* $Id$ */
19+
/**************************************************************************
20+
* Files save handler should be used as reference implementations of session
21+
* save handlers. PS_* functions are called as follows with standard usage.
22+
*
23+
* PS_OPEN_FUNC() - Create module data that manages save handler.
24+
* PS_CREATE_SID() and/or PS_VALIDATE_SID()
25+
* - PS_CREATE_ID() is called if session ID(key) is not
26+
* provided or invalid. PS_VALIDATE_SID() is called to
27+
* verify session ID already exists or not to mitigate
28+
* session adoption vulunerabilty risk.
29+
* PS_READ_FUNC() - Read data from storage.
30+
* PS_GC_FUNC() - Perform GC. Called by probability
31+
* (session.gc_probability/session.gc_divisor).
32+
* PS_WRITE_FUNC() or PS_UPDATE_TIMESTAMP()
33+
* - Write session data or update session data timestamp.
34+
* It depends on session data change.
35+
* PS_CLOSE_FUNC() - Clean up module data created by PS_OPEN_FUNC().
36+
*
37+
* Session module gurantees PS_OPEN_FUNC() is called before calling other
38+
* PS_*_FUNC() functions. Other than this, session module may call any
39+
* PS_*_FUNC() at any time. You may assume non null *mod_data created by
40+
* PS_OPEN_FUNC() is passed to PS_*_FUNC().
41+
*
42+
* NOTE:
43+
* - Save handlers _MUST_NOT_ change/refer PS() values.
44+
* i.e. PS(id), PS(session_status), PS(mod) and any other PS() values.
45+
* Use only function parameters passed from session module.
46+
* - Save handler _MUST_ use PS_GET_MOD_DATA()/PS_SET_MOD_DATA() macro to
47+
* set/get save handler module data(mod_data). mod_data contains
48+
* data required by PS modules. It will not be NULL except PS_OPEN_FUNC().
49+
* - Refer to PS_* macros in php_session.h for function/parameter defitions.
50+
* - Returning FAILURE state from PS_* function results in raising errors.
51+
* Avoid failure state as much as possible.
52+
* - Use static ps_[module name]_[function name] functions for internal use.
53+
*************************************************************************/
2054

2155
#include "php.h"
2256

@@ -67,7 +101,8 @@ typedef struct {
67101
} ps_files;
68102

69103
ps_module ps_mod_files = {
70-
PS_MOD_SID(files)
104+
/* New save handlers MUST use PS_MOD_UPDATE_TIMESTAMP macro */
105+
PS_MOD_UPDATE_TIMESTAMP(files)
71106
};
72107

73108

@@ -182,6 +217,43 @@ static void ps_files_open(ps_files *data, const char *key)
182217
}
183218
}
184219

220+
static int ps_files_write(ps_files *data, zend_string *key, zend_string *val)
221+
{
222+
zend_long n;
223+
zend_stat_t sbuf;
224+
225+
/* PS(id) may be changed by calling session_regenerate_id().
226+
Re-initialization should be tried here. ps_files_open() checks
227+
data->lastkey and reopen when it is needed. */
228+
ps_files_open(data, key->val);
229+
if (data->fd < 0) {
230+
return FAILURE;
231+
}
232+
233+
/* Truncate file if the amount of new data is smaller than the existing data set. */
234+
if (val->len < (int)data->st_size) {
235+
php_ignore_value(ftruncate(data->fd, 0));
236+
}
237+
238+
#if defined(HAVE_PWRITE)
239+
n = pwrite(data->fd, val->val, val->len, 0);
240+
#else
241+
lseek(data->fd, 0, SEEK_SET);
242+
n = write(data->fd, val->val, val->len);
243+
#endif
244+
245+
if (n != val->len) {
246+
if (n == -1) {
247+
php_error_docref(NULL, E_WARNING, "write failed: %s (%d)", strerror(errno), errno);
248+
} else {
249+
php_error_docref(NULL, E_WARNING, "write wrote less bytes than requested");
250+
}
251+
return FAILURE;
252+
}
253+
254+
return SUCCESS;
255+
}
256+
185257
static int ps_files_cleanup_dir(const char *dirname, int maxlifetime)
186258
{
187259
DIR *dir;
@@ -252,6 +324,18 @@ static int ps_files_key_exists(ps_files *data, const char *key)
252324

253325
#define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA()
254326

327+
328+
/*
329+
* Open save handler. Setup resources that are needed by the handler.
330+
* PARAMETERS: PS_OPEN_ARGS in php_session.h
331+
* RETURN VALUE: SUCCESS or FAILURE. Must set non-NULL valid module data
332+
* (void **mod_data) with SUCCESS, NULL(default) for FAILUREs.
333+
*
334+
* Files save handler checks/create save_path directory and setup ps_files data.
335+
* Note that files save handler supports splitting session data into multiple
336+
* directories.
337+
* *mod_data, *save_path, *session_name are guranteed to have non-NULL values.
338+
*/
255339
PS_OPEN_FUNC(files)
256340
{
257341
ps_files *data;
@@ -316,6 +400,17 @@ PS_OPEN_FUNC(files)
316400
return SUCCESS;
317401
}
318402

403+
404+
/*
405+
* Clean up opened resources.
406+
* PARAMETERS: PS_CLOSE_ARGS in php_session.h
407+
* RETURN VALUE: SUCCESS. Must set PS module data(void **mod_data) to NULL.
408+
*
409+
* Files save handler closes open files and it's memory.
410+
* *mod_data is guranteed to have non-NULL value.
411+
* PS_CLOSE_FUNC() must set *mod_data to NULL. PS_CLOSE_FUNC() should not
412+
* fail.
413+
*/
319414
PS_CLOSE_FUNC(files)
320415
{
321416
PS_FILES_DATA;
@@ -333,32 +428,24 @@ PS_CLOSE_FUNC(files)
333428
return SUCCESS;
334429
}
335430

431+
432+
/*
433+
* Read session data from opened resource.
434+
* PARAMETERS: PS_READ_ARGS in php_session.h
435+
* RETURN VALUE: SUCCESS or FAILURE. Must set non-NULL session data to (zend_string **val)
436+
* for SUCCESS. NULL(default) for FAILUREs.
437+
*
438+
* Files save handler supports splitting session data into multiple
439+
* directories.
440+
* *mod_data, *key are guranteed to have non-NULL values.
441+
*/
336442
PS_READ_FUNC(files)
337443
{
338444
zend_long n;
339445
zend_stat_t sbuf;
340446
PS_FILES_DATA;
341447

342-
/* If strict mode, check session id existence */
343-
if (PS(use_strict_mode) &&
344-
ps_files_key_exists(data, key? key->val : NULL) == FAILURE) {
345-
/* key points to PS(id), but cannot change here. */
346-
if (key) {
347-
zend_string_release(PS(id));
348-
PS(id) = NULL;
349-
}
350-
PS(id) = PS(mod)->s_create_sid((void **)&data);
351-
if (!PS(id)) {
352-
return FAILURE;
353-
}
354-
if (PS(use_cookies)) {
355-
PS(send_cookie) = 1;
356-
}
357-
php_session_reset_id();
358-
PS(session_status) = php_session_active;
359-
}
360-
361-
ps_files_open(data, PS(id)->val);
448+
ps_files_open(data, key->val);
362449
if (data->fd < 0) {
363450
return FAILURE;
364451
}
@@ -390,47 +477,82 @@ PS_READ_FUNC(files)
390477
php_error_docref(NULL, E_WARNING, "read returned less bytes than requested");
391478
}
392479
zend_string_release(*val);
480+
*val = STR_EMPTY_ALLOC();
393481
return FAILURE;
394482
}
395483

396484
return SUCCESS;
397485
}
398486

487+
488+
/*
489+
* Write session data.
490+
* PARAMETERS: PS_WRITE_ARGS in php_session.h
491+
* RETURN VALUE: SUCCESS or FAILURE.
492+
*
493+
* PS_WRITE_FUNC() must write session data(zend_string *val) unconditionally.
494+
* *mod_data, *key, *val are guranteed to have non-NULL values.
495+
*/
399496
PS_WRITE_FUNC(files)
400497
{
401-
zend_long n;
402498
PS_FILES_DATA;
403499

404-
ps_files_open(data, key->val);
405-
if (data->fd < 0) {
406-
return FAILURE;
407-
}
500+
return ps_files_write(data, key, val);
501+
}
408502

409-
/* Truncate file if the amount of new data is smaller than the existing data set. */
410503

411-
if (val->len < (int)data->st_size) {
412-
php_ignore_value(ftruncate(data->fd, 0));
504+
/*
505+
* Update session data modification/access time stamp.
506+
* PARAMETERS: PS_UPDATE_TIMESTAMP_ARGS in php_session.h
507+
* RETURN VALUE: SUCCESS or FAILURE.
508+
*
509+
* PS_UPDATE_TIMESTAMP_FUNC() updates time stamp(mtime) so that active session
510+
* data files will not be purged by GC. If session data storage does not need to
511+
* update timestamp, it should return SUCCESS simply. (e.g. Memcache)
512+
* *mod_data, *key, *val are guranteed to have non-NULL values.
513+
*
514+
* NOTE: Updating access timestamp at PS_READ_FUNC() may extend life of obsolete
515+
* session data. Use of PS_UPDATE_TIMESTAMP_FUNC() is prefered whenenver it is
516+
* possible.
517+
*/
518+
PS_UPDATE_TIMESTAMP_FUNC(files)
519+
{
520+
char buf[MAXPATHLEN];
521+
struct utimbuf newtimebuf;
522+
struct utimbuf *newtime = &newtimebuf;
523+
int ret;
524+
PS_FILES_DATA;
525+
526+
if (!ps_files_path_create(buf, sizeof(buf), data, key->val)) {
527+
return FAILURE;
413528
}
414529

415-
#if defined(HAVE_PWRITE)
416-
n = pwrite(data->fd, val->val, val->len, 0);
530+
/* Update mtime */
531+
#ifdef HAVE_UTIME_NULL
532+
newtime = NULL;
417533
#else
418-
lseek(data->fd, 0, SEEK_SET);
419-
n = write(data->fd, val->val, val->len);
534+
newtime->modtime = newtime->actime = time(NULL);
420535
#endif
421-
422-
if (n != val->len) {
423-
if (n == -1) {
424-
php_error_docref(NULL, E_WARNING, "write failed: %s (%d)", strerror(errno), errno);
425-
} else {
426-
php_error_docref(NULL, E_WARNING, "write wrote less bytes than requested");
427-
}
428-
return FAILURE;
536+
ret = VCWD_UTIME(buf, newtime);
537+
if (ret == -1) {
538+
/* New session ID, create data file */
539+
return ps_files_write(data, key, val);
429540
}
430541

431542
return SUCCESS;
432543
}
433544

545+
546+
/*
547+
* Delete session data.
548+
* PARAMETERS: PS_DESTROY_ARGS in php_session.h
549+
* RETURN VALUE: SUCCESS or FAILURE.
550+
*
551+
* PS_DESTROY_FUNC() must remove the session data specified by *key from
552+
* session data storage unconditionally. It must not return FAILURE for
553+
* non-existent session data.
554+
* *mod_data, *key are guranteed to have non-NULL values.
555+
*/
434556
PS_DESTROY_FUNC(files)
435557
{
436558
char buf[MAXPATHLEN];
@@ -455,11 +577,23 @@ PS_DESTROY_FUNC(files)
455577
return SUCCESS;
456578
}
457579

580+
581+
/*
582+
* Cleanup expired session data.
583+
* PARAMETERS: PS_GC_ARGS in php_session.h
584+
* RETURN VALUE: SUCCESS or FAILURE. Number of deleted records(int *nrdels(default=-1)).
585+
*
586+
* PS_GC_FUNC() must remove session data that are not accessed
587+
* 'session.maxlifetime'(seconds). If storage does not need manual GC, it
588+
* may return SUCCESS simply. (e.g. Memcache) It must set number of records
589+
* deleted(nrdels).
590+
* *mod_data is guranteed to have non-NULL value.
591+
*/
458592
PS_GC_FUNC(files)
459593
{
460594
PS_FILES_DATA;
461595

462-
/* we don't perform any cleanup, if dirdepth is larger than 0.
596+
/* We don't perform any cleanup, if dirdepth is larger than 0.
463597
we return SUCCESS, since all cleanup should be handled by
464598
an external entity (i.e. find -ctime x | xargs rm) */
465599

@@ -470,6 +604,20 @@ PS_GC_FUNC(files)
470604
return SUCCESS;
471605
}
472606

607+
608+
/*
609+
* Create session ID.
610+
* PARAMETERS: PS_CREATE_SID_ARGS in php_session.h
611+
* RETURN VALUE: Valid session ID(zend_string *) or NULL for FAILURE.
612+
*
613+
* PS_CREATE_SID_FUNC() must check collision. i.e. Check session data if
614+
* new sid exists already.
615+
* *mod_data is guranteed to have non-NULL value.
616+
* NOTE: Default php_session_create_id() does not check collision. If
617+
* NULL is returned, session module create new ID by using php_session_create_id().
618+
* If php_session_create_id() fails due to invalid configuration, it raises E_ERROR.
619+
* NULL return value checks from php_session_create_id() is not required generally.
620+
*/
473621
PS_CREATE_SID_FUNC(files)
474622
{
475623
zend_string *sid;
@@ -478,13 +626,21 @@ PS_CREATE_SID_FUNC(files)
478626

479627
do {
480628
sid = php_session_create_id((void**)&data);
629+
if (!sid) {
630+
if (--maxfail < 0) {
631+
return NULL;
632+
} else {
633+
continue;
634+
}
635+
}
481636
/* Check collision */
482-
if (data && ps_files_key_exists(data, sid? sid->val : NULL) == SUCCESS) {
637+
/* FIXME: mod_data(data) should not be NULL (User handler could be NULL) */
638+
if (data && ps_files_key_exists(data, sid->val) == SUCCESS) {
483639
if (sid) {
484640
zend_string_release(sid);
485641
sid = NULL;
486642
}
487-
if (!(maxfail--)) {
643+
if (--maxfail < 0) {
488644
return NULL;
489645
}
490646
}
@@ -494,6 +650,22 @@ PS_CREATE_SID_FUNC(files)
494650
}
495651

496652

653+
/*
654+
* Check session ID existence for use_strict_mode support.
655+
* PARAMETERS: PS_VALIDATE_SID_ARGS in php_session.h
656+
* RETURN VALUE: SUCCESS or FAILURE.
657+
*
658+
* Return SUCCESS for valid key(already exsting session).
659+
* Return FAILURE for invalid key(non-existing session).
660+
* *mod_data, *key are guranteed to have non-NULL values.
661+
*/
662+
PS_VALIDATE_SID_FUNC(files)
663+
{
664+
PS_FILES_DATA;
665+
666+
return ps_files_key_exists(data, key->val);
667+
}
668+
497669
/*
498670
* Local variables:
499671
* tab-width: 4

ext/session/mod_files.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424
extern ps_module ps_mod_files;
2525
#define ps_files_ptr &ps_mod_files
2626

27-
PS_FUNCS_SID(files);
27+
PS_FUNCS_UPDATE_TIMESTAMP(files);
2828

2929
#endif

0 commit comments

Comments
 (0)