Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Lib/test/test_io/test_fileio.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ def testSeekTell(self):
self.f.seek(-5, 2)
self.assertEqual(self.f.tell(), 15)

def testSeekableAfterFailedSeek(self) -> None:
self.assertRaises(OSError, self.f.seek, -1)
self.assertTrue(self.f.seekable())

def testAttributes(self):
# verify expected attributes exist
f = self.f
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :class:`io.FileIO` ``seekable`` being cached as false because of an invalid
argument to :meth:`!io.FileIO.seek`
2 changes: 1 addition & 1 deletion Modules/_io/fileio.c
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_er
_Py_END_SUPPRESS_IPH
Py_END_ALLOW_THREADS

if (self->seekable < 0) {
if (self->seekable < 0 && (res >= 0 || errno == ESPIPE || posobj == NULL)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure across the set of OS + kernel + version which Python runs on this is correct... Can work on evaluating that thoroughly but my leaning would be just making it so specific returns we know about that indicate "bad argument" pass through.

For instance EBADF should probably still mark seekable as False / I could see code relying on that. Something like: EINVAL, ENXIO, EOVERFLOW for Linux; EBADF and EINVAL for OpenBSD.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here was that only in the following cases we're 100% sure we know for sure what seekable should be:

  • res >= 0 i.e. it succeeded
  • errno == ESPIPE i.e. Illegal seek
  • posobj == NULL i.e. offset == 0 in which case a failure would indicate a seek for SEEK_CUR/SEEK_END failed (so the problem is the file itself) => it's OK to cache the result of seeking directly

If we don't set the cache, "someone else" hopefully will later, but at least we're sure we never store a false value here.

Does that sound reasonable?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Thinking, will get back later this week)

self->seekable = (res >= 0);
}

Expand Down
Loading