diff --git a/lib/std/c.zig b/lib/std/c.zig index 7c0190854..39a865ebb 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -79,6 +79,7 @@ pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, fla pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t; pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int; pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: c_uint, ...) c_int; +pub extern "c" fn ftruncate(fd: c_int, length: off_t) c_int; pub extern "c" fn raise(sig: c_int) c_int; pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize; pub extern "c" fn readv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint) isize; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index 7ac5ecd3f..1da0db57d 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -82,6 +82,8 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int; +pub extern "c" fn ftruncate64(fd: c_int, length: off_t) c_int; + pub extern "c" fn sendfile( out_fd: fd_t, in_fd: fd_t, diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 8362f94ca..8bb377c99 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -99,6 +99,14 @@ pub const File = struct { return false; } + pub const SetEndPosError = os.TruncateError; + + /// Shrinks or expands the file. + /// The file offset after this call is undefined. + pub fn setEndPos(self: File, length: u64) SetEndPosError!void { + try os.truncate(self.handle, length); + } + pub const SeekError = os.SeekError; /// Repositions read/write file offset relative to the current offset. diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 38dd2bb67..961537707 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -125,6 +125,23 @@ test "File seek ops" { expect((try file.getPos()) == 1234); } +test "setEndPos" { + const tmp_file_name = "temp_test_file.txt"; + var file = try fs.cwd().createFile(tmp_file_name, .{}); + defer { + file.close(); + fs.cwd().deleteFile(tmp_file_name) catch {}; + } + + std.testing.expect((try file.getEndPos()) == 0); + try file.setEndPos(8192); + std.testing.expect((try file.getEndPos()) == 8192); + try file.setEndPos(4096); + std.testing.expect((try file.getEndPos()) == 4096); + try file.setEndPos(0); + std.testing.expect((try file.getEndPos()) == 0); +} + test "updateTimes" { const tmp_file_name = "just_a_temporary_file.txt"; var file = try fs.cwd().createFile(tmp_file_name, .{ .read = true }); diff --git a/lib/std/os.zig b/lib/std/os.zig index 76a5dc2be..62347c2ee 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -438,6 +438,39 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { return index; } +pub const TruncateError = error{ + /// The file descriptor is not open for writing. + NotFile, +} || UnexpectedError; + +pub fn truncate(fd: fd_t, length: u64) TruncateError!void { + if (std.Target.current.os.tag == .windows) { + try windows.SetFilePointerEx_BEGIN(fd, length); + + if (windows.kernel32.SetEndOfFile(fd) == 0) + return TruncateError.Unexpected; + + return; + } + + while (true) { + const rc = if (builtin.link_libc) blk: { + if (std.Target.current.os.tag == .linux) + break :blk system.ftruncate64(fd, @bitCast(off_t, length)) + else + break :blk system.ftruncate(fd, @bitCast(off_t, length)); + } else + system.ftruncate(fd, length); + + switch (errno(rc)) { + 0 => return, + EINTR => continue, + EBADF, EINVAL => return error.NotFile, + else => |err| return unexpectedErrno(err), + } + } +} + /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. /// /// Retries when interrupted by a signal. diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 79f9ba9c9..ba7356d62 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -390,6 +390,33 @@ pub fn write(fd: i32, buf: [*]const u8, count: usize) usize { return syscall3(SYS_write, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count); } +pub fn ftruncate(fd: i32, length: u64) usize { + if (@hasDecl(@This(), "SYS_ftruncate64")) { + if (require_aligned_register_pair) { + return syscall4( + SYS_ftruncate64, + @bitCast(usize, @as(isize, fd)), + 0, + @truncate(usize, length), + @truncate(usize, length >> 32), + ); + } else { + return syscall3( + SYS_ftruncate64, + @bitCast(usize, @as(isize, fd)), + @truncate(usize, length), + @truncate(usize, length >> 32), + ); + } + } else { + return syscall2( + SYS_ftruncate, + @bitCast(usize, @as(isize, fd)), + @truncate(usize, length), + ); + } +} + pub fn pwrite(fd: i32, buf: [*]const u8, count: usize, offset: usize) usize { if (@hasDecl(@This(), "SYS_pwrite64")) { if (require_aligned_register_pair) { diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 05a7a8f6a..2e5214eb2 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -8,6 +8,7 @@ pub extern "kernel32" fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) c pub extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(.Stdcall) BOOL; pub extern "kernel32" fn CreateDirectoryW(lpPathName: [*:0]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) callconv(.Stdcall) BOOL; +pub extern "kernel32" fn SetEndOfFile(hFile: HANDLE) callconv(.Stdcall) BOOL; pub extern "kernel32" fn CreateEventExW( lpEventAttributes: ?*SECURITY_ATTRIBUTES,