authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2025-03-14 15:14:27-07:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-03-26 02:57:23+01:00
log2210c4c3604522ad0b07e15bc2a2d050923161c5
treeb3a2e60974edcd3f4ffedb574ba3dfce844215ee
parent1408288b95952b486c8bdce3b1e8eb6910de4cb7

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

2 files changed, 63 insertions(+), 4 deletions(-)

lib/std/fs/test.zig+54
......@@ -1391,6 +1391,60 @@ test "pwritev, preadv" {
13911391 try testing.expectEqualStrings(&buf2, "line1\n");
13921392}
13931393
1394test "setEndPos" {
1395 // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
1396 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1397
1398 var tmp = tmpDir(.{});
1399 defer tmp.cleanup();
1400
1401 const file_name = "afile.txt";
1402 try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" });
1403 const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write });
1404 defer f.close();
1405
1406 const initial_size = try f.getEndPos();
1407 var buffer: [32]u8 = undefined;
1408
1409 {
1410 try f.setEndPos(initial_size);
1411 try testing.expectEqual(initial_size, try f.getEndPos());
1412 try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0));
1413 try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
1414 }
1415
1416 {
1417 const larger = initial_size + 4;
1418 try f.setEndPos(larger);
1419 try testing.expectEqual(larger, try f.getEndPos());
1420 try testing.expectEqual(larger, try f.preadAll(&buffer, 0));
1421 try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
1422 }
1423
1424 {
1425 const smaller = initial_size - 5;
1426 try f.setEndPos(smaller);
1427 try testing.expectEqual(smaller, try f.getEndPos());
1428 try testing.expectEqual(smaller, try f.preadAll(&buffer, 0));
1429 try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
1430 }
1431
1432 try f.setEndPos(0);
1433 try testing.expectEqual(0, try f.getEndPos());
1434 try testing.expectEqual(0, try f.preadAll(&buffer, 0));
1435
1436 // Invalid file length should error gracefully. Actual limit is host
1437 // and file-system dependent, but 1PB should fail most everywhere.
1438 // Except MacOS APFS limit is 8 exabytes.
1439 f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) {
1440 return err;
1441 };
1442
1443 try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63))); // Maximum signed value
1444
1445 try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64)));
1446}
1447
13941448test "access file" {
13951449 try testWithAllSupportedPathTypes(struct {
13961450 fn impl(ctx: *TestContext) !void {
lib/std/posix.zig+9-4
......@@ -1037,11 +1037,15 @@ pub const TruncateError = error{
10371037 PermissionDenied,
10381038} || UnexpectedError;
10391039
1040/// Length must be positive when treated as an i64.
10401041pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
1042 const signed_len: i64 = @bitCast(length);
1043 if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors
1044
10411045 if (native_os == .windows) {
10421046 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
10431047 var eof_info = windows.FILE_END_OF_FILE_INFORMATION{
1044 .EndOfFile = @bitCast(length),
1048 .EndOfFile = signed_len,
10451049 };
10461050
10471051 const rc = windows.ntdll.NtSetInformationFile(
......@@ -1057,6 +1061,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
10571061 .INVALID_HANDLE => unreachable, // Handle not open for writing
10581062 .ACCESS_DENIED => return error.AccessDenied,
10591063 .USER_MAPPED_FILE => return error.AccessDenied,
1064 .INVALID_PARAMETER => return error.FileTooBig,
10601065 else => return windows.unexpectedStatus(rc),
10611066 }
10621067 }
......@@ -1069,7 +1074,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
10691074 .PERM => return error.PermissionDenied,
10701075 .TXTBSY => return error.FileBusy,
10711076 .BADF => unreachable, // Handle not open for writing
1072 .INVAL => unreachable, // Handle not open for writing
1077 .INVAL => unreachable, // Handle not open for writing, negative length, or non-resizable handle
10731078 .NOTCAPABLE => return error.AccessDenied,
10741079 else => |err| return unexpectedErrno(err),
10751080 }
......@@ -1077,7 +1082,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
10771082
10781083 const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate;
10791084 while (true) {
1080 switch (errno(ftruncate_sym(fd, @bitCast(length)))) {
1085 switch (errno(ftruncate_sym(fd, signed_len))) {
10811086 .SUCCESS => return,
10821087 .INTR => continue,
10831088 .FBIG => return error.FileTooBig,
......@@ -1085,7 +1090,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
10851090 .PERM => return error.PermissionDenied,
10861091 .TXTBSY => return error.FileBusy,
10871092 .BADF => unreachable, // Handle not open for writing
1088 .INVAL => unreachable, // Handle not open for writing
1093 .INVAL => unreachable, // Handle not open for writing, negative length, or non-resizable handle
10891094 else => |err| return unexpectedErrno(err),
10901095 }
10911096 }