root/main/streams/userspace.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. stream_wrapper_dtor
  2. PHP_MINIT_FUNCTION
  3. user_wrapper_opener
  4. user_wrapper_opendir
  5. PHP_FUNCTION
  6. PHP_FUNCTION
  7. PHP_FUNCTION
  8. php_userstreamop_write
  9. php_userstreamop_read
  10. php_userstreamop_close
  11. php_userstreamop_flush
  12. php_userstreamop_seek
  13. statbuf_from_array
  14. php_userstreamop_stat
  15. php_userstreamop_set_option
  16. user_wrapper_unlink
  17. user_wrapper_rename
  18. user_wrapper_mkdir
  19. user_wrapper_rmdir
  20. user_wrapper_stat_url
  21. php_userstreamop_readdir
  22. php_userstreamop_closedir
  23. php_userstreamop_rewinddir

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2008 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Wez Furlong <wez@thebrainroom.com>                          |
   |          Sara Golemon <pollita@php.net>                              |
   +----------------------------------------------------------------------+
*/

/* $Id: userspace.c,v 1.31.2.3.2.8 2007/12/31 07:20:15 sebastian Exp $ */

#include "php.h"
#include "php_globals.h"
#include "ext/standard/file.h"

static int le_protocols;

struct php_user_stream_wrapper {
        char * protoname;
        char * classname;
        zend_class_entry *ce;
        php_stream_wrapper wrapper;
};

static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC);
static int user_wrapper_stat_url(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC);
static int user_wrapper_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC);
static int user_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC);
static int user_wrapper_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC);
static int user_wrapper_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC);
static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, char *filename, char *mode,
                int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC);

static php_stream_wrapper_ops user_stream_wops = {
        user_wrapper_opener,
        NULL, /* close - the streams themselves know how */
        NULL, /* stat - the streams themselves know how */
        user_wrapper_stat_url,
        user_wrapper_opendir,
        "user-space",
        user_wrapper_unlink,
        user_wrapper_rename,
        user_wrapper_mkdir,
        user_wrapper_rmdir
};


static void stream_wrapper_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
        struct php_user_stream_wrapper * uwrap = (struct php_user_stream_wrapper*)rsrc->ptr;

        efree(uwrap->protoname);
        efree(uwrap->classname);
        efree(uwrap);
}


