diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 0feaf69d6780..9070fd0a985b 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -16,9 +16,6 @@ pub const wasi = @import("fs/wasi.zig"); // TODO audit these APIs with respect to Dir and absolute paths -pub const symLink = os.symlink; -pub const symLinkZ = os.symlinkZ; -pub const symLinkC = @compileError("deprecated: renamed to symlinkZ"); pub const rename = os.rename; pub const renameZ = os.renameZ; pub const renameC = @compileError("deprecated: renamed to renameZ"); @@ -69,7 +66,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) { /// TODO remove the allocator requirement from this API pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { - if (symLink(existing_path, new_path)) { + if (cwd().symLink(existing_path, new_path, .{})) { return; } else |err| switch (err) { error.PathAlreadyExists => {}, @@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: try crypto.randomBytes(rand_buf[0..]); base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); - if (symLink(existing_path, tmp_path)) { + if (cwd().symLink(existing_path, new_path, .{})) { return rename(tmp_path, new_path); } else |err| switch (err) { error.PathAlreadyExists => continue, @@ -672,7 +669,7 @@ pub const Dir = struct { w.RIGHT_FD_FILESTAT_SET_TIMES | w.RIGHT_FD_FILESTAT_SET_SIZE; } - const fd = try os.openatWasi(self.fd, sub_path, 0x0, fdflags, base, 0x0); + const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0); return File{ .handle = fd }; } @@ -792,7 +789,7 @@ pub const Dir = struct { if (flags.exclusive) { oflags |= w.O_EXCL; } - const fd = try os.openatWasi(self.fd, sub_path, oflags, 0x0, base, 0x0); + const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0); return File{ .handle = fd }; } @@ -954,6 +951,9 @@ pub const Dir = struct { /// `true` means the opened directory can be scanned for the files and sub-directories /// of the result. It means the `iterate` function can be called. iterate: bool = false, + + /// `true` means it won't dereference the symlinks. + no_follow: bool = false, }; /// Opens a directory at the given path. The directory is a system resource that remains @@ -997,10 +997,11 @@ pub const Dir = struct { w.RIGHT_PATH_REMOVE_DIRECTORY | w.RIGHT_PATH_UNLINK_FILE; } + const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW; // TODO do we really need all the rights here? const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN; - const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, 0x0, base, inheriting); + const result = os.openatWasi(self.fd, sub_path, symlink_flags, w.O_DIRECTORY, 0x0, base, inheriting); const fd = result catch |err| switch (err) { error.FileTooBig => unreachable, // can't happen for directories error.IsDir => unreachable, // we're providing O_DIRECTORY @@ -1017,11 +1018,13 @@ pub const Dir = struct { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); return self.openDirW(sub_path_w.span().ptr, args); - } else if (!args.iterate) { + } + const symlink_flags: u32 = if (args.no_follow) os.O_NOFOLLOW else 0x0; + if (!args.iterate) { const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0; - return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH); + return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH | symlink_flags); } else { - return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC); + return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | symlink_flags); } } @@ -1033,7 +1036,7 @@ pub const Dir = struct { const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE; const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags; - return self.openDirAccessMaskW(sub_path_w, flags); + return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow); } /// `flags` must contain `os.O_DIRECTORY`. @@ -1053,7 +1056,7 @@ pub const Dir = struct { return Dir{ .fd = fd }; } - fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32) OpenError!Dir { + fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir { const w = os.windows; var result = Dir{ @@ -1083,6 +1086,7 @@ pub const Dir = struct { // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows"); } + const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; var io: w.IO_STATUS_BLOCK = undefined; const rc = w.ntdll.NtCreateFile( &result.fd, @@ -1093,7 +1097,7 @@ pub const Dir = struct { 0, w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, w.FILE_OPEN, - w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT, + w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point, null, 0, ); @@ -1207,21 +1211,101 @@ pub const Dir = struct { }; } + /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. + /// 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. + pub fn symLink( + self: Dir, + target_path: []const u8, + sym_link_path: []const u8, + flags: SymLinkFlags, + ) !void { + if (builtin.os.tag == .wasi) { + return self.symLinkWasi(target_path, sym_link_path, flags); + } + if (builtin.os.tag == .windows) { + const target_path_w = try os.windows.sliceToPrefixedFileW(target_path); + const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path); + return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); + } + const target_path_c = try os.toPosixPath(target_path); + const sym_link_path_c = try os.toPosixPath(sym_link_path); + return self.symLinkZ(&target_path_c, &sym_link_path_c, flags); + } + + /// WASI-only. Same as `symLink` except targeting WASI. + pub fn symLinkWasi( + self: Dir, + target_path: []const u8, + sym_link_path: []const u8, + flags: SymLinkFlags, + ) !void { + return os.symlinkatWasi(target_path, self.fd, sym_link_path); + } + + /// Same as `symLink`, except the pathname parameters are null-terminated. + pub fn symLinkZ( + self: Dir, + target_path_c: [*:0]const u8, + sym_link_path_c: [*:0]const u8, + flags: SymLinkFlags, + ) !void { + if (builtin.os.tag == .windows) { + const target_path_w = try os.windows.cStrToPrefixedFileW(target_path_c); + const sym_link_path_w = try os.windows.cStrToPrefixedFileW(sym_link_path_c); + return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); + } + return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c); + } + + /// Windows-only. Same as `symLink` except the pathname parameters + /// are null-terminated, WTF16 encoded. + pub fn symLinkW( + self: Dir, + target_path_w: [:0]const u16, + sym_link_path_w: [:0]const u16, + flags: SymLinkFlags, + ) !void { + return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory); + } + /// Read value of a symbolic link. /// The return value is a slice of `buffer`, from index `0`. /// Asserts that the path parameter has no null bytes. pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { + if (builtin.os.tag == .wasi) { + return self.readLinkWasi(sub_path, buffer); + } + if (builtin.os.tag == .windows) { + return os.windows.ReadLink(self.fd, sub_path, buffer); + } const sub_path_c = try os.toPosixPath(sub_path); return self.readLinkZ(&sub_path_c, buffer); } pub const readLinkC = @compileError("deprecated: renamed to readLinkZ"); + /// WASI-only. Same as `readLink` except targeting WASI. + pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { + return os.readlinkatWasi(self.fd, sub_path, buffer); + } + /// Same as `readLink`, except the `pathname` parameter is null-terminated. pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { + if (builtin.os.tag == .windows) { + const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); + return self.readLinkW(sub_path_w, buffer); + } return os.readlinkatZ(self.fd, sub_path_c, buffer); } + /// Windows-only. Same as `readLink` except the pathname parameter + /// is null-terminated, WTF16 encoded. + pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 { + return os.windows.ReadLinkW(self.fd, sub_path_w, buffer); + } + /// On success, caller owns returned buffer. /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 { @@ -1280,6 +1364,7 @@ pub const Dir = struct { pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { start_over: while (true) { var got_access_denied = false; + // First, try deleting the item as a file. This way we don't follow sym links. if (self.deleteFile(sub_path)) { return; @@ -1300,7 +1385,7 @@ pub const Dir = struct { error.Unexpected, => |e| return e, } - var dir = self.openDir(sub_path, .{ .iterate = true }) catch |err| switch (err) { + var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) { error.NotDir => { if (got_access_denied) { return error.AccessDenied; @@ -1367,7 +1452,7 @@ pub const Dir = struct { => |e| return e, } - const new_dir = dir.openDir(entry.name, .{ .iterate = true }) catch |err| switch (err) { + const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) { error.NotDir => { if (got_access_denied) { return error.AccessDenied; @@ -1692,8 +1777,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 return os.readlink(pathname, buffer); } +/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16 +/// encoded. +pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { + assert(path.isAbsoluteWindowsW(pathname_w)); + return os.readlinkW(pathname_w, buffer); +} + /// Same as `readLink`, except the path parameter is null-terminated. -pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { +pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { assert(path.isAbsoluteZ(pathname_c)); return os.readlinkZ(pathname_c, buffer); } @@ -1701,6 +1793,57 @@ pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ! pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute"); pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ"); +/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink +/// will point to a file or a directory. This value is ignored on all hosts +/// except Windows where creating symlinks to different resource types, requires +/// different flags. By default, `symLinkAbsolute` is assumed to point to a file. +pub const SymLinkFlags = struct { + is_directory: bool = false, +}; + +/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. +/// 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 `symLinkAbsoluteZ` and `symLinkAbsoluteW`. +pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void { + if (builtin.os.tag == .wasi) { + @compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead"); + } + assert(path.isAbsolute(target_path)); + assert(path.isAbsolute(sym_link_path)); + if (builtin.os.tag == .windows) { + return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory); + } + return os.symlink(target_path, sym_link_path); +} + +/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded. +/// Note that this function will by default try creating a symbolic link to a file. If you would +/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. +/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. +pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void { + assert(path.isAbsoluteWindowsW(target_path_w)); + assert(path.isAbsoluteWindowsW(sym_link_path_w)); + return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory); +} + +/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers. +/// See also `symLinkAbsolute`. +pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void { + assert(path.isAbsoluteZ(target_path_c)); + assert(path.isAbsoluteZ(sym_link_path_c)); + if (builtin.os.tag == .windows) { + const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c); + const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c); + return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory); + } + return os.symlinkZ(target_path_c, sym_link_path_c); +} + +pub const symLink = @compileError("deprecated: use Dir.symLink or symLinkAbsolute"); +pub const symLinkC = @compileError("deprecated: use Dir.symLinkZ or symLinkAbsoluteZ"); + pub const Walker = struct { stack: std.ArrayList(StackItem), name_buffer: std.ArrayList(u8), diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index a3cf2e800268..c9f171196776 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -10,6 +10,76 @@ const Dir = std.fs.Dir; const File = std.fs.File; const tmpDir = testing.tmpDir; +test "Dir.readLink" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // Create some targets + try tmp.dir.writeFile("file.txt", "nonsense"); + try tmp.dir.makeDir("subdir"); + + { + // Create symbolic link by path + try tmp.dir.symLink("file.txt", "symlink1", .{}); + try testReadLink(tmp.dir, "file.txt", "symlink1"); + } + { + // Create symbolic link by path + try tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }); + try testReadLink(tmp.dir, "subdir", "symlink2"); + } +} + +fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const given = try dir.readLink(symlink_path, buffer[0..]); + testing.expect(mem.eql(u8, target_path, given)); +} + +test "readLinkAbsolute" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // Create some targets + try tmp.dir.writeFile("file.txt", "nonsense"); + try tmp.dir.makeDir("subdir"); + + // Get base abs path + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + + const base_path = blk: { + const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); + break :blk try fs.realpathAlloc(&arena.allocator, relative_path); + }; + const allocator = &arena.allocator; + + { + const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "file.txt" }); + const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink1" }); + + // Create symbolic link by path + try fs.symLinkAbsolute(target_path, symlink_path, .{}); + try testReadLinkAbsolute(target_path, symlink_path); + } + { + const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" }); + const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink2" }); + + // Create symbolic link by path + try fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); + try testReadLinkAbsolute(target_path, symlink_path); + } +} + +fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); + testing.expect(mem.eql(u8, target_path, given)); +} + test "Dir.Iterator" { var tmp_dir = tmpDir(.{ .iterate = true }); defer tmp_dir.cleanup(); diff --git a/lib/std/os.zig b/lib/std/os.zig index dfb47208ca9c..3e05ac41e53c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1109,10 +1109,10 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope } /// Open and possibly create a file in WASI. -pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { +pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, lookup_flags: lookupflags_t, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { while (true) { var fd: fd_t = undefined; - switch (wasi.path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { + switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { wasi.ESUCCESS => return fd, wasi.EINTR => continue, @@ -1542,15 +1542,13 @@ pub const SymLinkError = error{ /// 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 `symlinkC` and `symlinkW`. +/// See also `symlinkZ. 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); + @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); @@ -1563,9 +1561,7 @@ pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); /// See also `symlink`. pub fn symlinkZ(target_path: [*:0]const u8, 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 windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); + @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); } switch (errno(system.symlink(target_path, sym_link_path))) { 0 => return, @@ -1598,9 +1594,7 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const 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); + @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); @@ -1633,19 +1627,11 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c } } -/// 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); + @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); } switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { 0 => return, @@ -1819,9 +1805,9 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0; const create_options_flags = if (want_rmdir_behavior) - @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE) + @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT) else - @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE); + @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead? const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2); var nt_name = w.UNICODE_STRING{ @@ -2355,6 +2341,11 @@ pub const ReadLinkError = error{ FileNotFound, SystemResources, NotDir, + InvalidUtf8, + BadPathName, + /// Windows-only. This error may occur if the opened reparse point is + /// of unsupported type. + UnsupportedReparsePointType, } || UnexpectedError; /// Read value of a symbolic link. @@ -2363,8 +2354,7 @@ 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"); } else if (builtin.os.tag == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(file_path); - return readlinkW(file_path_w.span().ptr, out_buffer); + return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); } else { const file_path_c = try toPosixPath(file_path); return readlinkZ(&file_path_c, out_buffer); @@ -2373,16 +2363,16 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); -/// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded. -/// Seel also `readlinkZ`. +/// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded. +/// See also `readlinkZ`. pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { - @compileError("TODO implement readlink for Windows"); + return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer); } /// 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); + const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); return readlinkW(file_path_w.span().ptr, out_buffer); } const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); @@ -2409,8 +2399,7 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink 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); + return windows.ReadLink(dirfd, file_path, out_buffer); } const file_path_c = try toPosixPath(file_path); return readlinkatZ(dirfd, &file_path_c, out_buffer); @@ -2441,7 +2430,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read /// 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"); + return windows.ReadLinkW(dirfd, file_path, out_buffer); } /// Same as `readlinkat` except `file_path` is null-terminated. @@ -3997,7 +3986,13 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; - return readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer); + const target = readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer) catch |err| { + switch (err) { + error.UnsupportedReparsePointType => unreachable, // Windows only, + else => |e| return e, + } + }; + return target; } const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) { EINVAL => unreachable, diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index e508f5ae20f3..7310562d64dc 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -17,6 +17,42 @@ const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; +const ArenaAllocator = std.heap.ArenaAllocator; + +test "symlink with relative paths" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + // First, try relative paths in cwd + var cwd = fs.cwd(); + try cwd.writeFile("file.txt", "nonsense"); + + if (builtin.os.tag == .windows) { + try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false); + } else { + try os.symlink("file.txt", "symlinked"); + } + + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const given = try os.readlink("symlinked", buffer[0..]); + expect(mem.eql(u8, "file.txt", given)); + + try cwd.deleteFile("file.txt"); + try cwd.deleteFile("symlinked"); +} + +test "readlink on Windows" { + if (builtin.os.tag != .windows) return error.SkipZigTest; + + try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); + try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); + try testReadlink("C:\\Users", "C:\\Documents and Settings"); +} + +fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const given = try os.readlink(symlink_path, buffer[0..]); + expect(mem.eql(u8, target_path, given)); +} test "fstatat" { // enable when `fstat` and `fstatat` are implemented on Windows @@ -41,9 +77,6 @@ test "fstatat" { } 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(); @@ -51,7 +84,11 @@ test "readlinkat" { try tmp.dir.writeFile("file.txt", "nonsense"); // create a symbolic link - try os.symlinkat("file.txt", tmp.dir.fd, "link"); + if (builtin.os.tag == .windows) { + try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false); + } else { + try os.symlinkat("file.txt", tmp.dir.fd, "link"); + } // read the link var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 1ed1ef1f54ca..ab549a0ebf73 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -145,6 +145,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN var delay: usize = 1; while (true) { + var flags: ULONG = undefined; const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; const rc = ntdll.NtCreateFile( &result, @@ -601,28 +602,244 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const CreateSymbolicLinkError = error{Unexpected}; +pub const CreateSymbolicLinkError = error{ + AccessDenied, + PathAlreadyExists, + FileNotFound, + NameTooLong, + InvalidUtf8, + BadPathName, + NoDevice, + Unexpected, +}; pub fn CreateSymbolicLink( + dir: ?HANDLE, sym_link_path: []const u8, target_path: []const u8, - flags: DWORD, + is_directory: bool, ) CreateSymbolicLinkError!void { const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); const target_path_w = try sliceToPrefixedFileW(target_path); - return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags); + return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory); } pub fn CreateSymbolicLinkW( - sym_link_path: [*:0]const u16, - target_path: [*:0]const u16, - flags: DWORD, + dir: ?HANDLE, + sym_link_path: [:0]const u16, + target_path: [:0]const u16, + is_directory: bool, ) CreateSymbolicLinkError!void { - if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) { - switch (kernel32.GetLastError()) { - else => |err| return unexpectedError(err), + const SYMLINK_DATA = extern struct { + ReparseTag: ULONG, + ReparseDataLength: USHORT, + Reserved: USHORT, + SubstituteNameOffset: USHORT, + SubstituteNameLength: USHORT, + PrintNameOffset: USHORT, + PrintNameLength: USHORT, + Flags: ULONG, + }; + + var symlink_handle: HANDLE = undefined; + if (is_directory) { + const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) { + error.Overflow => return error.NameTooLong, + }; + var nt_name = UNICODE_STRING{ + .Length = sym_link_len_bytes, + .MaximumLength = sym_link_len_bytes, + .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)), + }; + + if (sym_link_path[0] == '.' and sym_link_path[1] == 0) { + // Windows does not recognize this, but it does work with empty string. + nt_name.Length = 0; } + + var attr = OBJECT_ATTRIBUTES{ + .Length = @sizeOf(OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir, + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + + var io: IO_STATUS_BLOCK = undefined; + const rc = ntdll.NtCreateFile( + &symlink_handle, + GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, + &attr, + &io, + null, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_CREATE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, + null, + 0, + ); + switch (rc) { + .SUCCESS => {}, + .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NO_MEDIA_IN_DEVICE => return error.NoDevice, + .INVALID_PARAMETER => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + else => return unexpectedStatus(rc), + } + } else { + symlink_handle = OpenFile(sym_link_path, .{ + .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, + .dir = dir, + .creation = FILE_CREATE, + .io_mode = .blocking, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, + error.IsDir => return error.PathAlreadyExists, + error.PipeBusy => unreachable, + error.SharingViolation => return error.AccessDenied, + else => |e| return e, + }; } + defer CloseHandle(symlink_handle); + + // prepare reparse data buffer + var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; + const buf_len = @sizeOf(SYMLINK_DATA) + target_path.len * 4; + const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2; + const symlink_data = SYMLINK_DATA{ + .ReparseTag = IO_REPARSE_TAG_SYMLINK, + .ReparseDataLength = @intCast(u16, buf_len - header_len), + .Reserved = 0, + .SubstituteNameOffset = @intCast(u16, target_path.len * 2), + .SubstituteNameLength = @intCast(u16, target_path.len * 2), + .PrintNameOffset = 0, + .PrintNameLength = @intCast(u16, target_path.len * 2), + .Flags = if (dir) |_| SYMLINK_FLAG_RELATIVE else 0, + }; + + std.mem.copy(u8, buffer[0..], std.mem.asBytes(&symlink_data)); + @memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2); + const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2; + @memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2); + // TODO replace with NtDeviceIoControl + _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); +} + +pub const ReadLinkError = error{ + FileNotFound, + AccessDenied, + Unexpected, + NameTooLong, + UnsupportedReparsePointType, + InvalidUtf8, + BadPathName, +}; + +pub fn ReadLink( + dir: ?HANDLE, + sub_path: []const u8, + out_buffer: []u8, +) ReadLinkError![]u8 { + const sub_path_w = try sliceToPrefixedFileW(sub_path); + return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer); +} + +pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { + const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { + error.Overflow => return error.NameTooLong, + }; + var nt_name = UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), + }; + + if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { + // Windows does not recognize this, but it does work with empty string. + nt_name.Length = 0; + } + + var attr = OBJECT_ATTRIBUTES{ + .Length = @sizeOf(OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io: IO_STATUS_BLOCK = undefined; + var result_handle: HANDLE = undefined; + const rc = ntdll.NtCreateFile( + &result_handle, + FILE_READ_ATTRIBUTES, + &attr, + &io, + null, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_OPEN_REPARSE_POINT, + null, + 0, + ); + switch (rc) { + .SUCCESS => {}, + .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NO_MEDIA_IN_DEVICE => return error.FileNotFound, + .INVALID_PARAMETER => unreachable, + .SHARING_VIOLATION => return error.AccessDenied, + .ACCESS_DENIED => return error.AccessDenied, + .PIPE_BUSY => return error.AccessDenied, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .OBJECT_NAME_COLLISION => unreachable, + .FILE_IS_A_DIRECTORY => unreachable, + else => return unexpectedStatus(rc), + } + defer CloseHandle(result_handle); + + var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; + _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null); + + const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); + switch (reparse_struct.ReparseTag) { + IO_REPARSE_TAG_SYMLINK => { + const buf = @ptrCast(*const SYMBOLIC_LINK_REPARSE_BUFFER, @alignCast(@alignOf(SYMBOLIC_LINK_REPARSE_BUFFER), &reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; + return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer); + }, + IO_REPARSE_TAG_MOUNT_POINT => { + const buf = @ptrCast(*const MOUNT_POINT_REPARSE_BUFFER, @alignCast(@alignOf(MOUNT_POINT_REPARSE_BUFFER), &reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer); + }, + else => |value| { + std.debug.warn("unsupported symlink type: {}", .{value}); + return error.UnsupportedReparsePointType; + }, + } +} + +fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { + const prefix = [_]u16{ '\\', '?', '?', '\\' }; + var start_index: usize = 0; + if (!is_relative and std.mem.startsWith(u16, path, &prefix)) { + start_index = prefix.len; + } + const out_len = std.unicode.utf16leToUtf8(out_buffer, path[start_index..]) catch unreachable; + return out_buffer[0..out_len]; } pub const DeleteFileError = error{ @@ -1239,23 +1456,30 @@ pub const PathSpace = struct { } }; +/// Same as `sliceToPrefixedFileW` but accepts a pointer +/// to a null-terminated path. pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { return sliceToPrefixedFileW(mem.spanZ(s)); } +/// Converts the path `s` to WTF16, null-terminated. If the path is absolute, +/// it will get NT-style prefix `\??\` prepended automatically. For prepending +/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { // TODO https://github.com/ziglang/zig/issues/2765 var path_space: PathSpace = undefined; - for (s) |byte| { + const prefix = "\\??\\"; + const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0; + for (s[prefix_index..]) |byte| { switch (byte) { '*', '?', '"', '<', '>', '|' => return error.BadPathName, else => {}, } } - const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: { - const prefix = [_]u16{ '\\', '?', '?', '\\' }; - mem.copy(u16, path_space.data[0..], &prefix); - break :blk prefix.len; + const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { + const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; + mem.copy(u16, path_space.data[0..], prefix_u16[0..]); + break :blk prefix_u16.len; }; path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); if (path_space.len > path_space.data.len) return error.NameTooLong; @@ -1287,6 +1511,12 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { path_space.len = start_index + s.len; if (path_space.len > path_space.data.len) return error.NameTooLong; mem.copy(u16, path_space.data[start_index..], s); + // > File I/O functions in the Windows API convert "/" to "\" as part of + // > converting the name to an NT-style name, except when using the "\\?\" + // > prefix as detailed in the following sections. + // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation + // Because we want the larger maximum path length for absolute paths, we + // convert forward slashes to backward slashes here. for (path_space.data[0..path_space.len]) |*elem| { if (elem.* == '/') { elem.* = '\\'; diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index a6a9e50d53d4..9f50570e3e46 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -488,7 +488,7 @@ pub const FILE_OPEN_BY_FILE_ID = 0x00002000; pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000; pub const FILE_NO_COMPRESSION = 0x00008000; pub const FILE_RESERVE_OPFILTER = 0x00100000; -pub const FILE_TRANSACTED_MODE = 0x00200000; +pub const FILE_OPEN_REPARSE_POINT = 0x00200000; pub const FILE_OPEN_OFFLINE_FILE = 0x00400000; pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000; @@ -1542,3 +1542,34 @@ pub const POSVERSIONINFOW = *OSVERSIONINFOW; pub const LPOSVERSIONINFOW = *OSVERSIONINFOW; pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW; pub const PRTL_OSVERSIONINFOW = *RTL_OSVERSIONINFOW; + +pub const REPARSE_DATA_BUFFER = extern struct { + ReparseTag: ULONG, + ReparseDataLength: USHORT, + Reserved: USHORT, + DataBuffer: [1]UCHAR, +}; +pub const SYMBOLIC_LINK_REPARSE_BUFFER = extern struct { + SubstituteNameOffset: USHORT, + SubstituteNameLength: USHORT, + PrintNameOffset: USHORT, + PrintNameLength: USHORT, + Flags: ULONG, + PathBuffer: [1]WCHAR, +}; +pub const MOUNT_POINT_REPARSE_BUFFER = extern struct { + SubstituteNameOffset: USHORT, + SubstituteNameLength: USHORT, + PrintNameOffset: USHORT, + PrintNameLength: USHORT, + PathBuffer: [1]WCHAR, +}; +pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024; +pub const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4; +pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8; +pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c; +pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003; +pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1; + +pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; +pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2; diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index af494efbab2d..8e4f92e5eec7 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -552,6 +552,9 @@ pub const NativeTargetInfo = struct { error.SystemResources => return error.SystemResources, error.NotDir => return error.GnuLibCVersionUnavailable, error.Unexpected => return error.GnuLibCVersionUnavailable, + error.InvalidUtf8 => unreachable, // Windows only + error.BadPathName => unreachable, // Windows only + error.UnsupportedReparsePointType => unreachable, // Windows only }; return glibcVerFromLinkName(link_name); } @@ -817,6 +820,9 @@ pub const NativeTargetInfo = struct { &link_buf, ) catch |err| switch (err) { error.NameTooLong => unreachable, + error.InvalidUtf8 => unreachable, // Windows only + error.BadPathName => unreachable, // Windows only + error.UnsupportedReparsePointType => unreachable, // Windows only error.AccessDenied, error.FileNotFound,