diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 6e0a9defd7..830a4ac7cd 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -66,15 +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 { - const res = blk: { - // TODO this is just a temporary until Dir.symLink is implemented on Windows - if (builtin.os.tag == .windows) { - break :blk os.windows.CreateSymbolicLink(new_path, existing_path, false); - } else { - break :blk cwd().symLink(existing_path, new_path, .{}); - } - }; - if (res) { + if (cwd().symLink(existing_path, new_path, .{})) { return; } else |err| switch (err) { error.PathAlreadyExists => {}, @@ -92,15 +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); - const res2 = blk: { - // TODO this is just a temporary until Dir.symLink is implemented on Windows - if (builtin.os.tag == .windows) { - break :blk os.windows.CreateSymbolicLink(tmp_path, existing_path, false); - } else { - break :blk cwd().symLink(existing_path, new_path, .{}); - } - }; - if (res2) { + if (cwd().symLink(existing_path, new_path, .{})) { return rename(tmp_path, new_path); } else |err| switch (err) { error.PathAlreadyExists => continue, @@ -1239,10 +1223,12 @@ pub const Dir = struct { flags: SymLinkFlags, ) !void { if (builtin.os.tag == .wasi) { - return self.symLinkWasi(target_path, sym_link_path); + return self.symLinkWasi(target_path, sym_link_path, flags); } if (builtin.os.tag == .windows) { - @compileError("TODO implement Dir.symLink on 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); @@ -1250,15 +1236,41 @@ pub const Dir = struct { } /// 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 { + 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 { + 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. @@ -1761,10 +1773,10 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]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 `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. +/// 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, }; @@ -1781,7 +1793,7 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags assert(path.isAbsolute(target_path)); assert(path.isAbsolute(sym_link_path)); if (builtin.os.tag == .windows) { - return os.windows.CreateSymbolicLink(sym_link_path, target_path, flags.is_directory); + return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory); } return os.symlink(target_path, sym_link_path); } @@ -1790,10 +1802,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags /// 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 { +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(sym_link_path_w, target_path_w, flags.is_directory); + 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. diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index c95af79e02..bb0893ab4c 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -27,7 +27,7 @@ test "symlink with relative paths" { try cwd.writeFile("file.txt", "nonsense"); if (builtin.os.tag == .windows) { - try os.windows.CreateSymbolicLink("symlinked", "file.txt", false); + try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false); } else { try os.symlink("file.txt", "symlinked"); } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index f3f5eca450..219b07dbc0 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -602,9 +602,34 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const CreateSymbolicLinkError = error{ AccessDenied, PathAlreadyExists, FileNotFound, NameTooLong, InvalidUtf8, BadPathName, Unexpected }; +pub const CreateSymbolicLinkError = error{ + AccessDenied, + PathAlreadyExists, + FileNotFound, + NameTooLong, + InvalidUtf8, + BadPathName, + NoDevice, + Unexpected, +}; -pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_path: [:0]const u16, is_directory: bool) CreateSymbolicLinkError!void { +pub fn CreateSymbolicLink( + dir: ?HANDLE, + sym_link_path: []const u8, + target_path: []const u8, + 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(dir, sym_link_path_w.span(), target_path_w.span(), is_directory); +} + +pub fn CreateSymbolicLinkW( + dir: ?HANDLE, + sym_link_path: [:0]const u16, + target_path: [:0]const u16, + is_directory: bool, +) CreateSymbolicLinkError!void { const SYMLINK_DATA = extern struct { ReparseTag: ULONG, ReparseDataLength: USHORT, @@ -660,7 +685,7 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_ .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, + .NO_MEDIA_IN_DEVICE => return error.NoDevice, .INVALID_PARAMETER => unreachable, .ACCESS_DENIED => return error.AccessDenied, .OBJECT_PATH_SYNTAX_BAD => unreachable, @@ -674,7 +699,11 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_ .creation = FILE_CREATE, .io_mode = .blocking, }) catch |err| switch (err) { - else => |e| unreachable, + error.WouldBlock => unreachable, + error.IsDir => return error.PathAlreadyExists, + error.PipeBusy => unreachable, + error.SharingViolation => return error.AccessDenied, + else => |e| return e, }; } defer CloseHandle(symlink_handle); @@ -702,53 +731,6 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_ _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); } -pub fn CreateSymbolicLink( - sym_link_path: []const u8, - target_path: []const u8, - is_directory: bool, -) CreateSymbolicLinkError!void { - const sym_link_path_w = try sliceToWin32PrefixedFileW(sym_link_path); - const target_path_w = try sliceToWin32PrefixedFileW(target_path); - return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, is_directory); -} - -pub fn CreateSymbolicLinkW( - sym_link_path: [*:0]const u16, - target_path: [*:0]const u16, - is_directory: bool, -) CreateSymbolicLinkError!void { - // Previously, until Win 10 Creators Update, creating symbolic links required - // SeCreateSymbolicLink privilege. Currently, this is no longer required if the - // OS is in Developer Mode; however, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE - // must be added to the input flags. - const flags = if (is_directory) SYMBOLIC_LINK_FLAG_DIRECTORY else 0; - if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0) { - switch (kernel32.GetLastError()) { - .INVALID_PARAMETER => { - // If we're on Windows pre Creators Update, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE - // flag is an invalid parameter, in which case repeat without the flag. - if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) { - switch (kernel32.GetLastError()) { - .PRIVILEGE_NOT_HELD => return error.AccessDenied, - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - .ALREADY_EXISTS => return error.PathAlreadyExists, - else => |err| return unexpectedError(err), - } - } - return; - }, - .PRIVILEGE_NOT_HELD => return error.AccessDenied, - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - .ALREADY_EXISTS => return error.PathAlreadyExists, - else => |err| return unexpectedError(err), - } - } -} - pub const DeleteFileError = error{ FileNotFound, AccessDenied,