PHP_MINIT_FUNCTION(user_streams)
{
        le_protocols = zend_register_list_destructors_ex(stream_wrapper_dtor, NULL, "stream factory", 0);
        if (le_protocols == FAILURE)
                return FAILURE;

        REGISTER_LONG_CONSTANT("STREAM_USE_PATH",                       USE_PATH, CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_IGNORE_URL",             IGNORE_URL, CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_ENFORCE_SAFE_MODE",      ENFORCE_SAFE_MODE, CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_REPORT_ERRORS",          REPORT_ERRORS, CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_MUST_SEEK",                      STREAM_MUST_SEEK, CONST_CS|CONST_PERSISTENT);

        REGISTER_LONG_CONSTANT("STREAM_URL_STAT_LINK",          PHP_STREAM_URL_STAT_LINK,               CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_URL_STAT_QUIET",         PHP_STREAM_URL_STAT_QUIET,              CONST_CS|CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("STREAM_MKDIR_RECURSIVE",        PHP_STREAM_MKDIR_RECURSIVE,             CONST_CS|CONST_PERSISTENT);

        REGISTER_LONG_CONSTANT("STREAM_IS_URL", PHP_STREAM_IS_URL,              CONST_CS|CONST_PERSISTENT);
        return SUCCESS;
}

struct _php_userstream_data {
        struct php_user_stream_wrapper * wrapper;
        zval * object;
};
typedef struct _php_userstream_data php_userstream_data_t;

/* names of methods */
#define USERSTREAM_OPEN         "stream_open"
#define USERSTREAM_CLOSE        "stream_close"
#define USERSTREAM_READ         "stream_read"
#define USERSTREAM_WRITE        "stream_write"
#define USERSTREAM_FLUSH        "stream_flush"
#define USERSTREAM_SEEK         "stream_seek"
#define USERSTREAM_TELL         "stream_tell"
#define USERSTREAM_EOF          "stream_eof"
#define USERSTREAM_STAT         "stream_stat"
#define USERSTREAM_STATURL      "url_stat"
#define USERSTREAM_UNLINK       "unlink"
#define USERSTREAM_RENAME       "rename"
#define USERSTREAM_MKDIR        "mkdir"
#define USERSTREAM_RMDIR        "rmdir"
#define USERSTREAM_DIR_OPEN             "dir_opendir"
#define USERSTREAM_DIR_READ             "dir_readdir"
#define USERSTREAM_DIR_REWIND   "dir_rewinddir"
#define USERSTREAM_DIR_CLOSE    "dir_closedir"
#define USERSTREAM_LOCK     "stream_lock"

/* {{{ class should have methods like these:
 
        function stream_open($path, $mode, $options, &$opened_path)
        {
                return true/false;
        }
        
        function stream_read($count)
        {
                return false on error;
                else return string;
        }
        
        function stream_write($data)
        {
                return false on error;
                else return count written;
        }
        
        function stream_close()
        {
        }
        
        function stream_flush()
        {
                return true/false;
        }
        
        function stream_seek($offset, $whence)
        {
                return true/false;
        }

        function stream_tell()
        {
                return (int)$position;
        }

        function stream_eof()
        {
                return true/false;
        }

        function stream_stat()
        {
                return array( just like that returned by fstat() );
        }

        function url_stat(string $url, int $flags)
        {
                return array( just like that returned by stat() );
        }

        function unlink(string $url)
        {
                return true / false;
        }

        function rename(string $from, string $to)
        {
                return true / false;
        }

        function mkdir($dir, $mode, $options)
        {
                return true / false;
        }

        function rmdir($dir, $options)
        {
                return true / false;
        }

        function dir_opendir(string $url, int $options)
        {
                return true / false;
        }

        function dir_readdir()
        {
                return string next filename in dir ;
        }

        function dir_closedir()
        {
                release dir related resources;
        }

        function dir_rewinddir()
        {
                reset to start of dir list;
        }
  
        }}} **/

static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        php_userstream_data_t *us;
        zval *zfilename, *zmode, *zopened, *zoptions, *zretval = NULL, *zfuncname;
        zval **args[4]; 
        int call_result;
        php_stream *stream = NULL;
        zval *zcontext = NULL;
        zend_bool old_in_user_include;

        /* Try to catch bad usage without preventing flexibility */
        if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "infinite recursion prevented");
                return NULL;
        }
        FG(user_stream_current_filename) = filename;
        
        /* if the user stream was registered as local and we are in include context,
                we add allow_url_include restrictions to allow_url_fopen ones */
        /* we need only is_url == 0 here since if is_url == 1 and remote wrappers
                were restricted we wouldn't get here */
        old_in_user_include = PG(in_user_include);
        if(uwrap->wrapper.is_url == 0 && 
                (options & STREAM_OPEN_FOR_INCLUDE) &&
                !PG(allow_url_include)) {
                PG(in_user_include) = 1;
        }

        us = emalloc(sizeof(*us));
        us->wrapper = uwrap;    

        /* create an instance of our class */
        ALLOC_ZVAL(us->object);
        object_init_ex(us->object, uwrap->ce);
        ZVAL_REFCOUNT(us->object) = 1;
        PZVAL_IS_REF(us->object) = 1;
        
        if (uwrap->ce->constructor) {
                zend_fcall_info fci;
                zend_fcall_info_cache fcc;
                zval *retval_ptr;
                
                fci.size = sizeof(fci);
                fci.function_table = &uwrap->ce->function_table;
                fci.function_name = NULL;
                fci.symbol_table = NULL;
                fci.object_pp = &us->object;
                fci.retval_ptr_ptr = &retval_ptr;
                fci.param_count = 0;
                fci.params = NULL;
                fci.no_separation = 1;
                
                fcc.initialized = 1;
                fcc.function_handler = uwrap->ce->constructor;
                fcc.calling_scope = EG(scope);
                fcc.object_pp = &us->object;

                if (zend_call_function(&fci, &fcc TSRMLS_CC) == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not execute %s::%s()", uwrap->ce->name, uwrap->ce->constructor->common.function_name);
                        zval_dtor(us->object);
                        FREE_ZVAL(us->object);
                        efree(us);
                        FG(user_stream_current_filename) = NULL;
                        PG(in_user_include) = old_in_user_include;
                        return NULL;
                } else {
                        if (retval_ptr) {
                                zval_ptr_dtor(&retval_ptr);
                        }
                }
        }

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(us->object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(us->object, "context");
        }
        
        /* call it's stream_open method - set up params first */
        MAKE_STD_ZVAL(zfilename);
        ZVAL_STRING(zfilename, filename, 1);
        args[0] = &zfilename;

        MAKE_STD_ZVAL(zmode);
        ZVAL_STRING(zmode, mode, 1);
        args[1] = &zmode;

        MAKE_STD_ZVAL(zoptions);
        ZVAL_LONG(zoptions, options);
        args[2] = &zoptions;

        MAKE_STD_ZVAL(zopened);
        ZVAL_REFCOUNT(zopened) = 1;
        PZVAL_IS_REF(zopened) = 1;
        ZVAL_NULL(zopened);
        args[3] = &zopened;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_OPEN, 1);
        
        call_result = call_user_function_ex(NULL,
                        &us->object,
                        zfuncname,
                        &zretval,
                        4, args,
                        0, NULL TSRMLS_CC);
        
        if (call_result == SUCCESS && zretval != NULL && zval_is_true(zretval)) {
                /* the stream is now open! */
                stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode);

                /* if the opened path is set, copy it out */
                if (Z_TYPE_P(zopened) == IS_STRING && opened_path) {
                        *opened_path = estrndup(Z_STRVAL_P(zopened), Z_STRLEN_P(zopened));
                }

                /* set wrapper data to be a reference to our object */
                stream->wrapperdata = us->object;
                zval_add_ref(&stream->wrapperdata);
        } else {
                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "\"%s::" USERSTREAM_OPEN "\" call failed",
                        us->wrapper->classname);
        }
        
        /* destroy everything else */
        if (stream == NULL) {
                zval_ptr_dtor(&us->object);
                efree(us);
        }
        if (zretval)
                zval_ptr_dtor(&zretval);
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zopened);
        zval_ptr_dtor(&zoptions);
        zval_ptr_dtor(&zmode);
        zval_ptr_dtor(&zfilename);

        FG(user_stream_current_filename) = NULL;
                
        PG(in_user_include) = old_in_user_include;
        return stream;
}

