authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-06-25 00:01:38-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-06-25 00:01:38-04:00
log41c6cc9001a1a95ea6471c86a1ff4b59890de115
treec7d8d24778f9e774c894fe0c7a0aea7791cddcba
parent78d8931647f207965ef98354db67fb880987fafe
parentd40e367b73c130c49b9a7b57adcac8df52a13aa7
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5677 from kubkon/fstatat

[libstd]: implement fstatat in WASI plus fix on macOS

4 files changed, 90 insertions(+), 12 deletions(-)

lib/std/c.zig+2-1
...@@ -73,7 +73,6 @@ pub extern "c" fn abort() noreturn;...@@ -73,7 +73,6 @@ pub extern "c" fn abort() noreturn;
73pub extern "c" fn exit(code: c_int) noreturn;73pub extern "c" fn exit(code: c_int) noreturn;
74pub extern "c" fn isatty(fd: fd_t) c_int;74pub extern "c" fn isatty(fd: fd_t) c_int;
75pub extern "c" fn close(fd: fd_t) c_int;75pub extern "c" fn close(fd: fd_t) c_int;
76pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int;
77pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t;76pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t;
78pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int;77pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int;
79pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: c_uint, ...) c_int;78pub 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...@@ -116,9 +115,11 @@ pub extern "c" fn readlinkat(dirfd: fd_t, noalias path: [*:0]const u8, noalias b
116pub usingnamespace switch (builtin.os.tag) {115pub usingnamespace switch (builtin.os.tag) {
117 .macosx, .ios, .watchos, .tvos => struct {116 .macosx, .ios, .watchos, .tvos => struct {
118 pub const realpath = @"realpath$DARWIN_EXTSN";117 pub const realpath = @"realpath$DARWIN_EXTSN";
118 pub const fstatat = @"fstatat$INODE64";
119 },119 },
120 else => struct {120 else => struct {
121 pub extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8;121 pub extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8;
122 pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int;
122 },123 },
123};124};
124125
lib/std/c/darwin.zig+1
...@@ -16,6 +16,7 @@ pub extern "c" fn @"realpath$DARWIN_EXTSN"(noalias file_name: [*:0]const u8, noa...@@ -16,6 +16,7 @@ pub extern "c" fn @"realpath$DARWIN_EXTSN"(noalias file_name: [*:0]const u8, noa
1616
17pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize;17pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize;
18pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int;18pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int;
19pub extern "c" fn @"fstatat$INODE64"(dirfd: fd_t, path_name: [*:0]const u8, buf: *Stat, flags: u32) c_int;
1920
20pub extern "c" fn mach_absolute_time() u64;21pub extern "c" fn mach_absolute_time() u64;
21pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) void;22pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) void;
lib/std/os.zig+65-11
...@@ -1665,7 +1665,9 @@ pub const UnlinkError = error{...@@ -1665,7 +1665,9 @@ pub const UnlinkError = error{
1665/// Delete a name and possibly the file it refers to.1665/// Delete a name and possibly the file it refers to.
1666/// See also `unlinkC`.1666/// See also `unlinkC`.
1667pub fn unlink(file_path: []const u8) UnlinkError!void {1667pub fn unlink(file_path: []const u8) UnlinkError!void {
1668 if (builtin.os.tag == .windows) {1668 if (builtin.os.tag == .wasi) {
1669 @compileError("unlink is not supported in WASI; use unlinkat instead");
1670 } else if (builtin.os.tag == .windows) {
1669 const file_path_w = try windows.sliceToPrefixedFileW(file_path);1671 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1670 return windows.DeleteFileW(file_path_w.span().ptr);1672 return windows.DeleteFileW(file_path_w.span().ptr);
1671 } else {1673 } else {
...@@ -1722,6 +1724,8 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo...@@ -1722,6 +1724,8 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
17221724
1723pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ");1725pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ");
17241726
1727/// WASI-only. Same as `unlinkat` but targeting WASI.
1728/// See also `unlinkat`.
1725pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {1729pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
1726 const remove_dir = (flags & AT_REMOVEDIR) != 0;1730 const remove_dir = (flags & AT_REMOVEDIR) != 0;
1727 const res = if (remove_dir)1731 const res = if (remove_dir)
...@@ -1868,7 +1872,9 @@ const RenameError = error{...@@ -1868,7 +1872,9 @@ const RenameError = error{
18681872
1869/// Change the name or location of a file.1873/// Change the name or location of a file.
1870pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {1874pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
1871 if (builtin.os.tag == .windows) {1875 if (builtin.os.tag == .wasi) {
1876 @compileError("rename is not supported in WASI; use renameat instead");
1877 } else if (builtin.os.tag == .windows) {
1872 const old_path_w = try windows.sliceToPrefixedFileW(old_path);1878 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
1873 const new_path_w = try windows.sliceToPrefixedFileW(new_path);1879 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
1874 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);1880 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
...@@ -1939,7 +1945,8 @@ pub fn renameat(...@@ -1939,7 +1945,8 @@ pub fn renameat(
1939 }1945 }
1940}1946}
19411947
1942/// Same as `renameat` expect only WASI.1948/// WASI-only. Same as `renameat` expect targeting WASI.
1949/// See also `renameat`.
1943pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void {1950pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void {
1944 switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) {1951 switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) {
1945 wasi.ESUCCESS => return,1952 wasi.ESUCCESS => return,
...@@ -2144,7 +2151,9 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro...@@ -2144,7 +2151,9 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro
2144/// Create a directory.2151/// Create a directory.
2145/// `mode` is ignored on Windows.2152/// `mode` is ignored on Windows.
2146pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {2153pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
2147 if (builtin.os.tag == .windows) {2154 if (builtin.os.tag == .wasi) {
2155 @compileError("mkdir is not supported in WASI; use mkdirat instead");
2156 } else if (builtin.os.tag == .windows) {
2148 const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);2157 const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
2149 windows.CloseHandle(sub_dir_handle);2158 windows.CloseHandle(sub_dir_handle);
2150 return;2159 return;
...@@ -2197,7 +2206,9 @@ pub const DeleteDirError = error{...@@ -2197,7 +2206,9 @@ pub const DeleteDirError = error{
21972206
2198/// Deletes an empty directory.2207/// Deletes an empty directory.
2199pub fn rmdir(dir_path: []const u8) DeleteDirError!void {2208pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
2200 if (builtin.os.tag == .windows) {2209 if (builtin.os.tag == .wasi) {
2210 @compileError("rmdir is not supported in WASI; use unlinkat instead");
2211 } else if (builtin.os.tag == .windows) {
2201 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);2212 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2202 return windows.RemoveDirectoryW(dir_path_w.span().ptr);2213 return windows.RemoveDirectoryW(dir_path_w.span().ptr);
2203 } else {2214 } else {
...@@ -2246,7 +2257,9 @@ pub const ChangeCurDirError = error{...@@ -2246,7 +2257,9 @@ pub const ChangeCurDirError = error{
2246/// Changes the current working directory of the calling process.2257/// Changes the current working directory of the calling process.
2247/// `dir_path` is recommended to be a UTF-8 encoded string.2258/// `dir_path` is recommended to be a UTF-8 encoded string.
2248pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {2259pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2249 if (builtin.os.tag == .windows) {2260 if (builtin.os.tag == .wasi) {
2261 @compileError("chdir is not supported in WASI");
2262 } else if (builtin.os.tag == .windows) {
2250 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);2263 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2251 @compileError("TODO implement chdir for Windows");2264 @compileError("TODO implement chdir for Windows");
2252 } else {2265 } else {
...@@ -2310,9 +2323,11 @@ pub const ReadLinkError = error{...@@ -2310,9 +2323,11 @@ pub const ReadLinkError = error{
2310/// Read value of a symbolic link.2323/// Read value of a symbolic link.
2311/// The return value is a slice of `out_buffer` from index 0.2324/// The return value is a slice of `out_buffer` from index 0.
2312pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {2325pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2313 if (builtin.os.tag == .windows) {2326 if (builtin.os.tag == .wasi) {
2327 @compileError("readlink is not supported in WASI; use readlinkat instead");
2328 } else if (builtin.os.tag == .windows) {
2314 const file_path_w = try windows.sliceToPrefixedFileW(file_path);2329 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2315 @compileError("TODO implement readlink for Windows");2330 return readlinkW(file_path_w.span().ptr, out_buffer);
2316 } else {2331 } else {
2317 const file_path_c = try toPosixPath(file_path);2332 const file_path_c = try toPosixPath(file_path);
2318 return readlinkZ(&file_path_c, out_buffer);2333 return readlinkZ(&file_path_c, out_buffer);
...@@ -2321,11 +2336,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {...@@ -2321,11 +2336,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
23212336
2322pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");2337pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
23232338
2339/// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded.
2340/// Seel also `readlinkZ`.
2341pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
2342 @compileError("TODO implement readlink for Windows");
2343}
2344
2324/// Same as `readlink` except `file_path` is null-terminated.2345/// Same as `readlink` except `file_path` is null-terminated.
2325pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {2346pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
2326 if (builtin.os.tag == .windows) {2347 if (builtin.os.tag == .windows) {
2327 const file_path_w = try windows.cStrToPrefixedFileW(file_path);2348 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
2328 @compileError("TODO implement readlink for Windows");2349 return readlinkW(file_path_w.span().ptr, out_buffer);
2329 }2350 }
2330 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);2351 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
2331 switch (errno(rc)) {2352 switch (errno(rc)) {
...@@ -3055,6 +3076,7 @@ pub const FStatError = error{...@@ -3055,6 +3076,7 @@ pub const FStatError = error{
3055 AccessDenied,3076 AccessDenied,
3056} || UnexpectedError;3077} || UnexpectedError;
30573078
3079/// Return information about a file descriptor.
3058pub fn fstat(fd: fd_t) FStatError!Stat {3080pub fn fstat(fd: fd_t) FStatError!Stat {
3059 if (builtin.os.tag == .wasi) {3081 if (builtin.os.tag == .wasi) {
3060 var stat: wasi.filestat_t = undefined;3082 var stat: wasi.filestat_t = undefined;
...@@ -3067,6 +3089,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat {...@@ -3067,6 +3089,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat {
3067 else => |err| return unexpectedErrno(err),3089 else => |err| return unexpectedErrno(err),
3068 }3090 }
3069 }3091 }
3092 if (builtin.os.tag == .windows) {
3093 @compileError("fstat is not yet implemented on Windows");
3094 }
30703095
3071 var stat: Stat = undefined;3096 var stat: Stat = undefined;
3072 switch (errno(system.fstat(fd, &stat))) {3097 switch (errno(system.fstat(fd, &stat))) {
...@@ -3081,13 +3106,42 @@ pub fn fstat(fd: fd_t) FStatError!Stat {...@@ -3081,13 +3106,42 @@ pub fn fstat(fd: fd_t) FStatError!Stat {
30813106
3082pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound };3107pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound };
30833108
3109/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
3110/// which is relative to `dirfd` handle.
3111/// See also `fstatatZ` and `fstatatWasi`.
3084pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {3112pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
3085 const pathname_c = try toPosixPath(pathname);3113 if (builtin.os.tag == .wasi) {
3086 return fstatatZ(dirfd, &pathname_c, flags);3114 return fstatatWasi(dirfd, pathname, flags);
3115 } else if (builtin.os.tag == .windows) {
3116 @compileError("fstatat is not yet implemented on Windows");
3117 } else {
3118 const pathname_c = try toPosixPath(pathname);
3119 return fstatatZ(dirfd, &pathname_c, flags);
3120 }
3087}3121}
30883122
3089pub const fstatatC = @compileError("deprecated: renamed to fstatatZ");3123pub const fstatatC = @compileError("deprecated: renamed to fstatatZ");
30903124
3125/// WASI-only. Same as `fstatat` but targeting WASI.
3126/// See also `fstatat`.
3127pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
3128 var stat: wasi.filestat_t = undefined;
3129 switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) {
3130 wasi.ESUCCESS => return Stat.fromFilestat(stat),
3131 wasi.EINVAL => unreachable,
3132 wasi.EBADF => unreachable, // Always a race condition.
3133 wasi.ENOMEM => return error.SystemResources,
3134 wasi.EACCES => return error.AccessDenied,
3135 wasi.EFAULT => unreachable,
3136 wasi.ENAMETOOLONG => return error.NameTooLong,
3137 wasi.ENOENT => return error.FileNotFound,
3138 wasi.ENOTDIR => return error.FileNotFound,
3139 else => |err| return unexpectedErrno(err),
3140 }
3141}
3142
3143/// Same as `fstatat` but `pathname` is null-terminated.
3144/// See also `fstatat`.
3091pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {3145pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
3092 var stat: Stat = undefined;3146 var stat: Stat = undefined;
3093 switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) {3147 switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) {
lib/std/os/test.zig+22
...@@ -18,6 +18,28 @@ const AtomicOrder = builtin.AtomicOrder;...@@ -18,6 +18,28 @@ const AtomicOrder = builtin.AtomicOrder;
18const tmpDir = std.testing.tmpDir;18const tmpDir = std.testing.tmpDir;
19const Dir = std.fs.Dir;19const Dir = std.fs.Dir;
2020
21test "fstatat" {
22 // enable when `fstat` and `fstatat` are implemented on Windows
23 if (builtin.os.tag == .windows) return error.SkipZigTest;
24
25 var tmp = tmpDir(.{});
26 defer tmp.cleanup();
27
28 // create dummy file
29 const contents = "nonsense";
30 try tmp.dir.writeFile("file.txt", contents);
31
32 // fetch file's info on the opened fd directly
33 const file = try tmp.dir.openFile("file.txt", .{});
34 const stat = try os.fstat(file.handle);
35 defer file.close();
36
37 // now repeat but using `fstatat` instead
38 const flags = if (builtin.os.tag == .wasi) 0x0 else os.AT_SYMLINK_NOFOLLOW;
39 const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags);
40 expectEqual(stat, statat);
41}
42
21test "readlinkat" {43test "readlinkat" {
22 // enable when `readlinkat` and `symlinkat` are implemented on Windows44 // enable when `readlinkat` and `symlinkat` are implemented on Windows
23 if (builtin.os.tag == .windows) return error.SkipZigTest;45 if (builtin.os.tag == .windows) return error.SkipZigTest;