Commit ae8abedbed

Jakub Konka <kubkon@jakubkonka.com>
2020-07-14 23:06:55
Use NtCreateFile to get handle to reparse point
1 parent d17c9b3
Changed files (4)
lib/std/os/windows/bits.zig
@@ -1568,7 +1568,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 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 SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
+pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x00000001;
 
 pub const SYMBOLIC_LINK_FLAG_FILE: DWORD = 0x0;
 pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
lib/std/os/test.zig
@@ -43,32 +43,42 @@ 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 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"});
-
-    // 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));
+    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));
 }
 
 test "readlinkat" {
lib/std/os/windows.zig
@@ -1370,4 +1370,74 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
         std.debug.dumpCurrentStackTrace(null);
     }
     return error.Unexpected;
+}
+
+pub const OpenAsReparsePointError = error {
+    FileNotFound,
+    NoDevice,
+    SharingViolation,
+    AccessDenied,
+    PipeBusy,
+    PathAlreadyExists,
+    Unexpected,
+    NameTooLong,
+};
+
+/// Open file as a reparse point
+pub fn OpenAsReparsePoint(
+    dir: ?HANDLE,
+    sub_path_w: [*:0]const u16,
+) OpenAsReparsePointError!HANDLE {
+    const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
+        error.Overflow => return error.NameTooLong,
+    };
+    var nt_name = UNICODE_STRING{
+        .Length = path_len_bytes,
+        .MaximumLength = path_len_bytes,
+        .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+    };
+
+    if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
+        // Windows does not recognize this, but it does work with empty string.
+        nt_name.Length = 0;
+    }
+
+    var attr = OBJECT_ATTRIBUTES{
+        .Length = @sizeOf(OBJECT_ATTRIBUTES),
+        .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
+        .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+        .ObjectName = &nt_name,
+        .SecurityDescriptor = null,
+        .SecurityQualityOfService = null,
+    };
+    var io: IO_STATUS_BLOCK = undefined;
+    var result_handle: HANDLE = undefined;
+    const rc = ntdll.NtCreateFile(
+        &result_handle,
+        FILE_READ_ATTRIBUTES,
+        &attr,
+        &io,
+        null,
+        FILE_ATTRIBUTE_NORMAL,
+        FILE_SHARE_READ,
+        FILE_OPEN,
+        FILE_OPEN_REPARSE_POINT,
+        null,
+        0,
+    );
+    switch (rc) {
+        .SUCCESS => return result_handle,
+        .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,
+        .INVALID_PARAMETER => unreachable,
+        .SHARING_VIOLATION => return error.SharingViolation,
+        .ACCESS_DENIED => return error.AccessDenied,
+        .PIPE_BUSY => return error.PipeBusy,
+        .OBJECT_PATH_SYNTAX_BAD => unreachable,
+        .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
+        .FILE_IS_A_DIRECTORY => unreachable,
+        else => return unexpectedStatus(rc),
+    }
 }
\ No newline at end of file
lib/std/os.zig
@@ -2394,36 +2394,37 @@ pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
 /// See also `readlinkZ`.
 pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
     const w = windows;
-    const sharing = w.FILE_SHARE_DELETE | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE;
-    const disposition = w.OPEN_EXISTING;
-    const flags = w.FILE_FLAG_BACKUP_SEMANTICS | w.FILE_FLAG_OPEN_REPARSE_POINT;
-    const handle = w.CreateFileW(file_path, 0, sharing, null, disposition, flags, null) catch |err| {
+
+    const dir = if (std.fs.path.isAbsoluteWindowsW(file_path)) null else std.fs.cwd().fd;
+    const handle = w.OpenAsReparsePoint(dir, file_path) catch |err| {
         switch (err) {
             error.SharingViolation => return error.AccessDenied,
-            error.PathAlreadyExists => unreachable,
             error.PipeBusy => unreachable,
+            error.PathAlreadyExists => unreachable,
+            error.NoDevice => return error.FileNotFound,
             else => |e| return e,
         }
     };
-    var reparse_buf align(@alignOf(w.REPARSE_DATA_BUFFER)) = [_]u8{0} ** (w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+
+    var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
     _ = try w.DeviceIoControl(handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
-    const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, &reparse_buf[0]);
+    // std.debug.warn("\n\n{x}\n\n", .{reparse_buf});
+    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 alignment = @alignOf(w.SymbolicLinkReparseBuffer);
-            const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(alignment, &reparse_struct.DataBuffer[0]));
-            const offset = buf.SubstituteNameOffset / 2;
-            const len = buf.SubstituteNameLength / 2;
-            const f = buf.Flags;
+            const buf = @ptrCast(*const w.SymbolicLinkReparseBuffer, @alignCast(@alignOf(w.SymbolicLinkReparseBuffer), &reparse_struct.DataBuffer[0]));
+            const offset = buf.SubstituteNameOffset >> 1;
+            const len = buf.SubstituteNameLength >> 1;
             const path_buf = @as([*]const u16, &buf.PathBuffer);
-            std.debug.warn("got symlink => offset={}, len={}, flags = {}, {}\n", .{ offset, len, f, w.SYMLINK_FLAG_RELATIVE });
-            // TODO handle absolute paths and namespace prefix
-            const out_len = std.unicode.utf16leToUtf8(out_buffer, path_buf[offset .. offset + len]) catch unreachable;
-            std.debug.warn("got symlink => utf8={}\n", .{out_buffer[0..out_len]});
-            return out_buffer[0..out_len];
+            const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0;
+            return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer);
         },
         w.IO_REPARSE_TAG_MOUNT_POINT => {
-            @panic("TODO parse mount point");
+            const buf = @ptrCast(*const w.MountPointReparseBuffer, @alignCast(@alignOf(w.MountPointReparseBuffer), &reparse_struct.DataBuffer[0]));
+            const offset = buf.SubstituteNameOffset >> 1;
+            const len = buf.SubstituteNameLength >> 1;
+            const path_buf = @as([*]const u16, &buf.PathBuffer);
+            return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer);
         },
         else => |value| {
             std.debug.warn("unsupported symlink type: {}", .{value});
@@ -2432,6 +2433,13 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
     }
 }
 
+fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
+    const out_len = std.unicode.utf16leToUtf8(out_buffer, path) catch unreachable;
+    std.debug.warn("got symlink => utf8={}\n", .{out_buffer[0..out_len]});
+    // TODO handle absolute paths and namespace prefix '/??/'
+    return out_buffer[0..out_len];
+}
+
 /// Same as `readlink` except `file_path` is null-terminated.
 pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
     if (builtin.os.tag == .windows) {