static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, char *filename, char *mode,
                int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        php_userstream_data_t *us;
        zval *zfilename, *zoptions, *zretval = NULL, *zfuncname, *zcontext;
        zval **args[2]; 
        int call_result;
        php_stream *stream = NULL;

        /* Try to catch bad usage without preventing flexibility */
        if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "infinite recursion prevented");
                return NULL;
        }
        FG(user_stream_current_filename) = filename;
        
        us = emalloc(sizeof(*us));
        us->wrapper = uwrap;    

        /* create an instance of our class */
        ALLOC_ZVAL(us->object);
        object_init_ex(us->object, uwrap->ce);
        ZVAL_REFCOUNT(us->object) = 1;
        PZVAL_IS_REF(us->object) = 1;

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(us->object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(us->object, "context");
        }
        
        /* call it's dir_open method - set up params first */
        MAKE_STD_ZVAL(zfilename);
        ZVAL_STRING(zfilename, filename, 1);
        args[0] = &zfilename;

        MAKE_STD_ZVAL(zoptions);
        ZVAL_LONG(zoptions, options);
        args[1] = &zoptions;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_DIR_OPEN, 1);
        
        call_result = call_user_function_ex(NULL,
                        &us->object,
                        zfuncname,
                        &zretval,
                        2, args,
                        0, NULL TSRMLS_CC);
        
        if (call_result == SUCCESS && zretval != NULL && zval_is_true(zretval)) {
                /* the stream is now open! */
                stream = php_stream_alloc_rel(&php_stream_userspace_dir_ops, us, 0, mode);

                /* set wrapper data to be a reference to our object */
                stream->wrapperdata = us->object;
                zval_add_ref(&stream->wrapperdata);
        } else {
                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed",
                        us->wrapper->classname);
        }
        
        /* destroy everything else */
        if (stream == NULL) {
                zval_ptr_dtor(&us->object);
                efree(us);
        }
        if (zretval)
                zval_ptr_dtor(&zretval);
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zoptions);
        zval_ptr_dtor(&zfilename);

        FG(user_stream_current_filename) = NULL;
                
        return stream;
}


/* {{{ proto bool stream_wrapper_register(string protocol, string classname[, integer flags])
   Registers a custom URL protocol handler class */
