Repository navigation
Conversation
|
@ndossche I created to deeper test script comparing different streams and behavior (see #23905 (comment)). |
0b8091a to
bc78826
Compare
|
@ndossche I reworked the branch and updated it to latest PHP-8.6. What has been changed since last?
New commit: $fp = fopen($file, 'r+'); // "hello world"
fread($fp, 5); // "hello"
fseek($fp, -12, SEEK_END); // -1, ftell() is still 5
fread($fp, 5); // before: "", now: " worl"A failed seek now returns early and keeps the position, the read buffer and the filter state. The new test
Not addressed / possible follow-ups
|
|
I will have a read through this tomorrow. |
| return php_stream_filters_seek_all(stream, is_start_seeking, offset, whence) == SUCCESS ? ret : -1; | ||
| } | ||
|
|
||
| if ((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { |
There was a problem hiding this comment.
Why did you change the behaviour of this case?
There was a problem hiding this comment.
This is needed because a failed seek discarded the read buffer, although the stream did not move. A buffered stream has usually read ahead, so the next read or write then continued from the read-ahead position while ftell() still reported the old one. This affects every buffered stream, e.g. plain files and user wrappers, not only the blob streams:
file_put_contents($f, 'hello world');
$fp = fopen($f, 'r+');
fread($fp, 5); // "hello" (the stream has read ahead to the end)
fseek($fp, -12, SEEK_END); // -1, ftell() is still 5
// on reading
fread($fp, 5); // before: "" (" world" skipped), this change: " worl"
// or writing:
fwrite($fp, '!'); // before: "hello world!", this change: "hello!world"With a user wrapper whose stream_seek() refuses the target, feof() returned true after the failed seek, and the next read returned "" although data remained. Stream filters were also told about the seek (php_stream_filters_seek_all()), although it never happened.
After the change, a failed seek returns early and keeps the position, the read buffer and the filter state. Successful seeks are unchanged. The SQLite blob fixes depend on this: their streams are buffered, so a failed seek after a partial read would otherwise still continue from the read-ahead position.
bukka
left a comment
There was a problem hiding this comment.
It looks good. Just minor things really.
I think it needs to target master only. There are some semantic changes and that's just given when you need to update UPGRADING / UPGRADING.INTERNALS which cannot be done during RC or stable version.
| stream->fatal_error = 0; | ||
|
|
||
| /* invalidate the buffer contents */ | ||
| stream->readpos = stream->writepos = 0; |
There was a problem hiding this comment.
hmm this should be probably kept in userspace.c for case where seek is successful but tell is missing or return non int (missing is already a warning I think but we should still reset it there).
There was a problem hiding this comment.
Good catch, thanks! In that case stream_seek() already moved the user stream, so keeping the buffer would mix the stale buffered data with the new position.
I have changed it so php_userstreamop_seek() now discards the read buffer and clears eof/fatal_error itself when stream_seek() succeeds but stream_tell() is missing or doesn't return an int. The seek still fails, and ftell() keeps reporting the old position, since the new one is unknown.
stream_seek_failure_keeps_buffer.phpt covers it.
| stream->eof = 0; | ||
| stream->fatal_error = 0; |
There was a problem hiding this comment.
Not yet.
php_stream_write_buffer() (before a write after a read) and php_stream_cast() call ops->seek directly to move back to the logical position, without going through php_stream_seek(). In these cases, the handler's own reset is the only thing that clears eof.
The blob read handler sets eof as soon as it reaches the end of the blob, while data is still buffered. Without the reset in the handler, a write or a cast after a partial read leaves feof() returning true in the middle of the blob.
Streams whose handlers never reset eof already lose data this way on master. With a read filter, which reads ahead until eof, fread() returns "" after such a write on a plain file.
I'll fix that in a follow up PR in the stream layer, which than removes these resets from the memory, temp and SQLite seek handler.
A failed seek reset the internal position to 0 but reported -1 as the stream position, so ftell() returned false and a following SEEK_CUR tripped an assertion. Leave the position unchanged instead, like plain files. php://temp was affected too while its data is held in memory, as it forwards seeks to an inner php://memory stream. Once spilled to a temporary file it already behaved correctly. Its seek no longer reports -1 either when it has no inner stream.
A failed seek discarded the read buffer although the stream did not move. A buffered stream has usually read ahead, so a following read or write continued from where the stream had read ahead to, while ftell() still reported the old position. Return early instead, keeping the position, the read buffer and the filter state. A user stream whose stream_seek() succeeds but whose stream_tell() is missing or returns no int has moved although the seek fails, so it still discards its read buffer, and now clears the eof flag as well. The next read then continues from where stream_seek() moved it to.
A failed seek clamped the internal position to 0 or the blob size but reported -1 as the stream position, so ftell() returned false and a following SEEK_CUR tripped an assertion. Leave both positions unchanged instead, and reject a negative SEEK_SET offset explicitly rather than relying on the size_t cast.
A failed seek clamped the internal position to 0 or the blob size but reported -1 as the stream position, so ftell() returned false and a following SEEK_CUR tripped an assertion. Leave both positions unchanged instead, and reject a negative SEEK_SET offset explicitly rather than relying on the size_t cast.
bc78826 to
ad14cd9
Compare
|
@bukka I updated the PR
|
fixes #23905
This is targeting 8.6 even if this bug exists on 8.4 as well but #21433 conflicts and targets 8.6.
Please tell me if I should target PHP-8.4 instead.