| author | |
| committer | |
| log | 2210c4c3604522ad0b07e15bc2a2d050923161c5 |
| tree | b3a2e60974edcd3f4ffedb574ba3dfce844215ee |
| parent | 1408288b95952b486c8bdce3b1e8eb6910de4cb7 |
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 #229602 files changed, 63 insertions(+), 4 deletions(-)
lib/std/fs/test.zig+54| ... | ... | @@ -1391,6 +1391,60 @@ test "pwritev, preadv" { |
| 1391 | 1391 | try testing.expectEqualStrings(&buf2, "line1\n"); |
| 1392 | 1392 | } |
| 1393 | 1393 | |
| 1394 | test "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 | ||
| 1394 | 1448 | test "access file" { |
| 1395 | 1449 | try testWithAllSupportedPathTypes(struct { |
| 1396 | 1450 | fn impl(ctx: *TestContext) !void { |
lib/std/posix.zig+9-4| ... | ... | @@ -1037,11 +1037,15 @@ pub const TruncateError = error{ |
| 1037 | 1037 | PermissionDenied, |
| 1038 | 1038 | } || UnexpectedError; |
| 1039 | 1039 | |
| 1040 | /// Length must be positive when treated as an i64. | |
| 1040 | 1041 | pub 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 | ||
| 1041 | 1045 | if (native_os == .windows) { |
| 1042 | 1046 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 1043 | 1047 | var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ |
| 1044 | .EndOfFile = @bitCast(length), | |
| 1048 | .EndOfFile = signed_len, | |
| 1045 | 1049 | }; |
| 1046 | 1050 | |
| 1047 | 1051 | const rc = windows.ntdll.NtSetInformationFile( |
| ... | ... | @@ -1057,6 +1061,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { |
| 1057 | 1061 | .INVALID_HANDLE => unreachable, // Handle not open for writing |
| 1058 | 1062 | .ACCESS_DENIED => return error.AccessDenied, |
| 1059 | 1063 | .USER_MAPPED_FILE => return error.AccessDenied, |
| 1064 | .INVALID_PARAMETER => return error.FileTooBig, | |
| 1060 | 1065 | else => return windows.unexpectedStatus(rc), |
| 1061 | 1066 | } |
| 1062 | 1067 | } |
| ... | ... | @@ -1069,7 +1074,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { |
| 1069 | 1074 | .PERM => return error.PermissionDenied, |
| 1070 | 1075 | .TXTBSY => return error.FileBusy, |
| 1071 | 1076 | .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 | |
| 1073 | 1078 | .NOTCAPABLE => return error.AccessDenied, |
| 1074 | 1079 | else => |err| return unexpectedErrno(err), |
| 1075 | 1080 | } |
| ... | ... | @@ -1077,7 +1082,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { |
| 1077 | 1082 | |
| 1078 | 1083 | const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; |
| 1079 | 1084 | while (true) { |
| 1080 | switch (errno(ftruncate_sym(fd, @bitCast(length)))) { | |
| 1085 | switch (errno(ftruncate_sym(fd, signed_len))) { | |
| 1081 | 1086 | .SUCCESS => return, |
| 1082 | 1087 | .INTR => continue, |
| 1083 | 1088 | .FBIG => return error.FileTooBig, |
| ... | ... | @@ -1085,7 +1090,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { |
| 1085 | 1090 | .PERM => return error.PermissionDenied, |
| 1086 | 1091 | .TXTBSY => return error.FileBusy, |
| 1087 | 1092 | .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 | |
| 1089 | 1094 | else => |err| return unexpectedErrno(err), |
| 1090 | 1095 | } |
| 1091 | 1096 | } |