PHP_FUNCTION(stream_wrapper_register)
{
        char *protocol, *classname;
        int protocol_len, classname_len;
        struct php_user_stream_wrapper * uwrap;
        int rsrc_id;
        long flags = 0;
        
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &protocol, &protocol_len, &classname, &classname_len, &flags) == FAILURE) {
                RETURN_FALSE;
        }
        
        uwrap = (struct php_user_stream_wrapper *)ecalloc(1, sizeof(*uwrap));
        uwrap->protoname = estrndup(protocol, protocol_len);
        uwrap->classname = estrndup(classname, classname_len);
        uwrap->wrapper.wops = &user_stream_wops;
        uwrap->wrapper.abstract = uwrap;
        uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);

        rsrc_id = ZEND_REGISTER_RESOURCE(NULL, uwrap, le_protocols);

        if (zend_lookup_class(uwrap->classname, classname_len, (zend_class_entry***)&uwrap->ce TSRMLS_CC) == SUCCESS) {
                uwrap->ce = *(zend_class_entry**)uwrap->ce;
                if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper TSRMLS_CC) == SUCCESS) {
                        RETURN_TRUE;
                } else {
                        /* We failed.  But why? */
                        if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol, protocol_len + 1)) {
                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Protocol %s:// is already defined.", protocol);
                        } else {
                                /* Hash doesn't exist so it must have been an invalid protocol scheme */
                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", classname, protocol);
                        }
                }
        } else {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "class '%s' is undefined", classname);
        }

        zend_list_delete(rsrc_id);
        RETURN_FALSE;
}
/* }}} */

/* {{{ proto bool stream_wrapper_unregister(string protocol)
        Unregister a wrapper for the life of the current request. */
PHP_FUNCTION(stream_wrapper_unregister)
{
        char *protocol;
        int protocol_len;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &protocol, &protocol_len) == FAILURE) {
                RETURN_FALSE;
        }

        if (php_unregister_url_stream_wrapper_volatile(protocol TSRMLS_CC) == FAILURE) {
                /* We failed */
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to unregister protocol %s://", protocol);
                RETURN_FALSE;
        }

        RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool stream_wrapper_restore(string protocol)
        Restore the original protocol handler, overriding if necessary */
PHP_FUNCTION(stream_wrapper_restore)
{
        char *protocol;
        int protocol_len;
        php_stream_wrapper **wrapperpp = NULL, *wrapper;
        HashTable *global_wrapper_hash;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &protocol, &protocol_len) == FAILURE) {
                RETURN_FALSE;
        }

        global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global();
        if (php_stream_get_url_stream_wrappers_hash() == global_wrapper_hash) {
                php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%s:// was never changed, nothing to restore", protocol);
                RETURN_TRUE;
        }

        if ((zend_hash_find(global_wrapper_hash, protocol, protocol_len + 1, (void**)&wrapperpp) == FAILURE) || !wrapperpp) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// never existed, nothing to restore", protocol);
                RETURN_FALSE;
        }

        /* next line might delete the pointer that wrapperpp points at, so deref it now */
        wrapper = *wrapperpp;

        /* A failure here could be okay given that the protocol might have been merely unregistered */
        php_unregister_url_stream_wrapper_volatile(protocol TSRMLS_CC);

        if (php_register_url_stream_wrapper_volatile(protocol, wrapper TSRMLS_CC) == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to restore original %s:// wrapper", protocol);
                RETURN_FALSE;
        }       

        RETURN_TRUE;
}
/* }}} */

static size_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        int call_result;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
        zval **args[1];
        zval *zbufptr;
        size_t didwrite = 0;

        assert(us != NULL);

        ZVAL_STRINGL(&func_name, USERSTREAM_WRITE, sizeof(USERSTREAM_WRITE)-1, 0);

        MAKE_STD_ZVAL(zbufptr);
        ZVAL_STRINGL(zbufptr, (char*)buf, count, 1);;
        args[0] = &zbufptr;

        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        1, args,
                        0, NULL TSRMLS_CC);
        zval_ptr_dtor(&zbufptr);

        didwrite = 0;
        if (call_result == SUCCESS && retval != NULL) {
                convert_to_long(retval);
                didwrite = Z_LVAL_P(retval);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
                                us->wrapper->classname);
        }

        /* don't allow strange buffer overruns due to bogus return */
        if (didwrite > count) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_WRITE " wrote %ld bytes more data than requested (%ld written, %ld max)",
                                us->wrapper->classname,
                                (long)(didwrite - count), (long)didwrite, (long)count);
                didwrite = count;
        }
        
        if (retval)
                zval_ptr_dtor(&retval);
        
        return didwrite;
}

