1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2018 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   | Authors: Wez Furlong <wez@thebrainroom.com>                          |
16   |          Sara Golemon <pollita@php.net>                              |
17   +----------------------------------------------------------------------+
18*/
19
20#include "php.h"
21#include "php_globals.h"
22#include "ext/standard/file.h"
23#include "ext/standard/flock_compat.h"
24#ifdef HAVE_SYS_FILE_H
25#include <sys/file.h>
26#endif
27#include <stddef.h>
28
29#if HAVE_UTIME
30# ifdef PHP_WIN32
31#  include <sys/utime.h>
32# else
33#  include <utime.h>
34# endif
35#endif
36
37static int le_protocols;
38
39struct php_user_stream_wrapper {
40	char * protoname;
41	char * classname;
42	zend_class_entry *ce;
43	php_stream_wrapper wrapper;
44};
45
46static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
47static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context);
48static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
49static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context);
50static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context);
51static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
52static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context);
53static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
54		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
55
56static const php_stream_wrapper_ops user_stream_wops = {
57	user_wrapper_opener,
58	NULL, /* close - the streams themselves know how */
59	NULL, /* stat - the streams themselves know how */
60	user_wrapper_stat_url,
61	user_wrapper_opendir,
62	"user-space",
63	user_wrapper_unlink,
64	user_wrapper_rename,
65	user_wrapper_mkdir,
66	user_wrapper_rmdir,
67	user_wrapper_metadata
68};
69
70
71static void stream_wrapper_dtor(zend_resource *rsrc)
72{
73	struct php_user_stream_wrapper * uwrap = (struct php_user_stream_wrapper*)rsrc->ptr;
74
75	efree(uwrap->protoname);
76	efree(uwrap->classname);
77	efree(uwrap);
78}
79
80
81PHP_MINIT_FUNCTION(user_streams)
82{
83	le_protocols = zend_register_list_destructors_ex(stream_wrapper_dtor, NULL, "stream factory", 0);
84	if (le_protocols == FAILURE)
85		return FAILURE;
86
87	REGISTER_LONG_CONSTANT("STREAM_USE_PATH", 			USE_PATH, CONST_CS|CONST_PERSISTENT);
88	REGISTER_LONG_CONSTANT("STREAM_IGNORE_URL", 		IGNORE_URL, CONST_CS|CONST_PERSISTENT);
89	REGISTER_LONG_CONSTANT("STREAM_REPORT_ERRORS", 		REPORT_ERRORS, CONST_CS|CONST_PERSISTENT);
90	REGISTER_LONG_CONSTANT("STREAM_MUST_SEEK", 			STREAM_MUST_SEEK, CONST_CS|CONST_PERSISTENT);
91
92	REGISTER_LONG_CONSTANT("STREAM_URL_STAT_LINK", 		PHP_STREAM_URL_STAT_LINK,		CONST_CS|CONST_PERSISTENT);
93	REGISTER_LONG_CONSTANT("STREAM_URL_STAT_QUIET", 	PHP_STREAM_URL_STAT_QUIET,		CONST_CS|CONST_PERSISTENT);
94	REGISTER_LONG_CONSTANT("STREAM_MKDIR_RECURSIVE",	PHP_STREAM_MKDIR_RECURSIVE,		CONST_CS|CONST_PERSISTENT);
95
96	REGISTER_LONG_CONSTANT("STREAM_IS_URL",	PHP_STREAM_IS_URL,		CONST_CS|CONST_PERSISTENT);
97
98	REGISTER_LONG_CONSTANT("STREAM_OPTION_BLOCKING",	PHP_STREAM_OPTION_BLOCKING,		CONST_CS|CONST_PERSISTENT);
99	REGISTER_LONG_CONSTANT("STREAM_OPTION_READ_TIMEOUT",	PHP_STREAM_OPTION_READ_TIMEOUT,		CONST_CS|CONST_PERSISTENT);
100	REGISTER_LONG_CONSTANT("STREAM_OPTION_READ_BUFFER",	PHP_STREAM_OPTION_READ_BUFFER,		CONST_CS|CONST_PERSISTENT);
101	REGISTER_LONG_CONSTANT("STREAM_OPTION_WRITE_BUFFER",	PHP_STREAM_OPTION_WRITE_BUFFER,		CONST_CS|CONST_PERSISTENT);
102
103	REGISTER_LONG_CONSTANT("STREAM_BUFFER_NONE",		PHP_STREAM_BUFFER_NONE,			CONST_CS|CONST_PERSISTENT);
104	REGISTER_LONG_CONSTANT("STREAM_BUFFER_LINE",		PHP_STREAM_BUFFER_LINE,			CONST_CS|CONST_PERSISTENT);
105	REGISTER_LONG_CONSTANT("STREAM_BUFFER_FULL",		PHP_STREAM_BUFFER_FULL,			CONST_CS|CONST_PERSISTENT);
106
107	REGISTER_LONG_CONSTANT("STREAM_CAST_AS_STREAM",		PHP_STREAM_AS_STDIO,			CONST_CS|CONST_PERSISTENT);
108	REGISTER_LONG_CONSTANT("STREAM_CAST_FOR_SELECT",	PHP_STREAM_AS_FD_FOR_SELECT,		CONST_CS|CONST_PERSISTENT);
109
110	REGISTER_LONG_CONSTANT("STREAM_META_TOUCH",			PHP_STREAM_META_TOUCH,			CONST_CS|CONST_PERSISTENT);
111	REGISTER_LONG_CONSTANT("STREAM_META_OWNER",			PHP_STREAM_META_OWNER,			CONST_CS|CONST_PERSISTENT);
112	REGISTER_LONG_CONSTANT("STREAM_META_OWNER_NAME",	PHP_STREAM_META_OWNER_NAME,		CONST_CS|CONST_PERSISTENT);
113	REGISTER_LONG_CONSTANT("STREAM_META_GROUP",			PHP_STREAM_META_GROUP,			CONST_CS|CONST_PERSISTENT);
114	REGISTER_LONG_CONSTANT("STREAM_META_GROUP_NAME",	PHP_STREAM_META_GROUP_NAME,		CONST_CS|CONST_PERSISTENT);
115	REGISTER_LONG_CONSTANT("STREAM_META_ACCESS",		PHP_STREAM_META_ACCESS,			CONST_CS|CONST_PERSISTENT);
116	return SUCCESS;
117}
118
119struct _php_userstream_data {
120	struct php_user_stream_wrapper * wrapper;
121	zval object;
122};
123typedef struct _php_userstream_data php_userstream_data_t;
124
125/* names of methods */
126#define USERSTREAM_OPEN		"stream_open"
127#define USERSTREAM_CLOSE	"stream_close"
128#define USERSTREAM_READ		"stream_read"
129#define USERSTREAM_WRITE	"stream_write"
130#define USERSTREAM_FLUSH	"stream_flush"
131#define USERSTREAM_SEEK		"stream_seek"
132#define USERSTREAM_TELL		"stream_tell"
133#define USERSTREAM_EOF		"stream_eof"
134#define USERSTREAM_STAT		"stream_stat"
135#define USERSTREAM_STATURL	"url_stat"
136#define USERSTREAM_UNLINK	"unlink"
137#define USERSTREAM_RENAME	"rename"
138#define USERSTREAM_MKDIR	"mkdir"
139#define USERSTREAM_RMDIR	"rmdir"
140#define USERSTREAM_DIR_OPEN		"dir_opendir"
141#define USERSTREAM_DIR_READ		"dir_readdir"
142#define USERSTREAM_DIR_REWIND	"dir_rewinddir"
143#define USERSTREAM_DIR_CLOSE	"dir_closedir"
144#define USERSTREAM_LOCK     "stream_lock"
145#define USERSTREAM_CAST		"stream_cast"
146#define USERSTREAM_SET_OPTION	"stream_set_option"
147#define USERSTREAM_TRUNCATE	"stream_truncate"
148#define USERSTREAM_METADATA	"stream_metadata"
149
150/* {{{ class should have methods like these:
151
152	function stream_open($path, $mode, $options, &$opened_path)
153	{
154	  	return true/false;
155	}
156
157	function stream_read($count)
158	{
159	   	return false on error;
160		else return string;
161	}
162
163	function stream_write($data)
164	{
165	   	return false on error;
166		else return count written;
167	}
168
169	function stream_close()
170	{
171	}
172
173	function stream_flush()
174	{
175		return true/false;
176	}
177
178	function stream_seek($offset, $whence)
179	{
180		return true/false;
181	}
182
183	function stream_tell()
184	{
185		return (int)$position;
186	}
187
188	function stream_eof()
189	{
190		return true/false;
191	}
192
193	function stream_stat()
194	{
195		return array( just like that returned by fstat() );
196	}
197
198	function stream_cast($castas)
199	{
200		if ($castas == STREAM_CAST_FOR_SELECT) {
201			return $this->underlying_stream;
202		}
203		return false;
204	}
205
206	function stream_set_option($option, $arg1, $arg2)
207	{
208		switch($option) {
209		case STREAM_OPTION_BLOCKING:
210			$blocking = $arg1;
211			...
212		case STREAM_OPTION_READ_TIMEOUT:
213			$sec = $arg1;
214			$usec = $arg2;
215			...
216		case STREAM_OPTION_WRITE_BUFFER:
217			$mode = $arg1;
218			$size = $arg2;
219			...
220		default:
221			return false;
222		}
223	}
224
225	function url_stat(string $url, int $flags)
226	{
227		return array( just like that returned by stat() );
228	}
229
230	function unlink(string $url)
231	{
232		return true / false;
233	}
234
235	function rename(string $from, string $to)
236	{
237		return true / false;
238	}
239
240	function mkdir($dir, $mode, $options)
241	{
242		return true / false;
243	}
244
245	function rmdir($dir, $options)
246	{
247		return true / false;
248	}
249
250	function dir_opendir(string $url, int $options)
251	{
252		return true / false;
253	}
254
255	function dir_readdir()
256	{
257		return string next filename in dir ;
258	}
259
260	function dir_closedir()
261	{
262		release dir related resources;
263	}
264
265	function dir_rewinddir()
266	{
267		reset to start of dir list;
268	}
269
270	function stream_lock($operation)
271	{
272		return true / false;
273	}
274
275 	function stream_truncate($new_size)
276	{
277		return true / false;
278	}
279
280	}}} **/
281
282static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object)
283{
284	if (uwrap->ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) {
285		ZVAL_UNDEF(object);
286		return;
287	}
288
289	/* create an instance of our class */
290	if (object_init_ex(object, uwrap->ce) == FAILURE) {
291		ZVAL_UNDEF(object);
292		return;
293	}
294
295	if (context) {
296		add_property_resource(object, "context", context->res);
297		GC_ADDREF(context->res);
298	} else {
299		add_property_null(object, "context");
300	}
301
302	if (uwrap->ce->constructor) {
303		zend_fcall_info fci;
304		zend_fcall_info_cache fcc;
305		zval retval;
306
307		fci.size = sizeof(fci);
308		ZVAL_UNDEF(&fci.function_name);
309		fci.object = Z_OBJ_P(object);
310		fci.retval = &retval;
311		fci.param_count = 0;
312		fci.params = NULL;
313		fci.no_separation = 1;
314
315		fcc.function_handler = uwrap->ce->constructor;
316		fcc.called_scope = Z_OBJCE_P(object);
317		fcc.object = Z_OBJ_P(object);
318
319		if (zend_call_function(&fci, &fcc) == FAILURE) {
320			php_error_docref(NULL, E_WARNING, "Could not execute %s::%s()", ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(uwrap->ce->constructor->common.function_name));
321			zval_ptr_dtor(object);
322			ZVAL_UNDEF(object);
323		} else {
324			zval_ptr_dtor(&retval);
325		}
326	}
327}
328
329static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode,
330									   int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
331{
332	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
333	php_userstream_data_t *us;
334	zval zretval, zfuncname;
335	zval args[4];
336	int call_result;
337	php_stream *stream = NULL;
338	zend_bool old_in_user_include;
339
340	/* Try to catch bad usage without preventing flexibility */
341	if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
342		php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
343		return NULL;
344	}
345	FG(user_stream_current_filename) = filename;
346
347	/* if the user stream was registered as local and we are in include context,
348		we add allow_url_include restrictions to allow_url_fopen ones */
349	/* we need only is_url == 0 here since if is_url == 1 and remote wrappers
350		were restricted we wouldn't get here */
351	old_in_user_include = PG(in_user_include);
352	if(uwrap->wrapper.is_url == 0 &&
353		(options & STREAM_OPEN_FOR_INCLUDE) &&
354		!PG(allow_url_include)) {
355		PG(in_user_include) = 1;
356	}
357
358	us = emalloc(sizeof(*us));
359	us->wrapper = uwrap;
360
361	user_stream_create_object(uwrap, context, &us->object);
362	if (Z_TYPE(us->object) == IS_UNDEF) {
363		FG(user_stream_current_filename) = NULL;
364		PG(in_user_include) = old_in_user_include;
365		efree(us);
366		return NULL;
367	}
368
369	/* call it's stream_open method - set up params first */
370	ZVAL_STRING(&args[0], filename);
371	ZVAL_STRING(&args[1], mode);
372	ZVAL_LONG(&args[2], options);
373	ZVAL_NEW_REF(&args[3], &EG(uninitialized_zval));
374
375	ZVAL_STRING(&zfuncname, USERSTREAM_OPEN);
376
377	zend_try {
378		call_result = call_user_function_ex(NULL,
379				Z_ISUNDEF(us->object)? NULL : &us->object,
380				&zfuncname,
381				&zretval,
382				4, args,
383				0, NULL	);
384	} zend_catch {
385		FG(user_stream_current_filename) = NULL;
386		zend_bailout();
387	} zend_end_try();
388
389	if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) {
390		/* the stream is now open! */
391		stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode);
392
393		/* if the opened path is set, copy it out */
394		if (Z_ISREF(args[3]) && Z_TYPE_P(Z_REFVAL(args[3])) == IS_STRING && opened_path) {
395			*opened_path = zend_string_copy(Z_STR_P(Z_REFVAL(args[3])));
396		}
397
398		/* set wrapper data to be a reference to our object */
399		ZVAL_COPY(&stream->wrapperdata, &us->object);
400	} else {
401		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" call failed",
402			us->wrapper->classname);
403	}
404
405	/* destroy everything else */
406	if (stream == NULL) {
407		zval_ptr_dtor(&us->object);
408		ZVAL_UNDEF(&us->object);
409		efree(us);
410	}
411	zval_ptr_dtor(&zretval);
412	zval_ptr_dtor(&zfuncname);
413	zval_ptr_dtor(&args[3]);
414	zval_ptr_dtor(&args[2]);
415	zval_ptr_dtor(&args[1]);
416	zval_ptr_dtor(&args[0]);
417
418	FG(user_stream_current_filename) = NULL;
419
420	PG(in_user_include) = old_in_user_include;
421	return stream;
422}
423
424static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
425		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
426{
427	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
428	php_userstream_data_t *us;
429	zval zretval, zfuncname;
430	zval args[2];
431	int call_result;
432	php_stream *stream = NULL;
433
434	/* Try to catch bad usage without preventing flexibility */
435	if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
436		php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
437		return NULL;
438	}
439	FG(user_stream_current_filename) = filename;
440
441	us = emalloc(sizeof(*us));
442	us->wrapper = uwrap;
443
444	user_stream_create_object(uwrap, context, &us->object);
445	if (Z_TYPE(us->object) == IS_UNDEF) {
446		FG(user_stream_current_filename) = NULL;
447		efree(us);
448		return NULL;
449	}
450
451	/* call it's dir_open method - set up params first */
452	ZVAL_STRING(&args[0], filename);
453	ZVAL_LONG(&args[1], options);
454
455	ZVAL_STRING(&zfuncname, USERSTREAM_DIR_OPEN);
456
457	call_result = call_user_function_ex(NULL,
458			Z_ISUNDEF(us->object)? NULL : &us->object,
459			&zfuncname,
460			&zretval,
461			2, args,
462			0, NULL	);
463
464	if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) {
465		/* the stream is now open! */
466		stream = php_stream_alloc_rel(&php_stream_userspace_dir_ops, us, 0, mode);
467
468		/* set wrapper data to be a reference to our object */
469		ZVAL_COPY(&stream->wrapperdata, &us->object);
470	} else {
471		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed",
472			us->wrapper->classname);
473	}
474
475	/* destroy everything else */
476	if (stream == NULL) {
477		zval_ptr_dtor(&us->object);
478		ZVAL_UNDEF(&us->object);
479		efree(us);
480	}
481	zval_ptr_dtor(&zretval);
482
483	zval_ptr_dtor(&zfuncname);
484	zval_ptr_dtor(&args[1]);
485	zval_ptr_dtor(&args[0]);
486
487	FG(user_stream_current_filename) = NULL;
488
489	return stream;
490}
491
492
493/* {{{ proto bool stream_wrapper_register(string protocol, string classname[, int flags])
494   Registers a custom URL protocol handler class */
495PHP_FUNCTION(stream_wrapper_register)
496{
497	zend_string *protocol, *classname;
498	struct php_user_stream_wrapper * uwrap;
499	zend_resource *rsrc;
500	zend_long flags = 0;
501
502	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &protocol, &classname, &flags) == FAILURE) {
503		RETURN_FALSE;
504	}
505
506	uwrap = (struct php_user_stream_wrapper *)ecalloc(1, sizeof(*uwrap));
507	uwrap->protoname = estrndup(ZSTR_VAL(protocol), ZSTR_LEN(protocol));
508	uwrap->classname = estrndup(ZSTR_VAL(classname), ZSTR_LEN(classname));
509	uwrap->wrapper.wops = &user_stream_wops;
510	uwrap->wrapper.abstract = uwrap;
511	uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);
512
513	rsrc = zend_register_resource(uwrap, le_protocols);
514
515	if ((uwrap->ce = zend_lookup_class(classname)) != NULL) {
516		if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper) == SUCCESS) {
517			RETURN_TRUE;
518		} else {
519			/* We failed.  But why? */
520			if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol)) {
521				php_error_docref(NULL, E_WARNING, "Protocol %s:// is already defined.", ZSTR_VAL(protocol));
522			} else {
523				/* Hash doesn't exist so it must have been an invalid protocol scheme */
524				php_error_docref(NULL, E_WARNING, "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", ZSTR_VAL(classname), ZSTR_VAL(protocol));
525			}
526		}
527	} else {
528		php_error_docref(NULL, E_WARNING, "class '%s' is undefined", ZSTR_VAL(classname));
529	}
530
531	zend_list_delete(rsrc);
532	RETURN_FALSE;
533}
534/* }}} */
535
536/* {{{ proto bool stream_wrapper_unregister(string protocol)
537	Unregister a wrapper for the life of the current request. */
538PHP_FUNCTION(stream_wrapper_unregister)
539{
540	zend_string *protocol;
541
542	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &protocol) == FAILURE) {
543		RETURN_FALSE;
544	}
545
546	if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) {
547		/* We failed */
548		php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol));
549		RETURN_FALSE;
550	}
551
552	RETURN_TRUE;
553}
554/* }}} */
555
556/* {{{ proto bool stream_wrapper_restore(string protocol)
557	Restore the original protocol handler, overriding if necessary */
558PHP_FUNCTION(stream_wrapper_restore)
559{
560	zend_string *protocol;
561	php_stream_wrapper *wrapper;
562	HashTable *global_wrapper_hash, *wrapper_hash;
563
564	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &protocol) == FAILURE) {
565		RETURN_FALSE;
566	}
567
568	global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global();
569	if ((wrapper = zend_hash_find_ptr(global_wrapper_hash, protocol)) == NULL) {
570		php_error_docref(NULL, E_WARNING, "%s:// never existed, nothing to restore", ZSTR_VAL(protocol));
571		RETURN_FALSE;
572	}
573
574	wrapper_hash = php_stream_get_url_stream_wrappers_hash();
575	if (wrapper_hash == global_wrapper_hash || zend_hash_find_ptr(wrapper_hash, protocol) == wrapper) {
576		php_error_docref(NULL, E_NOTICE, "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol));
577		RETURN_TRUE;
578	}
579
580	/* A failure here could be okay given that the protocol might have been merely unregistered */
581	php_unregister_url_stream_wrapper_volatile(protocol);
582
583	if (php_register_url_stream_wrapper_volatile(protocol, wrapper) == FAILURE) {
584		php_error_docref(NULL, E_WARNING, "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol));
585		RETURN_FALSE;
586	}
587
588	RETURN_TRUE;
589}
590/* }}} */
591
592static size_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count)
593{
594	zval func_name;
595	zval retval;
596	int call_result;
597	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
598	zval args[1];
599	size_t didwrite = 0;
600
601	assert(us != NULL);
602
603	ZVAL_STRINGL(&func_name, USERSTREAM_WRITE, sizeof(USERSTREAM_WRITE)-1);
604
605	ZVAL_STRINGL(&args[0], (char*)buf, count);
606
607	call_result = call_user_function_ex(NULL,
608			Z_ISUNDEF(us->object)? NULL : &us->object,
609			&func_name,
610			&retval,
611			1, args,
612			0, NULL);
613	zval_ptr_dtor(&args[0]);
614	zval_ptr_dtor(&func_name);
615
616	didwrite = 0;
617
618	if (EG(exception)) {
619		return 0;
620	}
621
622	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
623		convert_to_long(&retval);
624		didwrite = Z_LVAL(retval);
625	} else if (call_result == FAILURE) {
626		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
627				us->wrapper->classname);
628	}
629
630	/* don't allow strange buffer overruns due to bogus return */
631	if (didwrite > count) {
632		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)",
633				us->wrapper->classname,
634				(zend_long)(didwrite - count), (zend_long)didwrite, (zend_long)count);
635		didwrite = count;
636	}
637
638	zval_ptr_dtor(&retval);
639
640	return didwrite;
641}
642
643static size_t php_userstreamop_read(php_stream *stream, char *buf, size_t count)
644{
645	zval func_name;
646	zval retval;
647	zval args[1];
648	int call_result;
649	size_t didread = 0;
650	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
651
652	assert(us != NULL);
653
654	ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1);
655
656	ZVAL_LONG(&args[0], count);
657
658	call_result = call_user_function_ex(NULL,
659			Z_ISUNDEF(us->object)? NULL : &us->object,
660			&func_name,
661			&retval,
662			1, args,
663			0, NULL);
664
665	zval_ptr_dtor(&args[0]);
666	zval_ptr_dtor(&func_name);
667
668	if (EG(exception)) {
669		return -1;
670	}
671
672	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
673		convert_to_string(&retval);
674		didread = Z_STRLEN(retval);
675		if (didread > count) {
676			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " read, " ZEND_LONG_FMT " max) - excess data will be lost",
677					us->wrapper->classname, (zend_long)(didread - count), (zend_long)didread, (zend_long)count);
678			didread = count;
679		}
680		if (didread > 0)
681			memcpy(buf, Z_STRVAL(retval), didread);
682	} else if (call_result == FAILURE) {
683		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
684				us->wrapper->classname);
685	}
686
687	zval_ptr_dtor(&retval);
688	ZVAL_UNDEF(&retval);
689
690	/* since the user stream has no way of setting the eof flag directly, we need to ask it if we hit eof */
691
692	ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1);
693
694	call_result = call_user_function(NULL,
695			Z_ISUNDEF(us->object)? NULL : &us->object,
696			&func_name,
697			&retval,
698			0, NULL);
699
700	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) {
701		stream->eof = 1;
702	} else if (call_result == FAILURE) {
703		php_error_docref(NULL, E_WARNING,
704				"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
705				us->wrapper->classname);
706
707		stream->eof = 1;
708	}
709
710	zval_ptr_dtor(&retval);
711	zval_ptr_dtor(&func_name);
712
713	return didread;
714}
715
716static int php_userstreamop_close(php_stream *stream, int close_handle)
717{
718	zval func_name;
719	zval retval;
720	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
721
722	assert(us != NULL);
723
724	ZVAL_STRINGL(&func_name, USERSTREAM_CLOSE, sizeof(USERSTREAM_CLOSE)-1);
725
726	call_user_function(NULL,
727			Z_ISUNDEF(us->object)? NULL : &us->object,
728			&func_name,
729			&retval,
730			0, NULL);
731
732	zval_ptr_dtor(&retval);
733	zval_ptr_dtor(&func_name);
734
735	zval_ptr_dtor(&us->object);
736	ZVAL_UNDEF(&us->object);
737
738	efree(us);
739
740	return 0;
741}
742
743static int php_userstreamop_flush(php_stream *stream)
744{
745	zval func_name;
746	zval retval;
747	int call_result;
748	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
749
750	assert(us != NULL);
751
752	ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1);
753
754	call_result = call_user_function(NULL,
755			Z_ISUNDEF(us->object)? NULL : &us->object,
756			&func_name,
757			&retval,
758			0, NULL);
759
760	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval))
761		call_result = 0;
762	else
763		call_result = -1;
764
765	zval_ptr_dtor(&retval);
766	zval_ptr_dtor(&func_name);
767
768	return call_result;
769}
770
771static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
772{
773	zval func_name;
774	zval retval;
775	int call_result, ret;
776	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
777	zval args[2];
778
779	assert(us != NULL);
780
781	ZVAL_STRINGL(&func_name, USERSTREAM_SEEK, sizeof(USERSTREAM_SEEK)-1);
782
783	ZVAL_LONG(&args[0], offset);
784	ZVAL_LONG(&args[1], whence);
785
786	call_result = call_user_function_ex(NULL,
787			Z_ISUNDEF(us->object)? NULL : &us->object,
788			&func_name,
789			&retval,
790			2, args,
791			0, NULL);
792
793	zval_ptr_dtor(&args[0]);
794	zval_ptr_dtor(&args[1]);
795	zval_ptr_dtor(&func_name);
796
797	if (call_result == FAILURE) {
798		/* stream_seek is not implemented, so disable seeks for this stream */
799		stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
800		/* there should be no retval to clean up */
801
802		zval_ptr_dtor(&retval);
803
804		return -1;
805	} else if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) {
806		ret = 0;
807	} else {
808		ret = -1;
809	}
810
811	zval_ptr_dtor(&retval);
812	ZVAL_UNDEF(&retval);
813
814	if (ret) {
815		return ret;
816	}
817
818	/* now determine where we are */
819	ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1);
820
821	call_result = call_user_function(NULL,
822		Z_ISUNDEF(us->object)? NULL : &us->object,
823		&func_name,
824		&retval,
825		0, NULL);
826
827	if (call_result == SUCCESS && Z_TYPE(retval) == IS_LONG) {
828		*newoffs = Z_LVAL(retval);
829		ret = 0;
830	} else if (call_result == FAILURE) {
831		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", us->wrapper->classname);
832		ret = -1;
833	} else {
834		ret = -1;
835	}
836
837	zval_ptr_dtor(&retval);
838	zval_ptr_dtor(&func_name);
839	return ret;
840}
841
842/* parse the return value from one of the stat functions and store the
843 * relevant fields into the statbuf provided */
844static int statbuf_from_array(zval *array, php_stream_statbuf *ssb)
845{
846	zval *elem;
847
848#define STAT_PROP_ENTRY_EX(name, name2)                        \
849	if (NULL != (elem = zend_hash_str_find(Z_ARRVAL_P(array), #name, sizeof(#name)-1))) {     \
850		ssb->sb.st_##name2 = zval_get_long(elem);                                                      \
851	}
852
853#define STAT_PROP_ENTRY(name) STAT_PROP_ENTRY_EX(name,name)
854
855	memset(ssb, 0, sizeof(php_stream_statbuf));
856	STAT_PROP_ENTRY(dev);
857	STAT_PROP_ENTRY(ino);
858	STAT_PROP_ENTRY(mode);
859	STAT_PROP_ENTRY(nlink);
860	STAT_PROP_ENTRY(uid);
861	STAT_PROP_ENTRY(gid);
862#if HAVE_STRUCT_STAT_ST_RDEV
863	STAT_PROP_ENTRY(rdev);
864#endif
865	STAT_PROP_ENTRY(size);
866	STAT_PROP_ENTRY(atime);
867	STAT_PROP_ENTRY(mtime);
868	STAT_PROP_ENTRY(ctime);
869#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
870	STAT_PROP_ENTRY(blksize);
871#endif
872#ifdef HAVE_ST_BLOCKS
873	STAT_PROP_ENTRY(blocks);
874#endif
875
876#undef STAT_PROP_ENTRY
877#undef STAT_PROP_ENTRY_EX
878	return SUCCESS;
879}
880
881static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
882{
883	zval func_name;
884	zval retval;
885	int call_result;
886	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
887	int ret = -1;
888
889	ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1);
890
891	call_result = call_user_function(NULL,
892			Z_ISUNDEF(us->object)? NULL : &us->object,
893			&func_name,
894			&retval,
895			0, NULL);
896
897	if (call_result == SUCCESS && Z_TYPE(retval) == IS_ARRAY) {
898		if (SUCCESS == statbuf_from_array(&retval, ssb))
899			ret = 0;
900	} else {
901		if (call_result == FAILURE) {
902			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!",
903					us->wrapper->classname);
904		}
905	}
906
907	zval_ptr_dtor(&retval);
908	zval_ptr_dtor(&func_name);
909
910	return ret;
911}
912
913
914static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) {
915	zval func_name;
916	zval retval;
917	int call_result;
918	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
919	int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
920	zval args[3];
921
922	switch (option) {
923	case PHP_STREAM_OPTION_CHECK_LIVENESS:
924		ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1);
925		call_result = call_user_function(NULL, Z_ISUNDEF(us->object)? NULL : &us->object, &func_name, &retval, 0, NULL);
926		if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
927			ret = zval_is_true(&retval) ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
928		} else {
929			ret = PHP_STREAM_OPTION_RETURN_ERR;
930			php_error_docref(NULL, E_WARNING,
931					"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
932					us->wrapper->classname);
933		}
934		zval_ptr_dtor(&retval);
935		zval_ptr_dtor(&func_name);
936		break;
937
938	case PHP_STREAM_OPTION_LOCKING:
939		ZVAL_LONG(&args[0], 0);
940
941		if (value & LOCK_NB) {
942			Z_LVAL_P(&args[0]) |= PHP_LOCK_NB;
943		}
944		switch(value & ~LOCK_NB) {
945		case LOCK_SH:
946			Z_LVAL_P(&args[0]) |= PHP_LOCK_SH;
947			break;
948		case LOCK_EX:
949			Z_LVAL_P(&args[0]) |= PHP_LOCK_EX;
950			break;
951		case LOCK_UN:
952			Z_LVAL_P(&args[0]) |= PHP_LOCK_UN;
953			break;
954		}
955
956		/* TODO wouldblock */
957		ZVAL_STRINGL(&func_name, USERSTREAM_LOCK, sizeof(USERSTREAM_LOCK)-1);
958
959		call_result = call_user_function_ex(NULL,
960						Z_ISUNDEF(us->object)? NULL : &us->object,
961						&func_name,
962						&retval,
963						1, args, 0, NULL);
964
965		if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
966			ret = (Z_TYPE(retval) == IS_FALSE);
967		} else if (call_result == FAILURE) {
968			if (value == 0) {
969			   	/* lock support test (TODO: more check) */
970				ret = PHP_STREAM_OPTION_RETURN_OK;
971			} else {
972				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_LOCK " is not implemented!",
973								 us->wrapper->classname);
974				ret = PHP_STREAM_OPTION_RETURN_ERR;
975			}
976		}
977
978		zval_ptr_dtor(&retval);
979		zval_ptr_dtor(&func_name);
980		zval_ptr_dtor(&args[0]);
981		break;
982
983	case PHP_STREAM_OPTION_TRUNCATE_API:
984		ZVAL_STRINGL(&func_name, USERSTREAM_TRUNCATE, sizeof(USERSTREAM_TRUNCATE)-1);
985
986		switch (value) {
987		case PHP_STREAM_TRUNCATE_SUPPORTED:
988			if (zend_is_callable_ex(&func_name,
989					Z_ISUNDEF(us->object)? NULL : Z_OBJ(us->object),
990					IS_CALLABLE_CHECK_SILENT, NULL, NULL, NULL))
991				ret = PHP_STREAM_OPTION_RETURN_OK;
992			else
993				ret = PHP_STREAM_OPTION_RETURN_ERR;
994			break;
995
996		case PHP_STREAM_TRUNCATE_SET_SIZE: {
997			ptrdiff_t new_size = *(ptrdiff_t*) ptrparam;
998			if (new_size >= 0 && new_size <= (ptrdiff_t)LONG_MAX) {
999				ZVAL_LONG(&args[0], (zend_long)new_size);
1000				call_result = call_user_function_ex(NULL,
1001								Z_ISUNDEF(us->object)? NULL : &us->object,
1002								&func_name,
1003								&retval,
1004								1, args, 0, NULL);
1005				if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
1006					if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) {
1007						ret = (Z_TYPE(retval) == IS_TRUE) ? PHP_STREAM_OPTION_RETURN_OK :
1008											   PHP_STREAM_OPTION_RETURN_ERR;
1009					} else {
1010						php_error_docref(NULL, E_WARNING,
1011								"%s::" USERSTREAM_TRUNCATE " did not return a boolean!",
1012								us->wrapper->classname);
1013					}
1014				} else {
1015					php_error_docref(NULL, E_WARNING,
1016							"%s::" USERSTREAM_TRUNCATE " is not implemented!",
1017							us->wrapper->classname);
1018				}
1019				zval_ptr_dtor(&retval);
1020				zval_ptr_dtor(&args[0]);
1021			} else { /* bad new size */
1022				ret = PHP_STREAM_OPTION_RETURN_ERR;
1023			}
1024			break;
1025		}
1026		}
1027		zval_ptr_dtor(&func_name);
1028		break;
1029
1030	case PHP_STREAM_OPTION_READ_BUFFER:
1031	case PHP_STREAM_OPTION_WRITE_BUFFER:
1032	case PHP_STREAM_OPTION_READ_TIMEOUT:
1033	case PHP_STREAM_OPTION_BLOCKING: {
1034
1035		ZVAL_STRINGL(&func_name, USERSTREAM_SET_OPTION, sizeof(USERSTREAM_SET_OPTION)-1);
1036
1037		ZVAL_LONG(&args[0], option);
1038		ZVAL_NULL(&args[1]);
1039		ZVAL_NULL(&args[2]);
1040
1041		switch(option) {
1042		case PHP_STREAM_OPTION_READ_BUFFER:
1043		case PHP_STREAM_OPTION_WRITE_BUFFER:
1044			ZVAL_LONG(&args[1], value);
1045			if (ptrparam) {
1046				ZVAL_LONG(&args[2], *(long *)ptrparam);
1047			} else {
1048				ZVAL_LONG(&args[2], BUFSIZ);
1049			}
1050			break;
1051		case PHP_STREAM_OPTION_READ_TIMEOUT: {
1052			struct timeval tv = *(struct timeval*)ptrparam;
1053			ZVAL_LONG(&args[1], tv.tv_sec);
1054			ZVAL_LONG(&args[2], tv.tv_usec);
1055			break;
1056			}
1057		case PHP_STREAM_OPTION_BLOCKING:
1058			ZVAL_LONG(&args[1], value);
1059			break;
1060		default:
1061			break;
1062		}
1063
1064		call_result = call_user_function_ex(NULL,
1065			Z_ISUNDEF(us->object)? NULL : &us->object,
1066			&func_name,
1067			&retval,
1068			3, args, 0, NULL);
1069
1070		if (call_result == FAILURE) {
1071			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_SET_OPTION " is not implemented!",
1072					us->wrapper->classname);
1073			ret = PHP_STREAM_OPTION_RETURN_ERR;
1074		} else if (zend_is_true(&retval)) {
1075			ret = PHP_STREAM_OPTION_RETURN_OK;
1076		} else {
1077			ret = PHP_STREAM_OPTION_RETURN_ERR;
1078		}
1079
1080		zval_ptr_dtor(&retval);
1081		zval_ptr_dtor(&args[2]);
1082		zval_ptr_dtor(&args[1]);
1083		zval_ptr_dtor(&args[0]);
1084		zval_ptr_dtor(&func_name);
1085
1086		break;
1087		}
1088	}
1089
1090	return ret;
1091}
1092
1093
1094static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1095{
1096	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1097	zval zfuncname, zretval;
1098	zval args[1];
1099	int call_result;
1100	zval object;
1101	int ret = 0;
1102
1103	/* create an instance of our class */
1104	user_stream_create_object(uwrap, context, &object);
1105	if (Z_TYPE(object) == IS_UNDEF) {
1106		return ret;
1107	}
1108
1109	/* call the unlink method */
1110	ZVAL_STRING(&args[0], url);
1111
1112	ZVAL_STRING(&zfuncname, USERSTREAM_UNLINK);
1113
1114	call_result = call_user_function_ex(NULL,
1115			&object,
1116			&zfuncname,
1117			&zretval,
1118			1, args,
1119			0, NULL	);
1120
1121	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1122		ret = (Z_TYPE(zretval) == IS_TRUE);
1123	} else if (call_result == FAILURE) {
1124		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", uwrap->classname);
1125 	}
1126
1127	/* clean up */
1128	zval_ptr_dtor(&object);
1129	zval_ptr_dtor(&zretval);
1130	zval_ptr_dtor(&zfuncname);
1131
1132	zval_ptr_dtor(&args[0]);
1133
1134	return ret;
1135}
1136
1137static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to,
1138							   int options, php_stream_context *context)
1139{
1140	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1141	zval zfuncname, zretval;
1142	zval args[2];
1143	int call_result;
1144	zval object;
1145	int ret = 0;
1146
1147	/* create an instance of our class */
1148	user_stream_create_object(uwrap, context, &object);
1149	if (Z_TYPE(object) == IS_UNDEF) {
1150		return ret;
1151	}
1152
1153	/* call the rename method */
1154	ZVAL_STRING(&args[0], url_from);
1155	ZVAL_STRING(&args[1], url_to);
1156
1157	ZVAL_STRING(&zfuncname, USERSTREAM_RENAME);
1158
1159	call_result = call_user_function_ex(NULL,
1160			&object,
1161			&zfuncname,
1162			&zretval,
1163			2, args,
1164			0, NULL	);
1165
1166	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1167		ret = (Z_TYPE(zretval) == IS_TRUE);
1168	} else if (call_result == FAILURE) {
1169		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", uwrap->classname);
1170 	}
1171
1172	/* clean up */
1173	zval_ptr_dtor(&object);
1174	zval_ptr_dtor(&zretval);
1175
1176	zval_ptr_dtor(&zfuncname);
1177	zval_ptr_dtor(&args[1]);
1178	zval_ptr_dtor(&args[0]);
1179
1180	return ret;
1181}
1182
1183static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode,
1184							  int options, php_stream_context *context)
1185{
1186	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1187	zval zfuncname, zretval;
1188	zval args[3];
1189	int call_result;
1190	zval object;
1191	int ret = 0;
1192
1193	/* create an instance of our class */
1194	user_stream_create_object(uwrap, context, &object);
1195	if (Z_TYPE(object) == IS_UNDEF) {
1196		return ret;
1197	}
1198
1199	/* call the mkdir method */
1200	ZVAL_STRING(&args[0], url);
1201	ZVAL_LONG(&args[1], mode);
1202	ZVAL_LONG(&args[2], options);
1203
1204	ZVAL_STRING(&zfuncname, USERSTREAM_MKDIR);
1205
1206	call_result = call_user_function_ex(NULL,
1207			&object,
1208			&zfuncname,
1209			&zretval,
1210			3, args,
1211			0, NULL	);
1212
1213	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1214		ret = (Z_TYPE(zretval) == IS_TRUE);
1215	} else if (call_result == FAILURE) {
1216		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", uwrap->classname);
1217 	}
1218
1219	/* clean up */
1220	zval_ptr_dtor(&object);
1221	zval_ptr_dtor(&zretval);
1222
1223	zval_ptr_dtor(&zfuncname);
1224	zval_ptr_dtor(&args[2]);
1225	zval_ptr_dtor(&args[1]);
1226	zval_ptr_dtor(&args[0]);
1227
1228	return ret;
1229}
1230
1231static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
1232							  int options, php_stream_context *context)
1233{
1234	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1235	zval zfuncname, zretval;
1236	zval args[2];
1237	int call_result;
1238	zval object;
1239	int ret = 0;
1240
1241	/* create an instance of our class */
1242	user_stream_create_object(uwrap, context, &object);
1243	if (Z_TYPE(object) == IS_UNDEF) {
1244		return ret;
1245	}
1246
1247	/* call the rmdir method */
1248	ZVAL_STRING(&args[0], url);
1249	ZVAL_LONG(&args[1], options);
1250
1251	ZVAL_STRING(&zfuncname, USERSTREAM_RMDIR);
1252
1253	call_result = call_user_function_ex(NULL,
1254			&object,
1255			&zfuncname,
1256			&zretval,
1257			2, args,
1258			0, NULL	);
1259
1260	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1261		ret = (Z_TYPE(zretval) == IS_TRUE);
1262	} else if (call_result == FAILURE) {
1263		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", uwrap->classname);
1264 	}
1265
1266	/* clean up */
1267	zval_ptr_dtor(&object);
1268	zval_ptr_dtor(&zretval);
1269
1270	zval_ptr_dtor(&zfuncname);
1271	zval_ptr_dtor(&args[1]);
1272	zval_ptr_dtor(&args[0]);
1273
1274	return ret;
1275}
1276
1277static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option,
1278								 void *value, php_stream_context *context)
1279{
1280	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1281	zval zfuncname, zretval;
1282	zval args[3];
1283	int call_result;
1284	zval object;
1285	int ret = 0;
1286
1287	switch(option) {
1288		case PHP_STREAM_META_TOUCH:
1289			array_init(&args[2]);
1290			if(value) {
1291				struct utimbuf *newtime = (struct utimbuf *)value;
1292				add_index_long(&args[2], 0, newtime->modtime);
1293				add_index_long(&args[2], 1, newtime->actime);
1294			}
1295			break;
1296		case PHP_STREAM_META_GROUP:
1297		case PHP_STREAM_META_OWNER:
1298		case PHP_STREAM_META_ACCESS:
1299			ZVAL_LONG(&args[2], *(long *)value);
1300			break;
1301		case PHP_STREAM_META_GROUP_NAME:
1302		case PHP_STREAM_META_OWNER_NAME:
1303			ZVAL_STRING(&args[2], value);
1304			break;
1305		default:
1306			php_error_docref(NULL, E_WARNING, "Unknown option %d for " USERSTREAM_METADATA, option);
1307			zval_ptr_dtor(&args[2]);
1308			return ret;
1309	}
1310
1311	/* create an instance of our class */
1312	user_stream_create_object(uwrap, context, &object);
1313	if (Z_TYPE(object) == IS_UNDEF) {
1314		zval_ptr_dtor(&args[2]);
1315		return ret;
1316	}
1317
1318	/* call the mkdir method */
1319	ZVAL_STRING(&args[0], url);
1320	ZVAL_LONG(&args[1], option);
1321
1322	ZVAL_STRING(&zfuncname, USERSTREAM_METADATA);
1323
1324	call_result = call_user_function_ex(NULL,
1325			&object,
1326			&zfuncname,
1327			&zretval,
1328			3, args,
1329			0, NULL	);
1330
1331	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1332		ret = Z_TYPE(zretval) == IS_TRUE;
1333	} else if (call_result == FAILURE) {
1334		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", uwrap->classname);
1335 	}
1336
1337	/* clean up */
1338	zval_ptr_dtor(&object);
1339	zval_ptr_dtor(&zretval);
1340
1341	zval_ptr_dtor(&zfuncname);
1342	zval_ptr_dtor(&args[0]);
1343	zval_ptr_dtor(&args[1]);
1344	zval_ptr_dtor(&args[2]);
1345
1346	return ret;
1347}
1348
1349
1350static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags,
1351								 php_stream_statbuf *ssb, php_stream_context *context)
1352{
1353	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1354	zval zfuncname, zretval;
1355	zval args[2];
1356	int call_result;
1357	zval object;
1358	int ret = -1;
1359
1360	/* create an instance of our class */
1361	user_stream_create_object(uwrap, context, &object);
1362	if (Z_TYPE(object) == IS_UNDEF) {
1363		return ret;
1364	}
1365
1366	/* call it's stat_url method - set up params first */
1367	ZVAL_STRING(&args[0], url);
1368	ZVAL_LONG(&args[1], flags);
1369
1370	ZVAL_STRING(&zfuncname, USERSTREAM_STATURL);
1371
1372	call_result = call_user_function_ex(NULL,
1373			&object,
1374			&zfuncname,
1375			&zretval,
1376			2, args,
1377			0, NULL	);
1378
1379	if (call_result == SUCCESS && Z_TYPE(zretval) == IS_ARRAY) {
1380		/* We got the info we needed */
1381		if (SUCCESS == statbuf_from_array(&zretval, ssb))
1382			ret = 0;
1383	} else {
1384		if (call_result == FAILURE) {
1385			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!",
1386					uwrap->classname);
1387		}
1388	}
1389
1390	/* clean up */
1391	zval_ptr_dtor(&object);
1392	zval_ptr_dtor(&zretval);
1393
1394	zval_ptr_dtor(&zfuncname);
1395	zval_ptr_dtor(&args[1]);
1396	zval_ptr_dtor(&args[0]);
1397
1398	return ret;
1399
1400}
1401
1402static size_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count)
1403{
1404	zval func_name;
1405	zval retval;
1406	int call_result;
1407	size_t didread = 0;
1408	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1409	php_stream_dirent *ent = (php_stream_dirent*)buf;
1410
1411	/* avoid problems if someone mis-uses the stream */
1412	if (count != sizeof(php_stream_dirent))
1413		return 0;
1414
1415	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1);
1416
1417	call_result = call_user_function(NULL,
1418			Z_ISUNDEF(us->object)? NULL : &us->object,
1419			&func_name,
1420			&retval,
1421			0, NULL);
1422
1423	if (call_result == SUCCESS && Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
1424		convert_to_string(&retval);
1425		PHP_STRLCPY(ent->d_name, Z_STRVAL(retval), sizeof(ent->d_name), Z_STRLEN(retval));
1426
1427		didread = sizeof(php_stream_dirent);
1428	} else if (call_result == FAILURE) {
1429		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!",
1430				us->wrapper->classname);
1431	}
1432
1433	zval_ptr_dtor(&retval);
1434	zval_ptr_dtor(&func_name);
1435
1436	return didread;
1437}
1438
1439static int php_userstreamop_closedir(php_stream *stream, int close_handle)
1440{
1441	zval func_name;
1442	zval retval;
1443	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1444
1445	assert(us != NULL);
1446
1447	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_CLOSE, sizeof(USERSTREAM_DIR_CLOSE)-1);
1448
1449	call_user_function(NULL,
1450			Z_ISUNDEF(us->object)? NULL : &us->object,
1451			&func_name,
1452			&retval,
1453			0, NULL);
1454
1455	zval_ptr_dtor(&retval);
1456	zval_ptr_dtor(&func_name);
1457	zval_ptr_dtor(&us->object);
1458	ZVAL_UNDEF(&us->object);
1459
1460	efree(us);
1461
1462	return 0;
1463}
1464
1465static int php_userstreamop_rewinddir(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1466{
1467	zval func_name;
1468	zval retval;
1469	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1470
1471	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_REWIND, sizeof(USERSTREAM_DIR_REWIND)-1);
1472
1473	call_user_function(NULL,
1474			Z_ISUNDEF(us->object)? NULL : &us->object,
1475			&func_name,
1476			&retval,
1477			0, NULL);
1478
1479	zval_ptr_dtor(&retval);
1480	zval_ptr_dtor(&func_name);
1481
1482	return 0;
1483
1484}
1485
1486static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
1487{
1488	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1489	zval func_name;
1490	zval retval;
1491	zval args[1];
1492	php_stream * intstream = NULL;
1493	int call_result;
1494	int ret = FAILURE;
1495
1496	ZVAL_STRINGL(&func_name, USERSTREAM_CAST, sizeof(USERSTREAM_CAST)-1);
1497
1498	switch(castas) {
1499	case PHP_STREAM_AS_FD_FOR_SELECT:
1500		ZVAL_LONG(&args[0], PHP_STREAM_AS_FD_FOR_SELECT);
1501		break;
1502	default:
1503		ZVAL_LONG(&args[0], PHP_STREAM_AS_STDIO);
1504		break;
1505	}
1506
1507	call_result = call_user_function_ex(NULL,
1508			Z_ISUNDEF(us->object)? NULL : &us->object,
1509			&func_name,
1510			&retval,
1511			1, args, 0, NULL);
1512
1513	do {
1514		if (call_result == FAILURE) {
1515			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
1516					us->wrapper->classname);
1517			break;
1518		}
1519		if (!zend_is_true(&retval)) {
1520			break;
1521		}
1522		php_stream_from_zval_no_verify(intstream, &retval);
1523		if (!intstream) {
1524			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must return a stream resource",
1525					us->wrapper->classname);
1526			break;
1527		}
1528		if (intstream == stream) {
1529			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must not return itself",
1530					us->wrapper->classname);
1531			intstream = NULL;
1532			break;
1533		}
1534		ret = php_stream_cast(intstream, castas, retptr, 1);
1535	} while (0);
1536
1537	zval_ptr_dtor(&retval);
1538	zval_ptr_dtor(&func_name);
1539	zval_ptr_dtor(&args[0]);
1540
1541	return ret;
1542}
1543
1544const php_stream_ops php_stream_userspace_ops = {
1545	php_userstreamop_write, php_userstreamop_read,
1546	php_userstreamop_close, php_userstreamop_flush,
1547	"user-space",
1548	php_userstreamop_seek,
1549	php_userstreamop_cast,
1550	php_userstreamop_stat,
1551	php_userstreamop_set_option,
1552};
1553
1554const php_stream_ops php_stream_userspace_dir_ops = {
1555	NULL, /* write */
1556	php_userstreamop_readdir,
1557	php_userstreamop_closedir,
1558	NULL, /* flush */
1559	"user-space-dir",
1560	php_userstreamop_rewinddir,
1561	NULL, /* cast */
1562	NULL, /* stat */
1563	NULL  /* set_option */
1564};
1565