Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/std/posix: test ftruncate via std.fs.File.setEndPos() #23260

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions lib/std/fs/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 9 additions & 4 deletions lib/std/posix.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1037,11 +1037,15 @@ pub const TruncateError = error{
AccessDenied,
} || 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(
Expand All @@ -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),
}
}
Expand All @@ -1069,23 +1074,23 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
.PERM => return error.AccessDenied,
.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),
}
}

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,
.IO => return error.InputOutput,
.PERM => return error.AccessDenied,
.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),
}
}
Expand Down
Loading