static size_t php_userstreamop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        zval **args[1];
        int call_result;
        size_t didread = 0;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
        zval *zcount;

        assert(us != NULL);

        ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1, 0);

        MAKE_STD_ZVAL(zcount);
        ZVAL_LONG(zcount, count);
        args[0] = &zcount;

        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        1, args,
                        0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && retval != NULL) {
                convert_to_string(retval);
                didread = Z_STRLEN_P(retval);
                if (didread > count) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_READ " - read %ld bytes more data than requested (%ld read, %ld max) - excess data will be lost",
                                        us->wrapper->classname, (long)(didread - count), (long)didread, (long)count);
                        didread = count;
                }
                if (didread > 0)
                        memcpy(buf, Z_STRVAL_P(retval), didread);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
                                us->wrapper->classname);
        }
        zval_ptr_dtor(&zcount);

        if (retval) {
                zval_ptr_dtor(&retval);
                retval = NULL;
        }

        /* since the user stream has no way of setting the eof flag directly, we need to ask it if we hit eof */

        ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1, 0);

        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL, 0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && retval != NULL && zval_is_true(retval)) {
                stream->eof = 1;
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING,
                                "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
                                us->wrapper->classname);

                stream->eof = 1;
        }

        if (retval) {
                zval_ptr_dtor(&retval);
                retval = NULL;
        }

        return didread;
}

static int php_userstreamop_close(php_stream *stream, int close_handle TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;

        assert(us != NULL);
        
        ZVAL_STRINGL(&func_name, USERSTREAM_CLOSE, sizeof(USERSTREAM_CLOSE)-1, 0);
        
        call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL, 0, NULL TSRMLS_CC);

        if (retval)
                zval_ptr_dtor(&retval);
        
        zval_ptr_dtor(&us->object);

        efree(us);
        
        return 0;
}

static int php_userstreamop_flush(php_stream *stream TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        int call_result;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;

        assert(us != NULL);

        ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1, 0);
        
        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL, 0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && retval != NULL && zval_is_true(retval))
                call_result = 0;
        else
                call_result = -1;
        
        if (retval)
                zval_ptr_dtor(&retval);
        
        return call_result;
}

static int php_userstreamop_seek(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        int call_result, ret;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
        zval **args[2];
        zval *zoffs, *zwhence;

        assert(us != NULL);

        ZVAL_STRINGL(&func_name, USERSTREAM_SEEK, sizeof(USERSTREAM_SEEK)-1, 0);

        MAKE_STD_ZVAL(zoffs);
        ZVAL_LONG(zoffs, offset);
        args[0] = &zoffs;

        MAKE_STD_ZVAL(zwhence);
        ZVAL_LONG(zwhence, whence);
        args[1] = &zwhence;

        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        2, args,
                        0, NULL TSRMLS_CC);

        zval_ptr_dtor(&zoffs);
        zval_ptr_dtor(&zwhence);

        if (call_result == FAILURE) {
                /* stream_seek is not implemented, so disable seeks for this stream */
                stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
                /* there should be no retval to clean up */
                
                if (retval) 
                        zval_ptr_dtor(&retval);
                
                return -1;
        } else if (call_result == SUCCESS && retval != NULL && zval_is_true(retval)) {
                ret = 0;
        } else {
                ret = -1;
        }

        if (retval) {
                zval_ptr_dtor(&retval);
                retval = NULL;
        }

        if (ret) {
                return ret;
        }

        /* now determine where we are */
        ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1, 0);

        call_result = call_user_function_ex(NULL,
                &us->object,
                &func_name,
                &retval,
                0, NULL, 0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_LONG) {
                *newoffs = Z_LVAL_P(retval);
                ret = 0;
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", us->wrapper->classname);
                ret = -1;
        } else {
                ret = -1;
        }

        if (retval) {
                zval_ptr_dtor(&retval);
        }
        return ret;
}

/* parse the return value from one of the stat functions and store the
 * relevant fields into the statbuf provided */
