I finally got to the bottom of this bug. It was hiding nice and deep.
I thought I'd run this by folks first before committing, as it impacts streams, which are pretty important :D . It passes the current tests, and I wrote test for it too. (below).
Index: main/streams/streams.c
--- main/streams/streams.c (revision 287973)
+++ main/streams/streams.c (working copy)
@@ -901,10 +901,7 @@
}
if (!e) {
-
if (seek_len < maxlen && !stream->eof) {
-
return NULL;
-
}
-
toread = maxlen;
-
toread = (seek_len < maxlen) ? seek_len : maxlen; } else { toread = e - (char *) stream->readbuf - stream->readpos; skip = 1;
=======/ext/standard/tests/streams/bug49148.phpt:=======
--TEST--
Bug #49148 (combination of stream_get_line and fseek does not work correctly)
--FILE--
<?php
// should give back "line 1" and ""
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" and ""
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
fseek($tmp, ftell($tmp)); // expected that this does not affect result.
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" and "line 2"
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" and "line 2"
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
fseek($tmp, ftell($tmp)); // expected that this does not affect result.
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" and "line 2"
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" and "line 2"
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
fseek($tmp, ftell($tmp)); // expected that this does not affect result.
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
?>
--EXPECT--
string(5) "line1"
string(0) ""
string(5) "line1"
string(0) ""
string(5) "line1"
string(5) "line2"
string(5) "line1"
string(5) "line2"
string(5) "line1"
string(5) "line2"
string(5) "line1"
string(5) "line2"
Garrett
Hi!
I thought I'd run this by folks first before committing, as it
impacts streams, which are pretty important :D . It passes the
current tests, and I wrote test for it too. (below).
I notice that the bug says it happens only on Windows, while the fix
doesn't have anything specific for windows. Could you explain why it is so?
Another question is why stream->eof is not set in this case on Windows -
shouldn't it be?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
it happens on Linux and Windows. I've repro'd it in both places.
The bug submitter hadn't tested elsewhere.
G
-----Original Message-----
From: Stanislav Malyshev [mailto:stas@zend.com]
Sent: Wednesday, September 02, 2009 2:15 PM
To: Garrett Serack
Cc: 'PHP Internals'
Subject: Re: [PHP-DEV] Fix for 49148 (combination of stream_get_line and fseek does not work correctly)
Hi!
I thought I'd run this by folks first before committing, as it
impacts streams, which are pretty important :D . It passes the
current tests, and I wrote test for it too. (below).
I notice that the bug says it happens only on Windows, while the fix
doesn't have anything specific for windows. Could you explain why it is so?
Another question is why stream->eof is not set in this case on Windows -
shouldn't it be?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi!
it happens on Linux and Windows. I've repro'd it in both places.
The bug submitter hadn't tested elsewhere.
You are right. However, I notice something strange with your patch - if
I call stream_get_line again with the same parameters I get "".
Shouldn't I get false instead, as it's at EOF? fgets()
returns false.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Well, that's a really good question.
Pre-patch, at the end of the stream (when you don't explicitly seek), you get empty strings:
--example.php--
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
--results--
string(5) "line1"
string(0) ""
string(0) ""
string(0) ""
string(0) ""
Do you want to change the behavior so that it does return false? It's trivial to do (and I'd agree that it would be correct), but it does change the behavior from the current behavior.
G
-----Original Message-----
From: Stanislav Malyshev [mailto:stas@zend.com]
Sent: Thursday, September 03, 2009 5:31 PM
To: Garrett Serack
Cc: 'PHP Internals'
Subject: Re: [PHP-DEV] Fix for 49148 (combination of stream_get_line and fseek does not work correctly)
Hi!
it happens on Linux and Windows. I've repro'd it in both places.
The bug submitter hadn't tested elsewhere.
You are right. However, I notice something strange with your patch - if
I call stream_get_line again with the same parameters I get "".
Shouldn't I get false instead, as it's at EOF? fgets()
returns false.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi!
Well, that's a really good question.
Pre-patch, at the end of the stream (when you don't explicitly seek),
you get empty strings:
I think it's a bug, docs say it's like fgets and fgets returns false on
EOF. Also, "" with this function can mean "delimiter, than another
delimititer" if I understand correctly, because it doesn't return the
delims - which means returning "" on EOF is even worse bug.
Do you want to change the behavior so that it does return false? It's
trivial to do (and I'd agree that it would be correct), but it does
change the behavior from the current behavior.
I think it's a bug and should be changed to return false on EOF, just as
fgets does. Of course, if there's data before EOF it should return it,
and then next read should return false. The case where file ends with
delim should return data before delim on first read, and false on the
next after delim was read.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
I've updated my patch to return null on EOF.
Index: streams/streams.c
--- streams/streams.c (revision 289073)
+++ streams/streams.c (working copy)
@@ -890,6 +890,9 @@
size_t seek_len;
seek_len = stream->writepos - stream->readpos;
-
if(!seek_len)
-
return FALSE;
-
if (seek_len > maxlen) { seek_len = maxlen; }
@@ -901,10 +904,7 @@
}
if (!e) {
-
if (seek_len < maxlen && !stream->eof) {
-
return NULL;
-
}
-
toread = maxlen;
-
toread = (seek_len < maxlen) ? seek_len : maxlen; } else { toread = e - (char *) stream->readbuf - stream->readpos; skip = 1;
And the test script for it:
--TEST--
Bug #49148 (combination of stream_get_line and fseek does not work correctly)
--FILE--
<?php
// should give back "line 1" , false, false
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" , false, false
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
fseek($tmp, ftell($tmp)); // expected that this does not affect result.
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" , "line 2", false
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" , "line 2", false
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
fseek($tmp, ftell($tmp)); // expected that this does not affect result.
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" , "line 2", false
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
// should give back "line 1" , "line 2", false
$tmp = tmpfile()
;
fwrite($tmp, "line1\r\n");
fwrite($tmp, "line2\r\n");
fseek($tmp, 0);
var_dump(stream_get_line($tmp, null, "\r\n"));
fseek($tmp, ftell($tmp)); // expected that this does not affect result.
var_dump(stream_get_line($tmp, null, "\r\n"));
var_dump(stream_get_line($tmp, null, "\r\n"));
fclose($tmp);
?>
--EXPECT--
string(5) "line1"
bool(false)
bool(false)
string(5) "line1"
bool(false)
bool(false)
string(5) "line1"
string(5) "line2"
bool(false)
string(5) "line1"
string(5) "line2"
bool(false)
string(5) "line1"
string(5) "line2"
bool(false)
string(5) "line1"
string(5) "line2"
bool(false)
-----Original Message-----
From: Stanislav Malyshev [mailto:stas@zend.com]
Sent: Wednesday, September 09, 2009 2:59 PM
To: Garrett Serack
Cc: 'PHP Internals'
Subject: Re: [PHP-DEV] Fix for 49148 (combination of stream_get_line and fseek does not work correctly)
Hi!
Well, that's a really good question.
Pre-patch, at the end of the stream (when you don't explicitly seek),
you get empty strings:
I think it's a bug, docs say it's like fgets and fgets returns false on
EOF. Also, "" with this function can mean "delimiter, than another
delimititer" if I understand correctly, because it doesn't return the
delims - which means returning "" on EOF is even worse bug.
Do you want to change the behavior so that it does return false? It's
trivial to do (and I'd agree that it would be correct), but it does
change the behavior from the current behavior.
I think it's a bug and should be changed to return false on EOF, just as
fgets does. Of course, if there's data before EOF it should return it,
and then next read should return false. The case where file ends with
delim should return data before delim on first read, and false on the
next after delim was read.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi!
I've updated my patch to return null on EOF.
Unfortunately, on Linux this patch fails test stream_get_line_nb.phpt -
looks like it changes the stream behavior in the non-blocking case.
Index: streams/streams.c
--- streams/streams.c (revision 289073)
+++ streams/streams.c (working copy)
@@ -890,6 +890,9 @@
size_t seek_len;seek_len = stream->writepos - stream->readpos;
if(!seek_len)
return FALSE;
if (seek_len> maxlen) { seek_len = maxlen; }
@@ -901,10 +904,7 @@
}if (!e) {
if (seek_len< maxlen&& !stream->eof) {
return NULL;
}
toread = maxlen;
toread = (seek_len< maxlen) ? seek_len : maxlen; } else { toread = e - (char *) stream->readbuf - stream->readpos; skip = 1;
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com