From 64078ca92439d4f01d4a6e60a6ad33025da5a36a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 22 Jun 2020 09:14:51 +0200 Subject: [PATCH 1/2] Enhance std.os.symlinkat coverage Fixes `std.os.symlinkat` compile errors, adds Windows stub (still needs to be implemented), adds WASI implementation. --- lib/std/c.zig | 1 + lib/std/os.zig | 62 +++++++++++++++++++++++++++++++++++++++++---- lib/std/os/test.zig | 13 ++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index fe9fc7ac40..97d6bf5215 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -102,6 +102,7 @@ pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int; pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; +pub extern "c" fn symlinkat(oldpath: [*:0]const u8, newdirfd: fd_t, newpath: [*:0]const u8) c_int; pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int; pub extern "c" fn chdir(path: [*:0]const u8) c_int; diff --git a/lib/std/os.zig b/lib/std/os.zig index 0558390b9e..16ab314c14 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1520,15 +1520,17 @@ pub const SymLinkError = error{ /// If `sym_link_path` exists, it will not be overwritten. /// See also `symlinkC` and `symlinkW`. pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { + if (builtin.os.tag == .wasi) { + @compileError("symlink is not supported in WASI; use symlinkat instead"); + } if (builtin.os.tag == .windows) { const target_path_w = try windows.sliceToPrefixedFileW(target_path); const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); - } else { - const target_path_c = try toPosixPath(target_path); - const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkZ(&target_path_c, &sym_link_path_c); } + const target_path_c = try toPosixPath(target_path); + const sym_link_path_c = try toPosixPath(sym_link_path); + return symlinkZ(&target_path_c, &sym_link_path_c); } pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); @@ -1561,15 +1563,65 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin } } +/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string +/// `target_path` **relative** to `newdirfd` directory handle. +/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent +/// one; the latter case is known as a dangling link. +/// If `sym_link_path` exists, it will not be overwritten. +/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + if (builtin.os.tag == .wasi) { + return symlinkatWasi(target_path, newdirfd, sym_link_path); + } + if (builtin.os.tag == .windows) { + const target_path_w = try windows.sliceToPrefixedFileW(target_path); + const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); + return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path_w.span().ptr); + } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkatZ(target_path_c, newdirfd, sym_link_path_c); + return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c); } pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); +/// WASI-only. The same as `symlinkat` but targeting WASI. +/// See also `symlinkat`. +pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + switch (wasi.path_symlink(sym_link_path.ptr, sym_link_path.len, newdirfd, target_path.ptr, target_path.len)) { + wasi.ESUCCESS => {}, + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EACCES => return error.AccessDenied, + wasi.EPERM => return error.AccessDenied, + wasi.EDQUOT => return error.DiskQuota, + wasi.EEXIST => return error.PathAlreadyExists, + wasi.EIO => return error.FileSystem, + wasi.ELOOP => return error.SymLinkLoop, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOTDIR => return error.NotDir, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded. +/// See also `symlinkat`. +pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymlinkError!void { + @compileError("TODO implement on Windows"); +} + +/// The same as `symlinkat` except the parameters are null-terminated pointers. +/// See also `symlinkat`. pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { + if (builtin.os.tag == .windows) { + const target_path_w = try windows.cStrToPrefixedFileW(target_path); + const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); + return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path.span().ptr); + } switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { 0 => return, EFAULT => unreachable, diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index cc3b4f5741..d4624929ee 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -18,6 +18,19 @@ const AtomicOrder = builtin.AtomicOrder; const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; +test "readlinkat" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create file + try tmp.dir.writeFile("file.txt", "nonsense"); + + // create a symbolic link + try os.symlinkat("file.txt", tmp.dir.fd, "link"); + + // TODO read the link +} + test "makePath, put some files in it, deleteTree" { var tmp = tmpDir(.{}); defer tmp.cleanup(); From c950f0c6c3d5472a635ba8b971f46200d41221fd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 22 Jun 2020 09:40:06 +0200 Subject: [PATCH 2/2] Enhance std.os.readlinkat coverage Adds Windows stub (still needs to be implemented on Windows), adds WASI implementation, adds unit test testing basic chain of ops: create file -> symlink -> readlink. --- lib/std/os.zig | 46 +++++++++++++++++++++++++++++++++++++++++++-- lib/std/os/test.zig | 8 +++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 16ab314c14..59cde5d606 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1588,7 +1588,7 @@ pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); /// WASI-only. The same as `symlinkat` but targeting WASI. /// See also `symlinkat`. pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { - switch (wasi.path_symlink(sym_link_path.ptr, sym_link_path.len, newdirfd, target_path.ptr, target_path.len)) { + switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { wasi.ESUCCESS => {}, wasi.EFAULT => unreachable, wasi.EINVAL => unreachable, @@ -2343,12 +2343,54 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 } } +/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. +/// The return value is a slice of `out_buffer` from index 0. +/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. +pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (builtin.os.tag == .wasi) { + return readlinkatWasi(dirfd, file_path, out_buffer); + } + if (builtin.os.tag == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(file_path); + return readlinkatW(dirfd, file_path.span().ptr, out_buffer); + } + const file_path_c = try toPosixPath(file_path); + return readlinkatZ(dirfd, &file_path_c, out_buffer); +} + pub const readlinkatC = @compileError("deprecated: renamed to readlinkatZ"); +/// WASI-only. Same as `readlinkat` but targets WASI. +/// See also `readlinkat`. +pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + var bufused: usize = undefined; + switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { + wasi.ESUCCESS => return out_buffer[0..bufused], + wasi.EACCES => return error.AccessDenied, + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EIO => return error.FileSystem, + wasi.ELOOP => return error.SymLinkLoop, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOTDIR => return error.NotDir, + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. +/// See also `readlinkat`. +pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { + @compileError("TODO implement on Windows"); +} + +/// Same as `readlinkat` except `file_path` is null-terminated. +/// See also `readlinkat`. pub fn readlinkatZ(dirfd: fd_t, 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 readlinkatW(dirfd, file_path_w.span().ptr, out_buffer); } const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); switch (errno(rc)) { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index d4624929ee..c298a0a203 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -19,6 +19,9 @@ const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; test "readlinkat" { + // enable when `readlinkat` and `symlinkat` are implemented on Windows + if (builtin.os.tag == .windows) return error.SkipZigTest; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -28,7 +31,10 @@ test "readlinkat" { // create a symbolic link try os.symlinkat("file.txt", tmp.dir.fd, "link"); - // TODO read the link + // read the link + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const read_link = try os.readlinkat(tmp.dir.fd, "link", buffer[0..]); + expect(mem.eql(u8, "file.txt", read_link)); } test "makePath, put some files in it, deleteTree" {