static int statbuf_from_array(zval *array, php_stream_statbuf *ssb TSRMLS_DC)
{
        zval **elem;

#define STAT_PROP_ENTRY_EX(name, name2)                        \
        if (SUCCESS == zend_hash_find(Z_ARRVAL_P(array), #name, sizeof(#name), (void**)&elem)) {     \
                convert_to_long(*elem);                                                                   \
                ssb->sb.st_##name2 = Z_LVAL_PP(elem);                                                      \
        }

#define STAT_PROP_ENTRY(name) STAT_PROP_ENTRY_EX(name,name)

        STAT_PROP_ENTRY(dev);
        STAT_PROP_ENTRY(ino);
        STAT_PROP_ENTRY(mode);
        STAT_PROP_ENTRY(nlink);
        STAT_PROP_ENTRY(uid);
        STAT_PROP_ENTRY(gid);
#if HAVE_ST_RDEV
        STAT_PROP_ENTRY(rdev);
#endif
        STAT_PROP_ENTRY(size);
#ifdef NETWARE
        STAT_PROP_ENTRY_EX(atime, atime.tv_sec);
        STAT_PROP_ENTRY_EX(mtime, mtime.tv_sec);
        STAT_PROP_ENTRY_EX(ctime, ctime.tv_sec);
#else
        STAT_PROP_ENTRY(atime);
        STAT_PROP_ENTRY(mtime);
        STAT_PROP_ENTRY(ctime);
#endif
#ifdef HAVE_ST_BLKSIZE
        STAT_PROP_ENTRY(blksize);
#endif
#ifdef HAVE_ST_BLOCKS
        STAT_PROP_ENTRY(blocks);
#endif

#undef STAT_PROP_ENTRY  
#undef STAT_PROP_ENTRY_EX       
        return SUCCESS;
}

static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        int call_result;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
        int ret = -1;

        ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1, 0);

        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL, 0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_ARRAY) {
                if (SUCCESS == statbuf_from_array(retval, ssb TSRMLS_CC))
                        ret = 0;
        } else {
                if (call_result == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!",
                                        us->wrapper->classname);
                }
        }

        if (retval) 
                zval_ptr_dtor(&retval);
        
        return ret;
}


static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) {
        zval func_name;
        zval *retval = NULL;
        int call_result;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
        int ret = -1;
        zval *zvalue = NULL;
        zval **args[1];

        switch (option) {
        case PHP_STREAM_OPTION_CHECK_LIVENESS:
                ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1, 0);
                call_result = call_user_function_ex(NULL, &us->object, &func_name, &retval, 0, NULL, 0, NULL TSRMLS_CC);
                if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_BOOL) {
                        ret = zval_is_true(retval) ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
                } else {
                        ret = PHP_STREAM_OPTION_RETURN_ERR;
                        php_error_docref(NULL TSRMLS_CC, E_WARNING,
                                        "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
                                        us->wrapper->classname);
                }
                break;

        case PHP_STREAM_OPTION_LOCKING:
                MAKE_STD_ZVAL(zvalue);
                ZVAL_LONG(zvalue, value);
                args[0] = &zvalue;
                
                /* TODO wouldblock */
                ZVAL_STRINGL(&func_name, USERSTREAM_LOCK, sizeof(USERSTREAM_LOCK)-1, 0);
                
                call_result = call_user_function_ex(NULL,
                                                                                        &us->object,
                                                                                        &func_name,
                                                                                        &retval,
                                                                                        1, args, 0, NULL TSRMLS_CC);
                
                if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_BOOL) {
                        ret = !Z_LVAL_P(retval);
                } else if (call_result == FAILURE) {
                        if (value == 0) { 
                                /* lock support test (TODO: more check) */
                                ret = 0;
                        } else {
                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_LOCK " is not implemented!", 
                                                                 us->wrapper->classname);
                        }
                }

                break;
        }

        /* clean up */
        if (retval) {
                zval_ptr_dtor(&retval);
        }
  

        if (zvalue) {
                zval_ptr_dtor(&zvalue);
        }

        return ret;
}


static int user_wrapper_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        zval *zfilename, *zfuncname, *zretval, *zcontext;
        zval **args[1];
        int call_result;
        zval *object;
        int ret = 0;

        /* create an instance of our class */
        ALLOC_ZVAL(object);
        object_init_ex(object, uwrap->ce);
        ZVAL_REFCOUNT(object) = 1;
        PZVAL_IS_REF(object) = 1;

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(object, "context");
        }

        /* call the unlink method */
        MAKE_STD_ZVAL(zfilename);
        ZVAL_STRING(zfilename, url, 1);
        args[0] = &zfilename;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_UNLINK, 1);
        
        call_result = call_user_function_ex(NULL,
                        &object,
                        zfuncname,
                        &zretval,
                        1, args,
                        0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
                ret = Z_LVAL_P(zretval);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", uwrap->classname);
        }

        /* clean up */
        zval_ptr_dtor(&object);
        if (zretval)
                zval_ptr_dtor(&zretval);
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zfilename);

        return ret;
}

