Skip to content

Commit 402b359

Browse files
committed
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 ziglang#22960
1 parent ea57fb5 commit 402b359

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

lib/std/fs/test.zig

+54
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,60 @@ test "pwritev, preadv" {
13911391
try testing.expectEqualStrings(&buf2, "line1\n");
13921392
}
13931393

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+
13941448
test "access file" {
13951449
try testWithAllSupportedPathTypes(struct {
13961450
fn impl(ctx: *TestContext) !void {

lib/std/posix.zig

+11-4
Original file line numberDiff line numberDiff line change
@@ -1037,13 +1037,19 @@ pub const TruncateError = error{
10371037
AccessDenied,
10381038
} || UnexpectedError;
10391039

1040+
/// Length must be positive when treated as an i64.
10401041
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;
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

1051+
if (eof_info.EndOfFile < 0) return error.FileTooBig;
1052+
10471053
const rc = windows.ntdll.NtSetInformationFile(
10481054
fd,
10491055
&io_status_block,
@@ -1057,6 +1063,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
10571063
.INVALID_HANDLE => unreachable, // Handle not open for writing
10581064
.ACCESS_DENIED => return error.AccessDenied,
10591065
.USER_MAPPED_FILE => return error.AccessDenied,
1066+
.INVALID_PARAMETER => return error.FileTooBig,
10601067
else => return windows.unexpectedStatus(rc),
10611068
}
10621069
}
@@ -1069,23 +1076,23 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
10691076
.PERM => return error.AccessDenied,
10701077
.TXTBSY => return error.FileBusy,
10711078
.BADF => unreachable, // Handle not open for writing
1072-
.INVAL => unreachable, // Handle not open for writing
1079+
.INVAL => unreachable, // Handle not open for writing, negative length, or non-resizable handle
10731080
.NOTCAPABLE => return error.AccessDenied,
10741081
else => |err| return unexpectedErrno(err),
10751082
}
10761083
}
10771084

10781085
const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate;
10791086
while (true) {
1080-
switch (errno(ftruncate_sym(fd, @bitCast(length)))) {
1087+
switch (errno(ftruncate_sym(fd, signed_len))) {
10811088
.SUCCESS => return,
10821089
.INTR => continue,
10831090
.FBIG => return error.FileTooBig,
10841091
.IO => return error.InputOutput,
10851092
.PERM => return error.AccessDenied,
10861093
.TXTBSY => return error.FileBusy,
10871094
.BADF => unreachable, // Handle not open for writing
1088-
.INVAL => unreachable, // Handle not open for writing
1095+
.INVAL => unreachable, // Handle not open for writing, negative length, or non-resizable handle
10891096
else => |err| return unexpectedErrno(err),
10901097
}
10911098
}

0 commit comments

Comments
 (0)