Commit ac85befbb4

Sahnvour <sahnvour@pm.me>
2020-08-27 20:55:47
handle lack of privilege to create symbolic links on windows
1 parent e355bcc
Changed files (3)
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);
     }
 }
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");
     }
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(