Commit c47cb8d09f

Jakub Konka <kubkon@jakubkonka.com>
2020-07-14 23:30:05
Fix unlinkatW to allow file symlink deletion on Windows
1 parent ae8abed
Changed files (3)
lib
std
lib/std/os/windows/bits.zig
@@ -1549,7 +1549,7 @@ pub const REPARSE_DATA_BUFFER = extern struct {
     Reserved: USHORT,
     DataBuffer: [1]UCHAR,
 };
-pub const SymbolicLinkReparseBuffer = extern struct {
+pub const SYMBOLIC_LINK_REPARSE_BUFFER = extern struct {
     SubstituteNameOffset: USHORT,
     SubstituteNameLength: USHORT,
     PrintNameOffset: USHORT,
@@ -1557,7 +1557,7 @@ pub const SymbolicLinkReparseBuffer = extern struct {
     Flags: ULONG,
     PathBuffer: [1]WCHAR,
 };
-pub const MountPointReparseBuffer = extern struct {
+pub const MOUNT_POINT_REPARSE_BUFFER = extern struct {
     SubstituteNameOffset: USHORT,
     SubstituteNameLength: USHORT,
     PrintNameOffset: USHORT,
lib/std/os/test.zig
@@ -43,42 +43,51 @@ test "fstatat" {
 
 test "readlink" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
-    
-    var cwd = fs.cwd();
-    try cwd.writeFile("file.txt", "nonsense");
-    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));
-
-    // var tmp = tmpDir(.{});
-    // defer tmp.cleanup();
-
-    // // create file
-    // try tmp.dir.writeFile("file.txt", "nonsense");
-
-    // // get paths
-    // // TODO: use Dir's realpath function once that exists
-    // 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 target_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "file.txt"});
-    // const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{base_path, "symlinked"});
-    // std.debug.warn("\ntarget_path={}\n", .{target_path});
-    // std.debug.warn("symlink_path={}\n", .{symlink_path});
-
-    // // create symbolic link by path
-    // try os.symlink(target_path, symlink_path);
-
-    // // now, read the link and verify
-    // var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
-    // const given = try os.readlink(symlink_path, buffer[0..]);
-    // expect(mem.eql(u8, symlink_path, given));
+    // First, try relative paths
+    {
+        var cwd = fs.cwd();
+        try cwd.writeFile("file.txt", "nonsense");
+        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");
+    }
+
+    // Next, let's try fully-qualified paths
+    {
+        var tmp = tmpDir(.{});
+        // defer tmp.cleanup();
+
+        // create file
+        try tmp.dir.writeFile("file.txt", "nonsense");
+
+        // get paths
+        // TODO: use Dir's realpath function once that exists
+        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 target_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "file.txt" });
+        const symlink_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "symlinked" });
+        std.debug.warn("\ntarget_path={}\n", .{target_path});
+        std.debug.warn("symlink_path={}\n", .{symlink_path});
+
+        // create symbolic link by path
+        try os.symlink(target_path, symlink_path);
+
+        // now, read the link and verify
+        var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
+        const given = try os.readlink(symlink_path, buffer[0..]);
+        expect(mem.eql(u8, symlink_path, given));
+    }
 }
 
 test "readlinkat" {
lib/std/os.zig
@@ -1834,7 +1834,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr
     const create_options_flags = if (want_rmdir_behavior)
         @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE)
     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{
@@ -2371,7 +2371,7 @@ pub const ReadLinkError = error{
     InvalidUtf8,
     BadPathName,
     /// Windows-only.
-    UnsupportedSymlinkType,
+    UnsupportedReparsePointType,
 } || UnexpectedError;
 
 /// Read value of a symbolic link.
@@ -2412,7 +2412,7 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
     const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, @alignCast(@alignOf(w.REPARSE_DATA_BUFFER), &reparse_buf[0]));
     switch (reparse_struct.ReparseTag) {
         w.IO_REPARSE_TAG_SYMLINK => {
-            const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(@alignOf(w.SymbolicLinkReparseBuffer), &reparse_struct.DataBuffer[0]));
+            const buf = @ptrCast(*const w.SYMBOLIC_LINK_REPARSE_BUFFER, @alignCast(@alignOf(w.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);
@@ -2420,7 +2420,7 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
             return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer);
         },
         w.IO_REPARSE_TAG_MOUNT_POINT => {
-            const buf = @ptrCast(*const w.MountPointReparseBuffer, @alignCast(@alignOf(w.MountPointReparseBuffer), &reparse_struct.DataBuffer[0]));
+            const buf = @ptrCast(*const w.MOUNT_POINT_REPARSE_BUFFER, @alignCast(@alignOf(w.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);
@@ -2428,7 +2428,7 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
         },
         else => |value| {
             std.debug.warn("unsupported symlink type: {}", .{value});
-            return error.UnsupportedSymlinkType;
+            return error.UnsupportedReparsePointType;
         },
     }
 }