From 89d7fc773d66609a8d93106f9a6d84d479771690 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 11 Mar 2020 20:03:36 +0100 Subject: [PATCH 1/4] std: Fix pwrite invocation on 32bit architectures --- lib/std/os/linux.zig | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 719e541846..79f9ba9c95 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -350,7 +350,13 @@ pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: u64) usize { ); } } else { - return syscall4(SYS_pread, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset); + return syscall4( + SYS_pread, + @bitCast(usize, @as(isize, fd)), + @ptrToInt(buf), + count, + offset, + ); } } @@ -385,7 +391,36 @@ pub fn write(fd: i32, buf: [*]const u8, count: usize) usize { } pub fn pwrite(fd: i32, buf: [*]const u8, count: usize, offset: usize) usize { - return syscall4(SYS_pwrite, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset); + if (@hasDecl(@This(), "SYS_pwrite64")) { + if (require_aligned_register_pair) { + return syscall6( + SYS_pwrite64, + @bitCast(usize, @as(isize, fd)), + @ptrToInt(buf), + count, + 0, + @truncate(usize, offset), + @truncate(usize, offset >> 32), + ); + } else { + return syscall5( + SYS_pwrite64, + @bitCast(usize, @as(isize, fd)), + @ptrToInt(buf), + count, + @truncate(usize, offset), + @truncate(usize, offset >> 32), + ); + } + } else { + return syscall4( + SYS_pwrite, + @bitCast(usize, @as(isize, fd)), + @ptrToInt(buf), + count, + offset, + ); + } } pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize { From 11df0d0cf8c786d5b502c21a42d47631657a42f4 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 11 Mar 2020 20:56:43 +0100 Subject: [PATCH 2/4] std: Add setEndPos to fs.file Allow the user to shrink/grow the file size as needed. --- lib/std/c.zig | 1 + lib/std/c/linux.zig | 2 ++ lib/std/fs/file.zig | 8 ++++++++ lib/std/io/test.zig | 17 +++++++++++++++++ lib/std/os.zig | 33 +++++++++++++++++++++++++++++++++ lib/std/os/linux.zig | 27 +++++++++++++++++++++++++++ lib/std/os/windows/kernel32.zig | 1 + 7 files changed, 89 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 7c01908540..39a865ebbc 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 7ac5ecd3fe..1da0db57d6 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 8362f94ca5..8bb377c99f 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 38dd2bb67a..9615377070 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 76a5dc2be5..62347c2ee9 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 79f9ba9c95..ba7356d62c 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 05a7a8f6a3..2e5214eb20 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, From bd0b51477a6cb0dc7ea7739f61a70110d39ba601 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 12 Mar 2020 19:40:42 +0100 Subject: [PATCH 3/4] Address review comments --- lib/std/fs/file.zig | 2 +- lib/std/os.zig | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 8bb377c99f..c3809cd510 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -104,7 +104,7 @@ pub const File = struct { /// 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); + try os.ftruncate(self.handle, length); } pub const SeekError = os.SeekError; diff --git a/lib/std/os.zig b/lib/std/os.zig index 62347c2ee9..6614453a38 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -439,11 +439,13 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { } pub const TruncateError = error{ - /// The file descriptor is not open for writing. - NotFile, + FileTooBig, + InputOutput, + CannotTruncate, + FileBusy, } || UnexpectedError; -pub fn truncate(fd: fd_t, length: u64) TruncateError!void { +pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { if (std.Target.current.os.tag == .windows) { try windows.SetFilePointerEx_BEGIN(fd, length); @@ -454,18 +456,22 @@ pub fn truncate(fd: fd_t, length: u64) TruncateError!void { } while (true) { - const rc = if (builtin.link_libc) blk: { + const rc = if (builtin.link_libc) if (std.Target.current.os.tag == .linux) - break :blk system.ftruncate64(fd, @bitCast(off_t, length)) + system.ftruncate64(fd, @bitCast(off_t, length)) else - break :blk system.ftruncate(fd, @bitCast(off_t, length)); - } else + system.ftruncate(fd, @bitCast(off_t, length)) + else system.ftruncate(fd, length); switch (errno(rc)) { 0 => return, EINTR => continue, - EBADF, EINVAL => return error.NotFile, + EFBIG => return error.FileTooBig, + EIO => return error.InputOutput, + EPERM => return error.CannotTruncate, + ETXTBSY => return error.FileBusy, + EBADF, EINVAL => unreachable, else => |err| return unexpectedErrno(err), } } From de53537f10e22cc003cfbc77468349b758470f24 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 12 Mar 2020 22:46:12 +0100 Subject: [PATCH 4/4] Add NtDll-based ftruncate implementation --- lib/std/fs/file.zig | 2 +- lib/std/io/test.zig | 10 ++++++++-- lib/std/os.zig | 24 ++++++++++++++++++++---- lib/std/os/windows/bits.zig | 4 ++++ lib/std/os/windows/ntdll.zig | 7 +++++++ 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index c3809cd510..c4991a81ff 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -102,7 +102,7 @@ pub const File = struct { pub const SetEndPosError = os.TruncateError; /// Shrinks or expands the file. - /// The file offset after this call is undefined. + /// The file offset after this call is left unchanged. pub fn setEndPos(self: File, length: u64) SetEndPosError!void { try os.ftruncate(self.handle, length); } diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 9615377070..d8ee9612ea 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); -const std = @import("../std.zig"); +const std = @import("std"); +const builtin = std.builtin; const io = std.io; const meta = std.meta; const trait = std.trait; @@ -133,13 +133,19 @@ test "setEndPos" { fs.cwd().deleteFile(tmp_file_name) catch {}; } + // Verify that the file size changes and the file offset is not moved std.testing.expect((try file.getEndPos()) == 0); + std.testing.expect((try file.getPos()) == 0); try file.setEndPos(8192); std.testing.expect((try file.getEndPos()) == 8192); + std.testing.expect((try file.getPos()) == 0); + try file.seekTo(100); try file.setEndPos(4096); std.testing.expect((try file.getEndPos()) == 4096); + std.testing.expect((try file.getPos()) == 100); try file.setEndPos(0); std.testing.expect((try file.getEndPos()) == 0); + std.testing.expect((try file.getPos()) == 100); } test "updateTimes" { diff --git a/lib/std/os.zig b/lib/std/os.zig index 6614453a38..2c18a13250 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -447,10 +447,25 @@ pub const TruncateError = error{ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { if (std.Target.current.os.tag == .windows) { - try windows.SetFilePointerEx_BEGIN(fd, length); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var eof_info = windows.FILE_END_OF_FILE_INFORMATION{ + .EndOfFile = @bitCast(windows.LARGE_INTEGER, length), + }; - if (windows.kernel32.SetEndOfFile(fd) == 0) - return TruncateError.Unexpected; + const rc = windows.ntdll.NtSetInformationFile( + fd, + &io_status_block, + &eof_info, + @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), + .FileEndOfFileInformation, + ); + + switch (rc) { + .SUCCESS => {}, + .INVALID_HANDLE => unreachable, // Handle not open for writing + .ACCESS_DENIED => return error.CannotTruncate, + else => return windows.unexpectedStatus(rc), + } return; } @@ -471,7 +486,8 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { EIO => return error.InputOutput, EPERM => return error.CannotTruncate, ETXTBSY => return error.FileBusy, - EBADF, EINVAL => unreachable, + EBADF => unreachable, // Handle not open for writing + EINVAL => unreachable, // Handle not open for writing else => |err| return unexpectedErrno(err), } } diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 41466234ed..1e49b903cb 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -225,6 +225,10 @@ pub const FILE_POSITION_INFORMATION = extern struct { CurrentByteOffset: LARGE_INTEGER, }; +pub const FILE_END_OF_FILE_INFORMATION = extern struct { + EndOfFile: LARGE_INTEGER, +}; + pub const FILE_MODE_INFORMATION = extern struct { Mode: ULONG, }; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 49e60803bc..7ba58b2392 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -16,6 +16,13 @@ pub extern "NtDll" fn NtQueryInformationFile( Length: ULONG, FileInformationClass: FILE_INFORMATION_CLASS, ) callconv(.Stdcall) NTSTATUS; +pub extern "NtDll" fn NtSetInformationFile( + FileHandle: HANDLE, + IoStatusBlock: *IO_STATUS_BLOCK, + FileInformation: PVOID, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, +) callconv(.Stdcall) NTSTATUS; pub extern "NtDll" fn NtQueryAttributesFile( ObjectAttributes: *OBJECT_ATTRIBUTES,