static int user_wrapper_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        zval *zold_name, *znew_name, *zfuncname, *zretval, *zcontext;
        zval **args[2];
        int call_result;
        zval *object;
        int ret = 0;

        /* create an instance of our class */
        ALLOC_ZVAL(object);
        object_init_ex(object, uwrap->ce);
        ZVAL_REFCOUNT(object) = 1;
        PZVAL_IS_REF(object) = 1;

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(object, "context");
        }

        /* call the rename method */
        MAKE_STD_ZVAL(zold_name);
        ZVAL_STRING(zold_name, url_from, 1);
        args[0] = &zold_name;

        MAKE_STD_ZVAL(znew_name);
        ZVAL_STRING(znew_name, url_to, 1);
        args[1] = &znew_name;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_RENAME, 1);
        
        call_result = call_user_function_ex(NULL,
                        &object,
                        zfuncname,
                        &zretval,
                        2, args,
                        0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
                ret = Z_LVAL_P(zretval);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", uwrap->classname);
        }

        /* clean up */
        zval_ptr_dtor(&object);
        if (zretval)
                zval_ptr_dtor(&zretval);
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zold_name);
        zval_ptr_dtor(&znew_name);

        return ret;
}

static int user_wrapper_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        zval *zfilename, *zmode, *zoptions, *zfuncname, *zretval, *zcontext;
        zval **args[3];
        int call_result;
        zval *object;
        int ret = 0;

        /* create an instance of our class */
        ALLOC_ZVAL(object);
        object_init_ex(object, uwrap->ce);
        ZVAL_REFCOUNT(object) = 1;
        PZVAL_IS_REF(object) = 1;

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(object, "context");
        }

        /* call the unlink method */
        MAKE_STD_ZVAL(zfilename);
        ZVAL_STRING(zfilename, url, 1);
        args[0] = &zfilename;

        MAKE_STD_ZVAL(zmode);
        ZVAL_LONG(zmode, mode);
        args[1] = &zmode;

        MAKE_STD_ZVAL(zoptions);
        ZVAL_LONG(zoptions, options);
        args[2] = &zoptions;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_MKDIR, 1);
        
        call_result = call_user_function_ex(NULL,
                        &object,
                        zfuncname,
                        &zretval,
                        3, args,
                        0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
                ret = Z_LVAL_P(zretval);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", uwrap->classname);
        }

        /* clean up */
        zval_ptr_dtor(&object);
        if (zretval) {
                zval_ptr_dtor(&zretval);
        }
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zfilename);
        zval_ptr_dtor(&zmode);
        zval_ptr_dtor(&zoptions);

        return ret;
}

static int user_wrapper_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        zval *zfilename, *zoptions, *zfuncname, *zretval, *zcontext;
        zval **args[3];
        int call_result;
        zval *object;
        int ret = 0;

        /* create an instance of our class */
        ALLOC_ZVAL(object);
        object_init_ex(object, uwrap->ce);
        ZVAL_REFCOUNT(object) = 1;
        PZVAL_IS_REF(object) = 1;

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(object, "context");
        }

        /* call the unlink method */
        MAKE_STD_ZVAL(zfilename);
        ZVAL_STRING(zfilename, url, 1);
        args[0] = &zfilename;

        MAKE_STD_ZVAL(zoptions);
        ZVAL_LONG(zoptions, options);
        args[1] = &zoptions;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_RMDIR, 1);
        
        call_result = call_user_function_ex(NULL,
                        &object,
                        zfuncname,
                        &zretval,
                        2, args,
                        0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && zretval && Z_TYPE_P(zretval) == IS_BOOL) {
                ret = Z_LVAL_P(zretval);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", uwrap->classname);
        }

        /* clean up */
        zval_ptr_dtor(&object);
        if (zretval) {
                zval_ptr_dtor(&zretval);
        }
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zfilename);
        zval_ptr_dtor(&zoptions);

        return ret;
}

