authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-06-23 09:59:05+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-06-24 21:00:21+02:00
logbe78b7b648fdda7e0b052aa96eb4551edc64ef18
tree6d6a3ac789fc59928d196b47a6920aaebae40dca
parent2c7fc1c5c504af9dd22d5ac21ecc6773e263e395

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`.

2 files changed, 92 insertions(+), 22 deletions(-)

lib/std/os.zig+70-22
......@@ -1665,13 +1665,15 @@ pub const UnlinkError = error{
16651665/// Delete a name and possibly the file it refers to.
16661666/// See also `unlinkC`.
16671667pub fn unlink(file_path: []const u8) UnlinkError!void {
1668 if (builtin.os.tag == .wasi) {
1669 @compileError("unlink is not supported in WASI; use unlinkat instead");
1670 }
16681671 if (builtin.os.tag == .windows) {
16691672 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
16701673 return windows.DeleteFileW(file_path_w.span().ptr);
1671 } else {
1672 const file_path_c = try toPosixPath(file_path);
1673 return unlinkZ(&file_path_c);
16741674 }
1675 const file_path_c = try toPosixPath(file_path);
1676 return unlinkZ(&file_path_c);
16751677}
16761678
16771679pub 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
17221724
17231725pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ");
17241726
1727/// WASI-only. Same as `unlinkat` but targeting WASI.
1728/// See also `unlinkat`.
17251729pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
17261730 const remove_dir = (flags & AT_REMOVEDIR) != 0;
17271731 const res = if (remove_dir)
......@@ -1868,15 +1872,17 @@ const RenameError = error{
18681872
18691873/// Change the name or location of a file.
18701874pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
1875 if (builtin.os.tag == .wasi) {
1876 @compileError("rename is not supported in WASI; use renameat instead");
1877 }
18711878 if (builtin.os.tag == .windows) {
18721879 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
18731880 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
18741881 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
1875 } else {
1876 const old_path_c = try toPosixPath(old_path);
1877 const new_path_c = try toPosixPath(new_path);
1878 return renameZ(&old_path_c, &new_path_c);
18791882 }
1883 const old_path_c = try toPosixPath(old_path);
1884 const new_path_c = try toPosixPath(new_path);
1885 return renameZ(&old_path_c, &new_path_c);
18801886}
18811887
18821888pub const renameC = @compileError("deprecated: renamed to renameZ");
......@@ -1939,7 +1945,8 @@ pub fn renameat(
19391945 }
19401946}
19411947
1942/// Same as `renameat` expect only WASI.
1948/// WASI-only. Same as `renameat` expect targeting WASI.
1949/// See also `renameat`.
19431950pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void {
19441951 switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) {
19451952 wasi.ESUCCESS => return,
......@@ -2144,14 +2151,16 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro
21442151/// Create a directory.
21452152/// `mode` is ignored on Windows.
21462153pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
2154 if (builtin.os.tag == .wasi) {
2155 @compileError("mkdir is not supported in WASI; use mkdirat instead");
2156 }
21472157 if (builtin.os.tag == .windows) {
21482158 const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
21492159 windows.CloseHandle(sub_dir_handle);
21502160 return;
2151 } else {
2152 const dir_path_c = try toPosixPath(dir_path);
2153 return mkdirZ(&dir_path_c, mode);
21542161 }
2162 const dir_path_c = try toPosixPath(dir_path);
2163 return mkdirZ(&dir_path_c, mode);
21552164}
21562165
21572166/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
......@@ -2197,13 +2206,15 @@ pub const DeleteDirError = error{
21972206
21982207/// Deletes an empty directory.
21992208pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
2209 if (builtin.os.tag == .wasi) {
2210 @compileError("rmdir is not supported in WASI; use unlinkat instead");
2211 }
22002212 if (builtin.os.tag == .windows) {
22012213 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
22022214 return windows.RemoveDirectoryW(dir_path_w.span().ptr);
2203 } else {
2204 const dir_path_c = try toPosixPath(dir_path);
2205 return rmdirZ(&dir_path_c);
22062215 }
2216 const dir_path_c = try toPosixPath(dir_path);
2217 return rmdirZ(&dir_path_c);
22072218}
22082219
22092220pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
......@@ -2246,13 +2257,15 @@ pub const ChangeCurDirError = error{
22462257/// Changes the current working directory of the calling process.
22472258/// `dir_path` is recommended to be a UTF-8 encoded string.
22482259pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2260 if (builtin.os.tag == .wasi) {
2261 @compileError("chdir is not supported in WASI");
2262 }
22492263 if (builtin.os.tag == .windows) {
22502264 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
22512265 @compileError("TODO implement chdir for Windows");
2252 } else {
2253 const dir_path_c = try toPosixPath(dir_path);
2254 return chdirZ(&dir_path_c);
22552266 }
2267 const dir_path_c = try toPosixPath(dir_path);
2268 return chdirZ(&dir_path_c);
22562269}
22572270
22582271pub const chdirC = @compileError("deprecated: renamed to chdirZ");
......@@ -2310,22 +2323,30 @@ pub const ReadLinkError = error{
23102323/// Read value of a symbolic link.
23112324/// The return value is a slice of `out_buffer` from index 0.
23122325pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2326 if (builtin.os.tag == .wasi) {
2327 @compileError("readlink is not supported in WASI; use readlinkat instead");
2328 }
23132329 if (builtin.os.tag == .windows) {
23142330 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
2315 @compileError("TODO implement readlink for Windows");
2316 } else {
2317 const file_path_c = try toPosixPath(file_path);
2318 return readlinkZ(&file_path_c, out_buffer);
2331 return readlinkW(file_path_w.span().ptr, out_buffer);
23192332 }
2333 const file_path_c = try toPosixPath(file_path);
2334 return readlinkZ(&file_path_c, out_buffer);
23202335}
23212336
23222337pub 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
23242345/// Same as `readlink` except `file_path` is null-terminated.
23252346pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
23262347 if (builtin.os.tag == .windows) {
23272348 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);
23292350 }
23302351 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
23312352 switch (errno(rc)) {
......@@ -3055,6 +3076,7 @@ pub const FStatError = error{
30553076 AccessDenied,
30563077} || UnexpectedError;
30573078
3079/// Return information about a file descriptor.
30583080pub fn fstat(fd: fd_t) FStatError!Stat {
30593081 if (builtin.os.tag == .wasi) {
30603082 var stat: wasi.filestat_t = undefined;
......@@ -3081,13 +3103,39 @@ pub fn fstat(fd: fd_t) FStatError!Stat {
30813103
30823104pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound };
30833105
3106/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
3107/// which is relative to `dirfd` handle.
3108/// See also `fstatatZ` and `fstatatWasi`.
30843109pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
3110 if (builtin.os.tag == .wasi) {
3111 return fstatatWasi(dirfd, pathname, flags);
3112 }
30853113 const pathname_c = try toPosixPath(pathname);
30863114 return fstatatZ(dirfd, &pathname_c, flags);
30873115}
30883116
30893117pub const fstatatC = @compileError("deprecated: renamed to fstatatZ");
30903118
3119/// WASI-only. Same as `fstatat` but targeting WASI.
3120/// See also `fstatat`.
3121pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
3122 var stat: wasi.filestat_t = undefined;
3123 switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) {
3124 wasi.ESUCCESS => return Stat.fromFilestat(stat),
3125 wasi.EINVAL => unreachable,
3126 wasi.EBADF => unreachable, // Always a race condition.
3127 wasi.ENOMEM => return error.SystemResources,
3128 wasi.EACCES => return error.AccessDenied,
3129 wasi.EFAULT => unreachable,
3130 wasi.ENAMETOOLONG => return error.NameTooLong,
3131 wasi.ENOENT => return error.FileNotFound,
3132 wasi.ENOTDIR => return error.FileNotFound,
3133 else => |err| return unexpectedErrno(err),
3134 }
3135}
3136
3137/// Same as `fstatat` but `pathname` is null-terminated.
3138/// See also `fstatat`.
30913139pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
30923140 var stat: Stat = undefined;
30933141 switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) {
lib/std/os/test.zig+22
......@@ -18,6 +18,28 @@ const AtomicOrder = builtin.AtomicOrder;
1818const tmpDir = std.testing.tmpDir;
1919const 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
2143test "readlinkat" {
2244 // enable when `readlinkat` and `symlinkat` are implemented on Windows
2345 if (builtin.os.tag == .windows) return error.SkipZigTest;