wez Mon Sep 23 09:22:10 2002 EDT Modified files: /php4/main memory_streams.c php_streams.h streams.c user_streams.c /php4/ext/standard/tests/file userstreams.phpt Log: Revise buffer/seek code a little. Tidy up user streams even more. Make test case quite aggressive.
Index: php4/main/memory_streams.c diff -u php4/main/memory_streams.c:1.17 php4/main/memory_streams.c:1.18 --- php4/main/memory_streams.c:1.17 Sun Sep 22 21:47:03 2002 +++ php4/main/memory_streams.c Mon Sep 23 09:22:10 2002 @@ -216,6 +216,7 @@ PHPAPI php_stream *_php_stream_memory_create(int mode STREAMS_DC TSRMLS_DC) { php_stream_memory_data *self; + php_stream *stream; self = emalloc(sizeof(*self)); assert(self != NULL); @@ -224,7 +225,10 @@ self->fsize = 0; self->smax = -1; self->mode = mode; - return php_stream_alloc(&php_stream_memory_ops, self, 0, "rwb"); + + stream = php_stream_alloc(&php_stream_memory_ops, self, 0, "rwb"); + stream->flags |= PHP_STREAM_FLAG_NO_BUFFER; + return stream; } /* }}} */ @@ -435,8 +439,9 @@ self->smax = max_memory_usage; self->mode = mode; stream = php_stream_alloc(&php_stream_temp_ops, self, 0, "rwb"); + stream->flags |= PHP_STREAM_FLAG_NO_BUFFER; self->innerstream = php_stream_memory_create(mode); -/* php_stream_temp_write(stream, NULL, 0 TSRMLS_CC); */ + return stream; } /* }}} */ Index: php4/main/php_streams.h diff -u php4/main/php_streams.h:1.44 php4/main/php_streams.h:1.45 --- php4/main/php_streams.h:1.44 Sun Sep 22 21:47:04 2002 +++ php4/main/php_streams.h Mon Sep 23 09:22:10 2002 @@ -377,6 +377,10 @@ #define PHP_STREAM_BUFFER_LINE 1 /* line buffered */ #define PHP_STREAM_BUFFER_FULL 2 /* fully buffered */ +#define PHP_STREAM_OPTION_RETURN_OK 0 /* option set OK */ +#define PHP_STREAM_OPTION_RETURN_ERR -1 /* problem setting option */ +#define PHP_STREAM_OPTION_RETURN_NOTIMPL -2 /* underlying stream does not +implement; streams can handle it instead */ + /* copy up to maxlen bytes from src to dest. If maxlen is PHP_STREAM_COPY_ALL, copy until eof(src). * Uses mmap if the src is a plain file and at offset 0 */ #define PHP_STREAM_COPY_ALL -1 Index: php4/main/streams.c diff -u php4/main/streams.c:1.75 php4/main/streams.c:1.76 --- php4/main/streams.c:1.75 Sun Sep 22 21:47:04 2002 +++ php4/main/streams.c Mon Sep 23 09:22:10 2002 @@ -20,7 +20,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: streams.c,v 1.75 2002/09/23 01:47:04 wez Exp $ */ +/* $Id: streams.c,v 1.76 2002/09/23 13:22:10 wez Exp $ */ #define _GNU_SOURCE #include "php.h" @@ -357,8 +357,36 @@ PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC) { + size_t avail, toread, didread = 0; + + /* take from the read buffer first. + * It is possible that a buffered stream was switched to non-buffered, so we + * drain the remainder of the buffer before using the "raw" read mode for + * the excess */ + avail = stream->writepos - stream->readpos; + if (avail) { + toread = avail; + if (toread > size) + toread = size; + + memcpy(buf, stream->readbuf + stream->readpos, toread); + stream->readpos += toread; + size -= toread; + buf += toread; + didread += size; + } + + if (size == 0) + return didread; + if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1) { - return stream->ops->read(stream, buf, size TSRMLS_CC); + if (stream->filterhead) { + didread += stream->filterhead->fops->read(stream, +stream->filterhead, + buf, size + TSRMLS_CC); + } else { + didread += stream->ops->read(stream, buf, size TSRMLS_CC); + } } else { php_stream_fill_read_buffer(stream, size TSRMLS_CC); @@ -367,9 +395,10 @@ memcpy(buf, stream->readbuf + stream->readpos, size); stream->readpos += size; - stream->position += size; - return size; + didread += size; } + stream->position += size; + return didread; } PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC) @@ -550,7 +579,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC) { /* not moving anywhere */ - if (offset == 0 && whence == SEEK_CUR) + if ((offset == 0 && whence == SEEK_CUR) || (offset == stream->position && +whence == SEEK_SET)) return 0; /* handle the case where we are in the buffer */ @@ -576,6 +605,8 @@ stream->readpos = stream->writepos = 0; if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { + int ret; + if (stream->filterhead) stream->filterhead->fops->flush(stream, stream->filterhead, 0 TSRMLS_CC); @@ -585,7 +616,12 @@ whence = SEEK_SET; break; } - return stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC); + ret = stream->ops->seek(stream, offset, whence, &stream->position +TSRMLS_CC); + + if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) + return ret; + /* else the stream has decided that it can't support seeking after all; + * fall through to attempt emulation */ } /* emulate forward moving seeks with reads */ @@ -603,16 +639,37 @@ return 0; } - php_error_docref(NULL TSRMLS_CC, E_WARNING, "streams of type %s do not support seeking", stream->ops->label); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking"); return -1; } PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC) { - if (stream->ops->set_option) - return stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC); - return -1; + int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL; + + if (stream->ops->set_option) { + ret = stream->ops->set_option(stream, option, value, ptrparam +TSRMLS_CC); + } + + if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) { + switch(option) { + case PHP_STREAM_OPTION_BUFFER: + /* try to match the buffer mode as best we can */ + if (value == PHP_STREAM_BUFFER_NONE) { + stream->flags |= PHP_STREAM_FLAG_NO_BUFFER; + } else { + stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER; + } + ret = PHP_STREAM_OPTION_RETURN_OK; + break; + + default: + ret = PHP_STREAM_OPTION_RETURN_ERR; + } + } + + return ret; } PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC) @@ -1097,12 +1154,15 @@ switch(value) { case PHP_STREAM_BUFFER_NONE: + stream->flags |= PHP_STREAM_FLAG_NO_BUFFER; return setvbuf(data->file, NULL, _IONBF, 0); case PHP_STREAM_BUFFER_LINE: + stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER; return setvbuf(data->file, NULL, _IOLBF, size); case PHP_STREAM_BUFFER_FULL: + stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER; return setvbuf(data->file, NULL, _IOFBF, size); default: Index: php4/main/user_streams.c diff -u php4/main/user_streams.c:1.20 php4/main/user_streams.c:1.21 --- php4/main/user_streams.c:1.20 Sun Sep 22 21:47:04 2002 +++ php4/main/user_streams.c Mon Sep 23 09:22:10 2002 @@ -17,7 +17,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: user_streams.c,v 1.20 2002/09/23 01:47:04 wez Exp $ */ +/* $Id: user_streams.c,v 1.21 2002/09/23 13:22:10 wez Exp $ */ #include "php.h" #include "php_globals.h" @@ -85,29 +85,46 @@ /* 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() - { - } - function stream_seek($offset, $whence) - { - } + 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; + } **/ @@ -267,6 +284,9 @@ if (call_result == SUCCESS && retval != NULL) { convert_to_long_ex(&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 */ @@ -305,8 +325,14 @@ if (call_result == SUCCESS && retval != NULL && zval_is_true(retval)) didread = 0; - else + else { + if (call_result == FAILURE) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" +USERSTREAM_EOF " - is not implemented! Assuming EOF", + us->wrapper->classname); + } + didread = EOF; + } } else { zval *zcount; @@ -334,8 +360,10 @@ } 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); } @@ -430,10 +458,19 @@ zval_ptr_dtor(&zoffs); zval_ptr_dtor(&zwhence); - if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_LONG) - ret = Z_LVAL_P(retval); - else + 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 */ + return -1; + } else if (call_result == SUCCESS && retval != NULL && zval_is_true(retval)) { + ret = 0; + } else { ret = -1; + } + + if (retval) + zval_ptr_dtor(&retval); /* now determine where we are */ ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1, 0); @@ -446,6 +483,9 @@ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_LONG) *newoffs = Z_LVAL_P(retval); + else + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s::" USERSTREAM_TELL " - +is not implemented!", + us->wrapper->classname); if (retval) zval_ptr_dtor(&retval); Index: php4/ext/standard/tests/file/userstreams.phpt diff -u php4/ext/standard/tests/file/userstreams.phpt:1.1 php4/ext/standard/tests/file/userstreams.phpt:1.2 --- php4/ext/standard/tests/file/userstreams.phpt:1.1 Wed Sep 18 06:15:40 2002 +++ php4/ext/standard/tests/file/userstreams.phpt Mon Sep 23 09:22:10 2002 @@ -4,28 +4,151 @@ <?php # vim600:syn=php: -class uselessstream { +/* This is a fairly aggressive test that looks at + * user streams and also gives the seek/gets/buffer + * layer of streams a thorough testing */ + +$lyrics = <<<EOD +...and the road becomes my bride +I have stripped of all but pride +so in her I do confide +and she keeps me satisfied +gives me all I need +...and with dust in throat I crave +to the game you stay a slave +rover wanderer +nomad vagabond +call me what you will + But Ill take my time anywhere + Free to speak my mind anywhere + and Ill redefine anywhere + Anywhere I roam + Where I lay my head is home +...and the earth becomes my throne +I adapt to the unknown +under wandering stars Ive grown +by myself but not alone +I ask no one +...and my ties are severed clean +the less I have the more I gain +off the beaten path I reign +rover wanderer +nomad vagabond +call me what you will + But Ill take my time anywhere + Free to speak my mind anywhere + and Ill never mind anywhere + Anywhere I roam + Where I lay my head is home + But Ill take my time anywhere + Free to speak my mind anywhere + and Ill take my find anywhere + Anywhere I roam + Where I lay my head is home + carved upon my stone + my body lie but still I roam + Wherever I may roam. + +Wherever I May Roam + +EOD; + +/* repeat the data a few times so that it grows larger than + * the default cache chunk size and that we have something + * to seek around... */ +$DATA = ""; +for ($i = 0; $i < 30; $i++) { + if ($i % 2 == 0) + $DATA .= str_rot13($lyrics); + else + $DATA .= $lyrics; } -class mystream { +/* store the data in a regular file so that we can compare + * the results */ +$tf = tmpfile(); +fwrite($tf, $DATA); +$n = ftell($tf); +rewind($tf) or die("failed to rewind tmp file!"); +if (ftell($tf) != 0) + die("tmpfile is not at start!"); +$DATALEN = strlen($DATA); +if ($n != $DATALEN) + die("tmpfile stored $n bytes; should be $DATALEN!"); - function mystream() - { - echo "MYSTREAM: constructor called!\n"; - } +class uselessstream { +} +class mystream { var $path; var $mode; var $options; + var $position; + var $varname; + function stream_open($path, $mode, $options, &$opened_path) { $this->path = $path; $this->mode = $mode; $this->options = $options; + + $split = parse_url($path); + $this->varname = $split["host"]; + $this->position = 0; + return true; } + function stream_read($count) + { + $ret = substr($GLOBALS[$this->varname], $this->position, $count); + $this->position += strlen($ret); + return $ret; + } + + function stream_tell() + { + return $this->position; + } + + function stream_eof() + { + return $this->position >= strlen($GLOBALS[$this->varname]); + } + + function stream_seek($offset, $whence) + { + switch($whence) { + case SEEK_SET: + if ($offset < strlen($GLOBALS[$this->varname]) && +$offset >= 0) { + $this->position = $offset; + return true; + } else { + return false; + } + break; + case SEEK_CUR: + if ($offset >= 0) { + $this->position += $offset; + return true; + } else { + return false; + } + break; + case SEEK_END: + if (strlen($GLOBALS[$this->varname]) + $offset >= 0) { + $this->position = +strlen($GLOBALS[$this->varname]) + $offset; + return true; + } else { + return false; + } + break; + default: + return false; + } + } + } if (@file_register_wrapper("bogus", "class_not_exist")) @@ -42,10 +165,129 @@ if (is_resource($b)) die("Opened a bogon??"); -$fp = fopen("test://url", "rb"); +$fp = fopen("test://DATA", "rb"); if (!is_resource($fp)) die("Failed to open resource"); +/* some default seeks that will cause buffer/cache misses */ +$seeks = array( + array(SEEK_SET, 0, 0), + array(SEEK_CUR, 8450, 8450), + array(SEEK_CUR, -7904, 546), + array(SEEK_CUR, 12456, 13002), + + /* end up at BOF so that randomly generated seek offsets + * below will know where they are supposed to be */ + array(SEEK_SET, 0, 0) +); + +$whence_map = array( + SEEK_CUR, + SEEK_SET, + SEEK_END +); +$whence_names = array( + SEEK_CUR => "SEEK_CUR", + SEEK_SET => "SEEK_SET", + SEEK_END => "SEEK_END" + ); + +/* generate some random seek offsets */ +$position = 0; +for ($i = 0; $i < 256; $i++) { + $whence = $whence_map[array_rand($whence_map, 1)]; + switch($whence) { + case SEEK_SET: + $offset = rand(0, $DATALEN); + $position = $offset; + break; + case SEEK_END: + $offset = -rand(0, $DATALEN); + $position = $DATALEN + $offset; + break; + case SEEK_CUR: + $offset = rand(0, $DATALEN); + $offset -= $position; + $position += $offset; + break; + } + + $seeks[] = array($whence, $offset, $position); +} + +/* we compare the results of fgets using differing line lengths to + * test the fgets layer also */ +$line_lengths = array(1024, 256, 64, 16); +$fail_count = 0; + +ob_start(); +foreach($line_lengths as $line_length) { + /* now compare the real stream with the user stream */ + $j = 0; + rewind($tf); + rewind($fp); + foreach($seeks as $seekdata) { + list($whence, $offset, $position) = $seekdata; + + $rpb = ftell($tf); + $rr = (int)fseek($tf, $offset, $whence); + $rpa = ftell($tf); + $rline = fgets($tf, $line_length); + (int)fseek($tf, - strlen($rline), SEEK_CUR); + + $upb = ftell($fp); + $ur = (int)fseek($fp, $offset, $whence); + $upa = ftell($fp); + $uline = fgets($fp, $line_length); + (int)fseek($fp, - strlen($uline), SEEK_CUR); + + printf("\n--[%d] whence=%s offset=%d line_length=%d +position_should_be=%d --\n", + $j, $whence_names[$whence], $offset, $line_length, $position); + printf("REAL: pos=(%d,%d,%d) ret=%d line=`%s'\n", $rpb, $rpa, +ftell($tf), $rr, $rline); + printf("USER: pos=(%d,%d,%d) ret=%d line=`%s'\n", $upb, $upa, +ftell($fp), $ur, $uline); + + if ($rr != $ur || $rline != $uline || $rpa != $position || $upa != +$position) { + $fail_count++; + $dat = file_get_wrapper_data($fp); + var_dump($dat); + break; + } + + $j++; + } + if ($fail_count) + break; +} + +if ($fail_count == 0) { + ob_end_clean(); + echo "SEEK: OK\n"; +} else { + echo "SEEK: FAIL\n"; + ob_end_flush(); +} + +$fail_count = 0; +fseek($fp, $DATALEN / 2, SEEK_SET); +fseek($tf, $DATALEN / 2, SEEK_SET); + +while(!feof($fp)) { + $uline = fgets($fp, 1024); + $rline = fgets($fp, 1024); + + if ($uline != $rline) { + echo "FGETS: FAIL\nuser=$uline\nreal=$rline\n"; + $fail_count++; + break; + } +} + +if ($fail_count == 0) + echo "FGETS: OK\n"; + + ?> --EXPECT-- Registered +SEEK: OK +FGETS: OK
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php