diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2020-07-21 19:22:22 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2020-07-22 08:51:23 +0200 |
| commit | 3d41d3fb6e875192ab5f859ca4ef95c894df7fe4 (patch) | |
| tree | 7df319939d7cce48ff36cb1d8f41928cc3bcd30d /lib/std | |
| parent | 4887350bf420db3193569e8adb30937e6379db0c (diff) | |
| download | zig-3d41d3fb6e875192ab5f859ca4ef95c894df7fe4.tar.gz zig-3d41d3fb6e875192ab5f859ca4ef95c894df7fe4.zip | |
Draft out ReadLinkW using NT primitives
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/os.zig | 71 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 268 |
2 files changed, 144 insertions, 195 deletions
diff --git a/lib/std/os.zig b/lib/std/os.zig index 36f7669f66..71bf34acfe 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2364,8 +2364,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .wasi) { @compileError("readlink is not supported in WASI; use readlinkat instead"); } else if (builtin.os.tag == .windows) { - const file_path_w = try windows.sliceToWin32PrefixedFileW(file_path); - return readlinkW(file_path_w.span().ptr, out_buffer); + return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); } else { const file_path_c = try toPosixPath(file_path); return readlinkZ(&file_path_c, out_buffer); @@ -2377,56 +2376,7 @@ pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); /// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded. /// 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| { - switch (err) { - error.SharingViolation => return error.AccessDenied, - error.PipeBusy => unreachable, - error.PathAlreadyExists => unreachable, - else => |e| return e, - } - }; - defer w.CloseHandle(handle); - - 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, @alignCast(@alignOf(w.REPARSE_DATA_BUFFER), &reparse_buf[0])); - switch (reparse_struct.ReparseTag) { - w.IO_REPARSE_TAG_SYMLINK => { - 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); - 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 => { - 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); - return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer); - }, - else => |value| { - std.debug.warn("unsupported symlink type: {}", .{value}); - return error.UnsupportedReparsePointType; - }, - } -} - -fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { - const prefix = [_]u16{ '\\', '?', '?', '\\' }; - var start_index: usize = 0; - if (!is_relative and mem.startsWith(u16, path, &prefix)) { - start_index = prefix.len; - } - const out_len = std.unicode.utf16leToUtf8(out_buffer, path[start_index..]) catch unreachable; - return out_buffer[0..out_len]; + return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer); } /// Same as `readlink` except `file_path` is null-terminated. @@ -2459,8 +2409,7 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink return readlinkatWasi(dirfd, file_path, out_buffer); } if (builtin.os.tag == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(file_path); - return readlinkatW(dirfd, file_path.span().ptr, out_buffer); + return windows.ReadLink(dirfd, file_path, out_buffer); } const file_path_c = try toPosixPath(file_path); return readlinkatZ(dirfd, &file_path_c, out_buffer); @@ -2491,19 +2440,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. /// See also `readlinkat`. pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { - const w = windows; - - const handle = w.OpenReparsePoint(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, - } - }; - @compileError("TODO implement on Windows"); + return windows.ReadLinkW(dirfd, file_path, out_buffer); } /// Same as `readlinkat` except `file_path` is null-terminated. diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 219b07dbc0..ab549a0ebf 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -731,6 +731,117 @@ pub fn CreateSymbolicLinkW( _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); } +pub const ReadLinkError = error{ + FileNotFound, + AccessDenied, + Unexpected, + NameTooLong, + UnsupportedReparsePointType, + InvalidUtf8, + BadPathName, +}; + +pub fn ReadLink( + dir: ?HANDLE, + sub_path: []const u8, + out_buffer: []u8, +) ReadLinkError![]u8 { + const sub_path_w = try sliceToPrefixedFileW(sub_path); + return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer); +} + +pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { + 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 => {}, + .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NO_MEDIA_IN_DEVICE => return error.FileNotFound, + .INVALID_PARAMETER => unreachable, + .SHARING_VIOLATION => return error.AccessDenied, + .ACCESS_DENIED => return error.AccessDenied, + .PIPE_BUSY => return error.AccessDenied, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .OBJECT_NAME_COLLISION => unreachable, + .FILE_IS_A_DIRECTORY => unreachable, + else => return unexpectedStatus(rc), + } + 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..], null); + + const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); + switch (reparse_struct.ReparseTag) { + IO_REPARSE_TAG_SYMLINK => { + const buf = @ptrCast(*const SYMBOLIC_LINK_REPARSE_BUFFER, @alignCast(@alignOf(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); + const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; + return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer); + }, + IO_REPARSE_TAG_MOUNT_POINT => { + const buf = @ptrCast(*const MOUNT_POINT_REPARSE_BUFFER, @alignCast(@alignOf(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); + return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer); + }, + else => |value| { + std.debug.warn("unsupported symlink type: {}", .{value}); + return error.UnsupportedReparsePointType; + }, + } +} + +fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { + const prefix = [_]u16{ '\\', '?', '?', '\\' }; + var start_index: usize = 0; + if (!is_relative and std.mem.startsWith(u16, path, &prefix)) { + start_index = prefix.len; + } + const out_len = std.unicode.utf16leToUtf8(out_buffer, path[start_index..]) catch unreachable; + return out_buffer[0..out_len]; +} + pub const DeleteFileError = error{ FileNotFound, AccessDenied, @@ -1343,21 +1454,6 @@ pub const PathSpace = struct { pub fn span(self: PathSpace) [:0]const u16 { return self.data[0..self.len :0]; } - - fn ensureNtStyle(self: *PathSpace) void { - // > File I/O functions in the Windows API convert "/" to "\" as part of - // > converting the name to an NT-style name, except when using the "\\?\" - // > prefix as detailed in the following sections. - // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation - // Because we want the larger maximum path length for absolute paths, we - // convert forward slashes to backward slashes here. - for (self.data[0..self.len]) |*elem| { - if (elem.* == '/') { - elem.* = '\\'; - } - } - self.data[self.len] = 0; - } }; /// Same as `sliceToPrefixedFileW` but accepts a pointer @@ -1366,50 +1462,14 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { return sliceToPrefixedFileW(mem.spanZ(s)); } -/// Same as `sliceToWin32PrefixedFileW` but accepts a pointer -/// to a null-terminated path. -pub fn cStrToWin32PrefixedFileW(s: [*:0]const u8) !PathSpace { - return sliceToWin32PrefixedFileW(mem.spanZ(s)); -} - /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, /// it will get NT-style prefix `\??\` prepended automatically. For prepending /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { - return sliceToPrefixedFileWInternal(s, PathPrefix.Nt); -} - -/// Converts the path `s` to WTF16, null-terminated. If the path is absolute, -/// it will get Win32-style extended prefix `\\?\` prepended automatically. For prepending -/// NT-style prefix, see `sliceToPrefixedFileW` instead. -pub fn sliceToWin32PrefixedFileW(s: []const u8) !PathSpace { - return sliceToPrefixedFileWInternal(s, PathPrefix.Win32); -} - -const PathPrefix = enum { - Win32, - Nt, - - fn toUtf8(self: PathPrefix) []const u8 { - return switch (self) { - .Win32 => "\\\\?\\", - .Nt => "\\??\\", - }; - } - - fn toUtf16(self: PathPrefix) []const u16 { - return switch (self) { - .Win32 => &[_]u16{ '\\', '\\', '?', '\\' }, - .Nt => &[_]u16{ '\\', '?', '?', '\\' }, - }; - } -}; - -fn sliceToPrefixedFileWInternal(s: []const u8, prefix: PathPrefix) !PathSpace { // TODO https://github.com/ziglang/zig/issues/2765 var path_space: PathSpace = undefined; - const prefix_utf8 = prefix.toUtf8(); - const prefix_index: usize = if (mem.startsWith(u8, s, prefix_utf8)) prefix_utf8.len else 0; + const prefix = "\\??\\"; + const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0; for (s[prefix_index..]) |byte| { switch (byte) { '*', '?', '"', '<', '>', '|' => return error.BadPathName, @@ -1417,13 +1477,24 @@ fn sliceToPrefixedFileWInternal(s: []const u8, prefix: PathPrefix) !PathSpace { } } const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { - const prefix_utf16 = prefix.toUtf16(); - mem.copy(u16, path_space.data[0..], prefix_utf16); - break :blk prefix_utf16.len; + const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; + mem.copy(u16, path_space.data[0..], prefix_u16[0..]); + break :blk prefix_u16.len; }; path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); if (path_space.len > path_space.data.len) return error.NameTooLong; - path_space.ensureNtStyle(); + // > File I/O functions in the Windows API convert "/" to "\" as part of + // > converting the name to an NT-style name, except when using the "\\?\" + // > prefix as detailed in the following sections. + // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation + // Because we want the larger maximum path length for absolute paths, we + // convert forward slashes to backward slashes here. + for (path_space.data[0..path_space.len]) |*elem| { + if (elem.* == '/') { + elem.* = '\\'; + } + } + path_space.data[path_space.len] = 0; return path_space; } @@ -1440,7 +1511,18 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { path_space.len = start_index + s.len; if (path_space.len > path_space.data.len) return error.NameTooLong; mem.copy(u16, path_space.data[start_index..], s); - path_space.ensureNtStyle(); + // > File I/O functions in the Windows API convert "/" to "\" as part of + // > converting the name to an NT-style name, except when using the "\\?\" + // > prefix as detailed in the following sections. + // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation + // Because we want the larger maximum path length for absolute paths, we + // convert forward slashes to backward slashes here. + for (path_space.data[0..path_space.len]) |*elem| { + if (elem.* == '/') { + elem.* = '\\'; + } + } + path_space.data[path_space.len] = 0; return path_space; } @@ -1484,73 +1566,3 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { } return error.Unexpected; } - -pub const OpenReparsePointError = error{ - FileNotFound, - NoDevice, - SharingViolation, - AccessDenied, - PipeBusy, - PathAlreadyExists, - Unexpected, - NameTooLong, -}; - -/// Open file as a reparse point -pub fn OpenReparsePoint( - dir: ?HANDLE, - sub_path_w: [*:0]const u16, -) OpenReparsePointError!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), - } -} |
