diff options
| author | Pat Tullmann <pat.github@tullmann.org> | 2025-03-14 15:14:27 -0700 |
|---|---|---|
| committer | Alex Rønne Petersen <alex@alexrp.com> | 2025-03-26 02:57:23 +0100 |
| commit | 2210c4c3604522ad0b07e15bc2a2d050923161c5 (patch) | |
| tree | b3a2e60974edcd3f4ffedb574ba3dfce844215ee /lib | |
| parent | 1408288b95952b486c8bdce3b1e8eb6910de4cb7 (diff) | |
| download | zig-2210c4c3604522ad0b07e15bc2a2d050923161c5.tar.gz zig-2210c4c3604522ad0b07e15bc2a2d050923161c5.zip | |
lib/std/posix: test ftruncate via std.fs.File.setEndPos()
Add a test for std.fs.File's `setEndPos` (which is a simple wrapper around
`std.posix.ftruncate`) to exercise some success and failure paths.
Explicitly check that the `ftruncate` length isn't negative when
interpreted as a signed value. This avoids having to decode overloaded
`EINVAL` errors.
Add errno handling to Windows path to map INVALID_PARAMETER to FileTooBig.
Fixes #22960
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/std/fs/test.zig | 54 | ||||
| -rw-r--r-- | lib/std/posix.zig | 13 |
2 files changed, 63 insertions, 4 deletions
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 093c05fe33..dcc817dc60 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1391,6 +1391,60 @@ test "pwritev, preadv" { try testing.expectEqualStrings(&buf2, "line1\n"); } +test "setEndPos" { + // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission) + if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const file_name = "afile.txt"; + try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" }); + const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write }); + defer f.close(); + + const initial_size = try f.getEndPos(); + var buffer: [32]u8 = undefined; + + { + try f.setEndPos(initial_size); + try testing.expectEqual(initial_size, try f.getEndPos()); + try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0)); + try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]); + } + + { + const larger = initial_size + 4; + try f.setEndPos(larger); + try testing.expectEqual(larger, try f.getEndPos()); + try testing.expectEqual(larger, try f.preadAll(&buffer, 0)); + try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]); + } + + { + const smaller = initial_size - 5; + try f.setEndPos(smaller); + try testing.expectEqual(smaller, try f.getEndPos()); + try testing.expectEqual(smaller, try f.preadAll(&buffer, 0)); + try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]); + } + + try f.setEndPos(0); + try testing.expectEqual(0, try f.getEndPos()); + try testing.expectEqual(0, try f.preadAll(&buffer, 0)); + + // Invalid file length should error gracefully. Actual limit is host + // and file-system dependent, but 1PB should fail most everywhere. + // Except MacOS APFS limit is 8 exabytes. + f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) { + return err; + }; + + try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63))); // Maximum signed value + + try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64))); +} + test "access file" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 7c243f7b01..824ef119a4 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1037,11 +1037,15 @@ pub const TruncateError = error{ PermissionDenied, } || UnexpectedError; +/// Length must be positive when treated as an i64. pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { + const signed_len: i64 = @bitCast(length); + if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors + if (native_os == .windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ - .EndOfFile = @bitCast(length), + .EndOfFile = signed_len, }; const rc = windows.ntdll.NtSetInformationFile( @@ -1057,6 +1061,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { .INVALID_HANDLE => unreachable, // Handle not open for writing .ACCESS_DENIED => return error.AccessDenied, .USER_MAPPED_FILE => return error.AccessDenied, + .INVALID_PARAMETER => return error.FileTooBig, else => return windows.unexpectedStatus(rc), } } @@ -1069,7 +1074,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { .PERM => return error.PermissionDenied, .TXTBSY => return error.FileBusy, .BADF => unreachable, // Handle not open for writing - .INVAL => unreachable, // Handle not open for writing + .INVAL => unreachable, // Handle not open for writing, negative length, or non-resizable handle .NOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } @@ -1077,7 +1082,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; while (true) { - switch (errno(ftruncate_sym(fd, @bitCast(length)))) { + switch (errno(ftruncate_sym(fd, signed_len))) { .SUCCESS => return, .INTR => continue, .FBIG => return error.FileTooBig, @@ -1085,7 +1090,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { .PERM => return error.PermissionDenied, .TXTBSY => return error.FileBusy, .BADF => unreachable, // Handle not open for writing - .INVAL => unreachable, // Handle not open for writing + .INVAL => unreachable, // Handle not open for writing, negative length, or non-resizable handle else => |err| return unexpectedErrno(err), } } |
