From 791795a63a34bc69af59717d5eff5d4c76349756 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 13 Jul 2020 08:29:11 +0200 Subject: [PATCH] Finish symlink implementation on Windows --- lib/std/os.zig | 15 ++++++++++++++- lib/std/os/windows.zig | 35 +++++++++++++++++++++++++++++++---- lib/std/os/windows/bits.zig | 4 ++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index ab54ba5fe8..67b7dfe05b 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1541,6 +1541,10 @@ pub const SymLinkError = error{ /// 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. +/// On Windows, it is only legal to create a symbolic link to an existing resource. Furthermore, +/// 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 instead, see `symlinkW` for more information how to +/// do that. /// 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 { @@ -1550,7 +1554,7 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! 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); + return symlinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr); } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); @@ -1559,6 +1563,15 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); +/// Windows-only. Same as `symlink` 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, use `std.os.windows.CreateSymbolicLinkW` directly +/// specifying as flags `std.os.windows.CreateSymbolicLinkFlags.Directory`. +pub fn symlinkW(target_path: [*:0]const u16, sym_link_path: [*:0]const u16) SymLinkError!void { + const flags = windows.CreateSymbolicLinkFlags.File; + return windows.CreateSymbolicLinkW(sym_link_path, target_path, flags); +} + /// This is the same as `symlink` except the parameters are null-terminated pointers. /// See also `symlink`. pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 1ed1ef1f54..5439bc0df9 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -601,12 +601,17 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const CreateSymbolicLinkError = error{Unexpected}; +pub const CreateSymbolicLinkError = error{AccessDenied, FileNotFound, Unexpected}; + +pub const CreateSymbolicLinkFlags = enum(DWORD) { + File = SYMBOLIC_LINK_FLAG_FILE, + Directory = SYMBOLIC_LINK_FLAG_DIRECTORY, +}; pub fn CreateSymbolicLink( sym_link_path: []const u8, target_path: []const u8, - flags: DWORD, + flags: CreateSymbolicLinkFlags, ) CreateSymbolicLinkError!void { const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); const target_path_w = try sliceToPrefixedFileW(target_path); @@ -616,10 +621,32 @@ pub fn CreateSymbolicLink( pub fn CreateSymbolicLinkW( sym_link_path: [*:0]const u16, target_path: [*:0]const u16, - flags: DWORD, + flags: CreateSymbolicLinkFlags, ) CreateSymbolicLinkError!void { - if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) { + // 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. + if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, @enumToInt(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, @enumToInt(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, + 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, else => |err| return unexpectedError(err), } } diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 998d4a8091..715a3cce7e 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1569,3 +1569,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; 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 SYMBOLIC_LINK_FLAG_FILE: DWORD = 0x0; +pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; +pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2; \ No newline at end of file