From be78b7b648fdda7e0b052aa96eb4551edc64ef18 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jun 2020 09:59:05 +0200 Subject: [PATCH 1/3] Implement fstatat targeting WASI Also, add more informative `@compileError` in a few `std.os` functions that would otherwise yield a cryptic compile error when targeting WASI. Finally, enhance docs in a few places and add test case for `fstatat`. --- lib/std/os.zig | 92 ++++++++++++++++++++++++++++++++++----------- lib/std/os/test.zig | 22 +++++++++++ 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 59cde5d606..aa85f9483b 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1665,13 +1665,15 @@ pub const UnlinkError = error{ /// Delete a name and possibly the file it refers to. /// See also `unlinkC`. pub fn unlink(file_path: []const u8) UnlinkError!void { + if (builtin.os.tag == .wasi) { + @compileError("unlink is not supported in WASI; use unlinkat instead"); + } if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return windows.DeleteFileW(file_path_w.span().ptr); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkZ(&file_path_c); } + const file_path_c = try toPosixPath(file_path); + return unlinkZ(&file_path_c); } pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); @@ -1722,6 +1724,8 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ"); +/// WASI-only. Same as `unlinkat` but targeting WASI. +/// See also `unlinkat`. pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { const remove_dir = (flags & AT_REMOVEDIR) != 0; const res = if (remove_dir) @@ -1868,15 +1872,17 @@ const RenameError = error{ /// Change the name or location of a file. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { + if (builtin.os.tag == .wasi) { + @compileError("rename is not supported in WASI; use renameat instead"); + } if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameZ(&old_path_c, &new_path_c); } + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameZ(&old_path_c, &new_path_c); } pub const renameC = @compileError("deprecated: renamed to renameZ"); @@ -1939,7 +1945,8 @@ pub fn renameat( } } -/// Same as `renameat` expect only WASI. +/// WASI-only. Same as `renameat` expect targeting WASI. +/// See also `renameat`. pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { wasi.ESUCCESS => return, @@ -2144,14 +2151,16 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { + if (builtin.os.tag == .wasi) { + @compileError("mkdir is not supported in WASI; use mkdirat instead"); + } if (builtin.os.tag == .windows) { const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); windows.CloseHandle(sub_dir_handle); return; - } else { - const dir_path_c = try toPosixPath(dir_path); - return mkdirZ(&dir_path_c, mode); } + const dir_path_c = try toPosixPath(dir_path); + return mkdirZ(&dir_path_c, mode); } /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. @@ -2197,13 +2206,15 @@ pub const DeleteDirError = error{ /// Deletes an empty directory. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { + if (builtin.os.tag == .wasi) { + @compileError("rmdir is not supported in WASI; use unlinkat instead"); + } if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(dir_path_w.span().ptr); - } else { - const dir_path_c = try toPosixPath(dir_path); - return rmdirZ(&dir_path_c); } + const dir_path_c = try toPosixPath(dir_path); + return rmdirZ(&dir_path_c); } pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); @@ -2246,13 +2257,15 @@ pub const ChangeCurDirError = error{ /// Changes the current working directory of the calling process. /// `dir_path` is recommended to be a UTF-8 encoded string. pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { + if (builtin.os.tag == .wasi) { + @compileError("chdir is not supported in WASI"); + } if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); @compileError("TODO implement chdir for Windows"); - } else { - const dir_path_c = try toPosixPath(dir_path); - return chdirZ(&dir_path_c); } + const dir_path_c = try toPosixPath(dir_path); + return chdirZ(&dir_path_c); } pub const chdirC = @compileError("deprecated: renamed to chdirZ"); @@ -2310,22 +2323,30 @@ pub const ReadLinkError = error{ /// Read value of a symbolic link. /// The return value is a slice of `out_buffer` from index 0. pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (builtin.os.tag == .wasi) { + @compileError("readlink is not supported in WASI; use readlinkat instead"); + } if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); - @compileError("TODO implement readlink for Windows"); - } else { - const file_path_c = try toPosixPath(file_path); - return readlinkZ(&file_path_c, out_buffer); + return readlinkW(file_path_w.span().ptr, out_buffer); } + const file_path_c = try toPosixPath(file_path); + return readlinkZ(&file_path_c, out_buffer); } pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); +/// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded. +/// Seel also `readlinkZ`. +pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { + @compileError("TODO implement readlink for Windows"); +} + /// Same as `readlink` except `file_path` is null-terminated. pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); - @compileError("TODO implement readlink for Windows"); + return readlinkW(file_path_w.span().ptr, out_buffer); } const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); switch (errno(rc)) { @@ -3055,6 +3076,7 @@ pub const FStatError = error{ AccessDenied, } || UnexpectedError; +/// Return information about a file descriptor. pub fn fstat(fd: fd_t) FStatError!Stat { if (builtin.os.tag == .wasi) { var stat: wasi.filestat_t = undefined; @@ -3081,13 +3103,39 @@ pub fn fstat(fd: fd_t) FStatError!Stat { pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound }; +/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname` +/// which is relative to `dirfd` handle. +/// See also `fstatatZ` and `fstatatWasi`. pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { + if (builtin.os.tag == .wasi) { + return fstatatWasi(dirfd, pathname, flags); + } const pathname_c = try toPosixPath(pathname); return fstatatZ(dirfd, &pathname_c, flags); } pub const fstatatC = @compileError("deprecated: renamed to fstatatZ"); +/// WASI-only. Same as `fstatat` but targeting WASI. +/// See also `fstatat`. +pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { + var stat: wasi.filestat_t = undefined; + switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) { + wasi.ESUCCESS => return Stat.fromFilestat(stat), + wasi.EINVAL => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.ENOMEM => return error.SystemResources, + wasi.EACCES => return error.AccessDenied, + wasi.EFAULT => unreachable, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOTDIR => return error.FileNotFound, + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `fstatat` but `pathname` is null-terminated. +/// See also `fstatat`. pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { var stat: Stat = undefined; switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index c298a0a203..34747518e4 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -18,6 +18,28 @@ const AtomicOrder = builtin.AtomicOrder; const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; +test "fstatat" { + // enable when `fstat` and `fstatat` are implemented on Windows + if (builtin.os.tag == .windows) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create dummy file + const contents = "nonsense"; + try tmp.dir.writeFile("file.txt", contents); + + // fetch file's info on the opened fd directly + const file = try tmp.dir.openFile("file.txt", .{}); + const stat = try os.fstat(file.handle); + defer file.close(); + + // now repeat but using `fstatat` instead + const flags = if (builtin.os.tag == .wasi) 0x0 else os.AT_SYMLINK_NOFOLLOW; + const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags); + expectEqual(stat, statat); +} + test "readlinkat" { // enable when `readlinkat` and `symlinkat` are implemented on Windows if (builtin.os.tag == .windows) return error.SkipZigTest; From c63b23d684ded6ad7e740df87b1aedde0bec399d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jun 2020 17:45:31 +0200 Subject: [PATCH 2/3] Use fstatat on macOS (otherwise uses 32bit) --- lib/std/c.zig | 3 ++- lib/std/c/darwin.zig | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 97d6bf5215..d4c70a9fe4 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -73,7 +73,6 @@ pub extern "c" fn abort() noreturn; pub extern "c" fn exit(code: c_int) noreturn; pub extern "c" fn isatty(fd: fd_t) c_int; pub extern "c" fn close(fd: fd_t) c_int; -pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int; 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; @@ -116,9 +115,11 @@ pub extern "c" fn readlinkat(dirfd: fd_t, noalias path: [*:0]const u8, noalias b pub usingnamespace switch (builtin.os.tag) { .macosx, .ios, .watchos, .tvos => struct { pub const realpath = @"realpath$DARWIN_EXTSN"; + pub const fstatat = @"fstatat$INODE64"; }, else => struct { pub extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8; + pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int; }, }; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index f827eb6863..2426638569 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -16,6 +16,7 @@ pub extern "c" fn @"realpath$DARWIN_EXTSN"(noalias file_name: [*:0]const u8, noa pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize; pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int; +pub extern "c" fn @"fstatat$INODE64"(dirfd: fd_t, path_name: [*:0]const u8, buf: *Stat, flags: u32) c_int; pub extern "c" fn mach_absolute_time() u64; pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) void; From d40e367b73c130c49b9a7b57adcac8df52a13aa7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jun 2020 08:43:15 +0200 Subject: [PATCH 3/3] Reformat using if-else where appropriate --- lib/std/os.zig | 60 +++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index aa85f9483b..99d66db2bb 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1667,13 +1667,13 @@ pub const UnlinkError = error{ pub fn unlink(file_path: []const u8) UnlinkError!void { if (builtin.os.tag == .wasi) { @compileError("unlink is not supported in WASI; use unlinkat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return windows.DeleteFileW(file_path_w.span().ptr); + } else { + const file_path_c = try toPosixPath(file_path); + return unlinkZ(&file_path_c); } - const file_path_c = try toPosixPath(file_path); - return unlinkZ(&file_path_c); } pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); @@ -1874,15 +1874,15 @@ const RenameError = error{ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { if (builtin.os.tag == .wasi) { @compileError("rename is not supported in WASI; use renameat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameW(old_path_w.span().ptr, new_path_w.span().ptr); + } else { + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameZ(&old_path_c, &new_path_c); } - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameZ(&old_path_c, &new_path_c); } pub const renameC = @compileError("deprecated: renamed to renameZ"); @@ -2153,14 +2153,14 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .wasi) { @compileError("mkdir is not supported in WASI; use mkdirat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); windows.CloseHandle(sub_dir_handle); return; + } else { + const dir_path_c = try toPosixPath(dir_path); + return mkdirZ(&dir_path_c, mode); } - const dir_path_c = try toPosixPath(dir_path); - return mkdirZ(&dir_path_c, mode); } /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. @@ -2208,13 +2208,13 @@ pub const DeleteDirError = error{ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { if (builtin.os.tag == .wasi) { @compileError("rmdir is not supported in WASI; use unlinkat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(dir_path_w.span().ptr); + } else { + const dir_path_c = try toPosixPath(dir_path); + return rmdirZ(&dir_path_c); } - const dir_path_c = try toPosixPath(dir_path); - return rmdirZ(&dir_path_c); } pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); @@ -2259,13 +2259,13 @@ pub const ChangeCurDirError = error{ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { if (builtin.os.tag == .wasi) { @compileError("chdir is not supported in WASI"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); @compileError("TODO implement chdir for Windows"); + } else { + const dir_path_c = try toPosixPath(dir_path); + return chdirZ(&dir_path_c); } - const dir_path_c = try toPosixPath(dir_path); - return chdirZ(&dir_path_c); } pub const chdirC = @compileError("deprecated: renamed to chdirZ"); @@ -2325,13 +2325,13 @@ pub const ReadLinkError = error{ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .wasi) { @compileError("readlink is not supported in WASI; use readlinkat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return readlinkW(file_path_w.span().ptr, out_buffer); + } else { + const file_path_c = try toPosixPath(file_path); + return readlinkZ(&file_path_c, out_buffer); } - const file_path_c = try toPosixPath(file_path); - return readlinkZ(&file_path_c, out_buffer); } pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); @@ -3089,6 +3089,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat { else => |err| return unexpectedErrno(err), } } + if (builtin.os.tag == .windows) { + @compileError("fstat is not yet implemented on Windows"); + } var stat: Stat = undefined; switch (errno(system.fstat(fd, &stat))) { @@ -3109,9 +3112,12 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound }; pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { if (builtin.os.tag == .wasi) { return fstatatWasi(dirfd, pathname, flags); + } else if (builtin.os.tag == .windows) { + @compileError("fstatat is not yet implemented on Windows"); + } else { + const pathname_c = try toPosixPath(pathname); + return fstatatZ(dirfd, &pathname_c, flags); } - const pathname_c = try toPosixPath(pathname); - return fstatatZ(dirfd, &pathname_c, flags); } pub const fstatatC = @compileError("deprecated: renamed to fstatatZ");