aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorPat Tullmann <pat.github@tullmann.org>2025-03-14 15:14:27 -0700
committerAlex Rønne Petersen <alex@alexrp.com>2025-03-26 02:57:23 +0100
commit2210c4c3604522ad0b07e15bc2a2d050923161c5 (patch)
treeb3a2e60974edcd3f4ffedb574ba3dfce844215ee /lib
parent1408288b95952b486c8bdce3b1e8eb6910de4cb7 (diff)
downloadzig-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.zig54
-rw-r--r--lib/std/posix.zig13
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),
}
}