static int user_wrapper_stat_url(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
{
        struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
        zval *zfilename, *zfuncname, *zretval, *zflags, *zcontext;
        zval **args[2]; 
        int call_result;
        zval *object;
        int ret = -1;

        /* create an instance of our class */
        ALLOC_ZVAL(object);
        object_init_ex(object, uwrap->ce);
        ZVAL_REFCOUNT(object) = 1;
        PZVAL_IS_REF(object) = 1;

        if (context) {
                MAKE_STD_ZVAL(zcontext);
                php_stream_context_to_zval(context, zcontext);
                add_property_zval(object, "context", zcontext);
                /* The object property should be the only reference,
                   'get rid' of our local reference. */
                zval_ptr_dtor(&zcontext);
        } else {
                add_property_null(object, "context");
        }

        /* call the stat_url method */
        
        /* call it's stream_open method - set up params first */
        MAKE_STD_ZVAL(zfilename);
        ZVAL_STRING(zfilename, url, 1);
        args[0] = &zfilename;

        MAKE_STD_ZVAL(zflags);
        ZVAL_LONG(zflags, flags);
        args[1] = &zflags;

        MAKE_STD_ZVAL(zfuncname);
        ZVAL_STRING(zfuncname, USERSTREAM_STATURL, 1);
        
        call_result = call_user_function_ex(NULL,
                        &object,
                        zfuncname,
                        &zretval,
                        2, args,
                        0, NULL TSRMLS_CC);
        
        if (call_result == SUCCESS && zretval != NULL && Z_TYPE_P(zretval) == IS_ARRAY) {
                /* We got the info we needed */
                if (SUCCESS == statbuf_from_array(zretval, ssb TSRMLS_CC))
                        ret = 0;
        } else {
                if (call_result == FAILURE) {
                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!",
                                        uwrap->classname);
                }
        }
        
        /* clean up */
        zval_ptr_dtor(&object);
        if (zretval)
                zval_ptr_dtor(&zretval);
        
        zval_ptr_dtor(&zfuncname);
        zval_ptr_dtor(&zfilename);
        zval_ptr_dtor(&zflags);
                
        return ret;

}

static size_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        int call_result;
        size_t didread = 0;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
        php_stream_dirent *ent = (php_stream_dirent*)buf;

        /* avoid problems if someone mis-uses the stream */
        if (count != sizeof(php_stream_dirent))
                return 0;

        ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1, 0);

        call_result = call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL,
                        0, NULL TSRMLS_CC);

        if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) != IS_BOOL) {
                convert_to_string(retval);
                PHP_STRLCPY(ent->d_name, Z_STRVAL_P(retval), sizeof(ent->d_name), Z_STRLEN_P(retval));

                didread = sizeof(php_stream_dirent);
        } else if (call_result == FAILURE) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!",
                                us->wrapper->classname);
        }

        if (retval)
                zval_ptr_dtor(&retval);

        return didread;
}

static int php_userstreamop_closedir(php_stream *stream, int close_handle TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;

        assert(us != NULL);
        
        ZVAL_STRINGL(&func_name, USERSTREAM_DIR_CLOSE, sizeof(USERSTREAM_DIR_CLOSE)-1, 0);
        
        call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL, 0, NULL TSRMLS_CC);

        if (retval)
                zval_ptr_dtor(&retval);
        
        zval_ptr_dtor(&us->object);

        efree(us);
        
        return 0;
}

static int php_userstreamop_rewinddir(php_stream *stream, off_t offset, int whence, off_t *newoffs TSRMLS_DC)
{
        zval func_name;
        zval *retval = NULL;
        php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;

        ZVAL_STRINGL(&func_name, USERSTREAM_DIR_REWIND, sizeof(USERSTREAM_DIR_REWIND)-1, 0);
        
        call_user_function_ex(NULL,
                        &us->object,
                        &func_name,
                        &retval,
                        0, NULL, 0, NULL TSRMLS_CC);

        if (retval)
                zval_ptr_dtor(&retval);
        
        return 0;

}

php_stream_ops php_stream_userspace_ops = {
        php_userstreamop_write, php_userstreamop_read,
        php_userstreamop_close, php_userstreamop_flush,
        "user-space",
        php_userstreamop_seek,
        NULL, /* cast */
        php_userstreamop_stat, 
        php_userstreamop_set_option,
};

php_stream_ops php_stream_userspace_dir_ops = {
        NULL, /* write */
        php_userstreamop_readdir,
        php_userstreamop_closedir,
        NULL, /* flush */
        "user-space-dir",
        php_userstreamop_rewinddir,
        NULL, /* cast */
        NULL, /* stat */
        NULL  /* set_option */
};



/* [<][>][^][v][top][bottom][index][help] */