From ac85befbb4356daa774b6083a3aa853b9c78f548 Mon Sep 17 00:00:00 2001 From: Sahnvour Date: Thu, 27 Aug 2020 20:55:47 +0200 Subject: [PATCH] handle lack of privilege to create symbolic links on windows --- lib/std/fs/test.zig | 24 ++++++++++++++++++++---- lib/std/os/test.zig | 26 ++++++++++++++++++++++++-- lib/std/os/windows.zig | 19 ++++++++++++++++--- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 4be64ef225..409a53b1a7 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -25,12 +25,20 @@ test "Dir.readLink" { { // Create symbolic link by path - try tmp.dir.symLink("file.txt", "symlink1", .{}); + tmp.dir.symLink("file.txt", "symlink1", .{}) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => return error.SkipZigTest, + else => return err, + }; try testReadLink(tmp.dir, "file.txt", "symlink1"); } { // Create symbolic link by path - try tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }); + tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => return error.SkipZigTest, + else => return err, + }; try testReadLink(tmp.dir, "subdir", "symlink2"); } } @@ -66,7 +74,11 @@ test "readLinkAbsolute" { 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, .{}); + fs.symLinkAbsolute(target_path, symlink_path, .{}) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => return error.SkipZigTest, + else => return err, + }; try testReadLinkAbsolute(target_path, symlink_path); } { @@ -74,7 +86,11 @@ test "readLinkAbsolute" { 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 }); + fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true }) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => return error.SkipZigTest, + else => return err, + }; try testReadLinkAbsolute(target_path, symlink_path); } } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 93c4ee0403..576125e2a3 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -125,7 +125,20 @@ test "symlink with relative paths" { try cwd.writeFile("file.txt", "nonsense"); if (builtin.os.tag == .windows) { - try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false); + os.windows.CreateSymbolicLink( + cwd.fd, + &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, + &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, + false, + ) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => { + try cwd.deleteFile("file.txt"); + try cwd.deleteFile("symlinked"); + return error.SkipZigTest; + }, + else => return err, + }; } else { try os.symlink("file.txt", "symlinked"); } @@ -183,7 +196,16 @@ test "readlinkat" { // create a symbolic link if (builtin.os.tag == .windows) { - try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false); + os.windows.CreateSymbolicLink( + tmp.dir.fd, + &[_]u16{ 'l', 'i', 'n', 'k' }, + &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, + false, + ) catch |err| switch (err) { + // Symlink requires admin privileges on windows, so this test can legitimately fail. + error.AccessDenied => return error.SkipZigTest, + else => return err, + }; } else { try os.symlinkat("file.txt", tmp.dir.fd, "link"); } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index a23eead14f..bd9dc8b32e 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -164,7 +164,7 @@ pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16, } } -pub const DeviceIoControlError = error{Unexpected}; +pub const DeviceIoControlError = error{ AccessDenied, Unexpected }; /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. /// It implements similar behavior to `DeviceIoControl` and is meant to serve @@ -216,6 +216,7 @@ pub fn DeviceIoControl( }; switch (rc) { .SUCCESS => {}, + .PRIVILEGE_NOT_HELD => return error.AccessDenied, .INVALID_PARAMETER => unreachable, else => return unexpectedStatus(rc), } @@ -593,6 +594,12 @@ pub const CreateSymbolicLinkError = error{ Unexpected, }; +/// Needs either: +/// - `SeCreateSymbolicLinkPrivilege` privilege +/// or +/// - Developper mode on Windows 10 +/// otherwise fails with `error.AccessDenied`. In which case `sym_link_path` may still +/// be created on the file system but will lack reparse processing data applied to it. pub fn CreateSymbolicLink( dir: ?HANDLE, sym_link_path: []const u16, @@ -710,7 +717,10 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin 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..]); + _ = DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) { + error.AccessDenied => unreachable, + else => |e| return e, + }; const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); switch (reparse_struct.ReparseTag) { @@ -992,7 +1002,10 @@ pub fn GetFinalPathNameByHandle( input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength); @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength); - try DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]); + DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) { + error.AccessDenied => unreachable, + else => |e| return e, + }; const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, &output_buf[0]); const mount_points = @ptrCast(