Issues

ZF-1325: "Unwritable session.save_path" when using defaults in Zend_Session and php.ini

Description

<?php

set_include_path(get_include_path().PATH_SEPARATOR.'/path/to/zend/framework');

require_once 'Zend/Session.php';
Zend_Session::start();

?>

produces the following exception when session.save_path is not set in php.ini:

Uncaught exception 'Zend_Session_Exception' with message 'Unwritable session.save_path: ' in /path/to/zend/framework/Zend/Session.php:217

According to the PHP manual (http://nl2.php.net/manual/nl/…) session.save_path should default to "/tmp", but Zend_Session still complains. The following code works, but the ini_set call shouldn't be necessary.

<?php

set_include_path(get_include_path().PATH_SEPARATOR.'/path/to/zend/framework');

require_once 'Zend/Session.php';
ini_set('session.save_path','/tmp');
Zend_Session::start();

?>

Comments

added code blocks.

Looking at the code, I see in Zend/Session.php at the end of the {{setOptions()}} method:


        $savePath = ini_get('session.save_path');
        if (strpos($savePath, ';') !== false) {
            $savePath = explode(';', $savePath);
            $savePath = realpath(array_pop($savePath));
        }
        if (self::$_ignore_save_path !== true && !is_writable($savePath)) {
            throw new Zend_Session_Exception("Unwritable session.save_path: $savePath");
        }

But when I look at the www.php.net/manual/en/ref.session.php" rel="nofollow">PHP manual entry I see:

bq. session.save_path defines the argument which is passed to the save handler. If you choose the default files handler, this is the path where the files are created. Defaults to /tmp. See also session_save_path(). bq. There is an optional N argument to this directive that determines the number of directory levels your session files will be spread around in. For example, setting to '5;/tmp' may end up creating a session file and location like /tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If . In order to use N you must create all of these directories before use. A small shell script exists in ext/session to do this, it's called mod_files.sh. Also note that if N is used and greater than 0 then automatic garbage collection will not be performed, see a copy of php.ini for further information. Also, if you use N, be sure to surround session.save_path in "quotes" because the separator (\;) is also used for comments in php.ini.

If we consider it a good design decision that Zend_Session throws an Exception if the session path is not writable, then it must also check all of the child directories of the session.save_path when using the optional N argument. I think that this is not a good option, however, and I tend to question the sensibility of throwing an Exception at this point in the code for the purpose of verifying the session.save_path.

Volley to [~ralph] for additional comment.

Ok, after reading through the ext/session C, I know whats going on and why its going on. session_start(), regardless of whether or not it actually does start or not, will set its internal state to active. Which means, session_write_close (effectively) will run at RSHUTDOWN, thus changing the state of ext/session.

Also, the following code has the added benefit of being able to throw exceptions on ANY mod_handler ext/session decides to use, be it mm, files, db, or memcached. The idea as always, is that session_start should emit nothing from PHP and should start clean, otherwise, we should throw an exception as developers might want to be able to handle it at bootstrap time.


webdeveloper@webdevelopment ~ $ cat testerror.php
<?

ini_set('session.save_path', '/var/log');

class Zend_Session
{
  public function start()
  {
    set_error_handler(array('Zend_Session_Exception', 'handleSessionStartError'), E_ALL);
    session_start();
    restore_error_handler();
    if (Zend_Session_Exception::$sessionStartError !== null) {
       set_error_handler(array('Zend_Session_Exception', 'handleSilentWriteClose'), E_ALL);
       session_write_close();
       restore_error_handler();
       throw new Zend_Session_Exception(Zend_Session_Exception::$sessionStartError);
    }

  }
}

class Zend_Session_Exception extends Exception
{
  static public $sessionStartError = null;

  static public function handleSessionStartError($errno, $errstr)
  {
    self::$sessionStartError = $errstr;
  }

  static public function handleSilentWriteClose($errno, $errstr)
  { }

}


// this wouldbe bootstrap file
try
{
  Zend_Session::start();
} catch (Exception $e) {
  print_r('setup error: ' . $e->getMessage() . "\n");
}

echo "End of program

";
webdeveloper@webdevelopment ~ $ php testerror.php
setup error: session_start(): open(/var/log/sess_9bae147b205508d3dab70023eab739eb, O_RDWR) failed: Permission denied (13)
End of program

ABOVE IS PROOF-OF-CONCEPT ONLY, see the fixes in the code

Fisheye tab above or:

SVN r4763

Confirmed as fixed. Thanks to [~ralph]!