Skip to content

Commit 389a844

Browse files
authored
Fix file seek errors (#1842)
1 parent a0f828f commit 389a844

File tree

4 files changed

+75
-15
lines changed

4 files changed

+75
-15
lines changed

Src/IronPython/Modules/_fileio.cs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
133133
// according to [documentation](https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream.safefilehandle?view=net-9.0#remarks)
134134
// accessing SafeFileHandle sets the current stream position to 0
135135
// in practice it doesn't seem to be the case, but better to be sure
136-
if (this.mode == "ab+") {
136+
if (this.mode.StartsWith("ab", StringComparison.InvariantCulture)) {
137137
_streams.WriteStream.Seek(0L, SeekOrigin.End);
138138
}
139139
if (!_streams.IsSingleStream) {
@@ -372,26 +372,40 @@ public override BigInteger readinto(CodeContext/*!*/ context, object buf) {
372372
return readinto(bufferProtocol);
373373
}
374374

375-
[Documentation("seek(offset: int[, whence: int]) -> None. Move to new file position.\n\n"
376-
+ "Argument offset is a byte count. Optional argument whence defaults to\n"
377-
+ "0 (offset from start of file, offset should be >= 0); other values are 1\n"
378-
+ "(move relative to current position, positive or negative), and 2 (move\n"
379-
+ "relative to end of file, usually negative, although many platforms allow\n"
380-
+ "seeking beyond the end of a file).\n"
381-
+ "Note that not all file objects are seekable."
382-
)]
375+
376+
[Documentation("""
377+
seek(offset: int[, whence: int]) -> int. Change stream position.
378+
379+
Argument offset is a byte count. Optional argument whence defaults to
380+
0 or `os.SEEK_SET` (offset from start of file, offset should be >= 0);
381+
other values are 1 or `os.SEEK_CUR` (move relative to current position,
382+
positive or negative), and 2 or `os.SEEK_END` (move relative to end of
383+
file, usually negative, although many platforms allow seeking beyond
384+
the end of a file, by adding zeros to enlarge the file).
385+
386+
Return the new absolute position.
387+
388+
Note that not all file objects are seekable.
389+
""")]
383390
public override BigInteger seek(CodeContext/*!*/ context, BigInteger offset, [Optional] object whence) {
384391
_checkClosed();
385392

386-
return _streams.ReadStream.Seek((long)offset, (SeekOrigin)GetInt(whence));
387-
}
393+
var origin = (SeekOrigin)GetInt(whence);
394+
if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
395+
throw PythonOps.OSError(PythonFileManager.EINVAL, "Invalid argument");
388396

389-
public BigInteger seek(double offset, [Optional] object whence) {
390-
_checkClosed();
397+
long ofs = checked((long)offset);
398+
if (ofs < 0 && ClrModule.IsMono && origin == SeekOrigin.Current) {
399+
// Mono does not support negative offsets with SeekOrigin.Current
400+
// so we need to calculate the absolute offset
401+
ofs += _streams.ReadStream.Position;
402+
origin = SeekOrigin.Begin;
403+
}
391404

392-
throw PythonOps.TypeError("an integer is required");
405+
return _streams.ReadStream.Seek(ofs, origin);
393406
}
394407

408+
395409
[Documentation("seekable() -> bool. True if file supports random-access.")]
396410
public override bool seekable(CodeContext/*!*/ context) {
397411
_checkClosed();

Src/IronPython/Runtime/PythonFileManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ internal class PythonFileManager {
222222
internal const int ENOENT = 2;
223223
internal const int EBADF = 9;
224224
internal const int EACCES = 13;
225+
internal const int EINVAL = 22;
225226
internal const int EMFILE = 24;
226227

227228
// *** END GENERATED CODE ***

Src/Scripts/generate_os_codes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def darwin_code_expr(codes, fmt):
102102
def linux_code_expr(codes, fmt):
103103
return fmt(codes[linux_idx])
104104

105-
common_errno_codes = ['ENOENT', 'EBADF', 'EACCES', 'EMFILE']
105+
common_errno_codes = ['ENOENT', 'EBADF', 'EACCES', 'EINVAL', 'EMFILE']
106106

107107
def generate_common_errno_codes(cw):
108108
for name in common_errno_codes:

Tests/test_file.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,51 @@ def test_opener_uncallable(self):
783783

784784
self.assertRaises(TypeError, open, "", "r", opener=uncallable_opener)
785785

786+
787+
def test_seek(self):
788+
with open(self.temp_file, "w") as f:
789+
f.write("abc")
790+
self.assertRaises(TypeError, f.buffer.seek, 1.5)
791+
if is_cli:
792+
self.assertRaises(TypeError, f.seek, 1.5) # surprisingly, this doesn't raise an error in CPython
793+
794+
with open(self.temp_file, "rb") as f:
795+
self.assertEqual(f.tell(), 0)
796+
self.assertEqual(f.read(1), b"a")
797+
self.assertEqual(f.tell(), 1)
798+
799+
f.seek(2)
800+
self.assertEqual(f.tell(), 2)
801+
self.assertEqual(f.read(1), b"c")
802+
self.assertEqual(f.tell(), 3)
803+
804+
f.seek(0, os.SEEK_SET)
805+
self.assertEqual(f.tell(), 0)
806+
self.assertEqual(f.read(1), b"a")
807+
self.assertEqual(f.tell(), 1)
808+
809+
f.seek(0, os.SEEK_CUR)
810+
self.assertEqual(f.tell(), 1)
811+
self.assertEqual(f.read(1), b"b")
812+
self.assertEqual(f.tell(), 2)
813+
814+
f.raw.seek(2, os.SEEK_SET)
815+
f.raw.seek(-2, os.SEEK_CUR)
816+
self.assertEqual(f.raw.tell(), 0)
817+
self.assertEqual(f.raw.read(1), b"a")
818+
self.assertEqual(f.raw.tell(), 1)
819+
820+
f.raw.seek(-1, os.SEEK_END)
821+
self.assertEqual(f.raw.tell(), 2)
822+
self.assertEqual(f.raw.read(1), b"c")
823+
self.assertEqual(f.raw.tell(), 3)
824+
825+
self.assertRaises(TypeError, f.seek, 1.5)
826+
self.assertRaises(TypeError, f.raw.seek, 1.5)
827+
self.assertRaises(OSError, f.raw.seek, -1)
828+
self.assertRaises(OSError, f.raw.seek, 0, -1)
829+
830+
786831
def test_open_wbplus(self):
787832
with open(self.temp_file, "wb+") as f:
788833
f.write(b"abc")

0 commit comments

Comments
 (0)