Skip to content

Commit

Permalink
Fix file seek errors
Browse files Browse the repository at this point in the history
  • Loading branch information
BCSharp committed Dec 16, 2024
1 parent a0f828f commit 7353a26
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 21 deletions.
54 changes: 34 additions & 20 deletions Src/IronPython/Modules/_fileio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
_streams = new(OpenFile(context, pal, name, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite));
break;
case "ab+":
// Opening writeStream before readStream will create the file if it does not exist
var writeStream = OpenFile(context, pal, name, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
var readStream = OpenFile(context, pal, name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
readStream.Seek(0L, SeekOrigin.End);
writeStream.Seek(0L, SeekOrigin.End);
_streams = new(readStream, writeStream);
// Opening writeStream before readStream will create the file if it does not exist
var writeStream = OpenFile(context, pal, name, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
var readStream = OpenFile(context, pal, name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
readStream.Seek(0L, SeekOrigin.End);
writeStream.Seek(0L, SeekOrigin.End);
_streams = new(readStream, writeStream);
break;
default:
throw new InvalidOperationException();
Expand All @@ -133,7 +133,7 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
// according to [documentation](https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream.safefilehandle?view=net-9.0#remarks)
// accessing SafeFileHandle sets the current stream position to 0
// in practice it doesn't seem to be the case, but better to be sure
if (this.mode == "ab+") {
if (this.mode.StartsWith("ab", StringComparison.InvariantCulture)) {
_streams.WriteStream.Seek(0L, SeekOrigin.End);
}
if (!_streams.IsSingleStream) {
Expand Down Expand Up @@ -372,26 +372,40 @@ public override BigInteger readinto(CodeContext/*!*/ context, object buf) {
return readinto(bufferProtocol);
}

[Documentation("seek(offset: int[, whence: int]) -> None. Move to new file position.\n\n"
+ "Argument offset is a byte count. Optional argument whence defaults to\n"
+ "0 (offset from start of file, offset should be >= 0); other values are 1\n"
+ "(move relative to current position, positive or negative), and 2 (move\n"
+ "relative to end of file, usually negative, although many platforms allow\n"
+ "seeking beyond the end of a file).\n"
+ "Note that not all file objects are seekable."
)]

[Documentation("""
seek(offset: int[, whence: int]) -> int. Change stream position.
Argument offset is a byte count. Optional argument whence defaults to
0 or `os.SEEK_SET` (offset from start of file, offset should be >= 0);
other values are 1 or `os.SEEK_CUR` (move relative to current position,
positive or negative), and 2 or `os.SEEK_END` (move relative to end of
file, usually negative, although many platforms allow seeking beyond
the end of a file, by adding zeros to enlarge the file).
Return the new absolute position.
Note that not all file objects are seekable.
""")]
public override BigInteger seek(CodeContext/*!*/ context, BigInteger offset, [Optional] object whence) {
_checkClosed();

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

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

throw PythonOps.TypeError("an integer is required");
return _streams.ReadStream.Seek(ofs, origin);
}


[Documentation("seekable() -> bool. True if file supports random-access.")]
public override bool seekable(CodeContext/*!*/ context) {
_checkClosed();
Expand Down
1 change: 1 addition & 0 deletions Src/IronPython/Runtime/PythonFileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ internal class PythonFileManager {
internal const int ENOENT = 2;
internal const int EBADF = 9;
internal const int EACCES = 13;
internal const int EINVAL = 22;
internal const int EMFILE = 24;

// *** END GENERATED CODE ***
Expand Down
2 changes: 1 addition & 1 deletion Src/Scripts/generate_os_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def darwin_code_expr(codes, fmt):
def linux_code_expr(codes, fmt):
return fmt(codes[linux_idx])

common_errno_codes = ['ENOENT', 'EBADF', 'EACCES', 'EMFILE']
common_errno_codes = ['ENOENT', 'EBADF', 'EACCES', 'EINVAL', 'EMFILE']

def generate_common_errno_codes(cw):
for name in common_errno_codes:
Expand Down
45 changes: 45 additions & 0 deletions Tests/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,51 @@ def test_opener_uncallable(self):

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


def test_seek(self):
with open(self.temp_file, "w") as f:
f.write("abc")
self.assertRaises(TypeError, f.buffer.seek, 1.5)
if is_cli:
self.assertRaises(TypeError, f.seek, 1.5) # surprisingly, this doesn't raise an error in CPython

with open(self.temp_file, "rb") as f:
self.assertEqual(f.tell(), 0)
self.assertEqual(f.read(1), b"a")
self.assertEqual(f.tell(), 1)

f.seek(2)
self.assertEqual(f.tell(), 2)
self.assertEqual(f.read(1), b"c")
self.assertEqual(f.tell(), 3)

f.seek(0, os.SEEK_SET)
self.assertEqual(f.tell(), 0)
self.assertEqual(f.read(1), b"a")
self.assertEqual(f.tell(), 1)

f.seek(0, os.SEEK_CUR)
self.assertEqual(f.tell(), 1)
self.assertEqual(f.read(1), b"b")
self.assertEqual(f.tell(), 2)

f.raw.seek(2, os.SEEK_SET)
f.raw.seek(-2, os.SEEK_CUR)
self.assertEqual(f.raw.tell(), 0)
self.assertEqual(f.raw.read(1), b"a")
self.assertEqual(f.raw.tell(), 1)

f.raw.seek(-1, os.SEEK_END)
self.assertEqual(f.raw.tell(), 2)
self.assertEqual(f.raw.read(1), b"c")
self.assertEqual(f.raw.tell(), 3)

self.assertRaises(TypeError, f.seek, 1.5)
self.assertRaises(TypeError, f.raw.seek, 1.5)
self.assertRaises(OSError, f.raw.seek, -1)
self.assertRaises(OSError, f.raw.seek, 0, -1)


def test_open_wbplus(self):
with open(self.temp_file, "wb+") as f:
f.write(b"abc")
Expand Down

0 comments on commit 7353a26

Please sign in to comment.