-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
implement nt path conversion for windows #7664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1723,6 +1723,81 @@ pub const PathSpace = struct { | |
| } | ||
| }; | ||
|
|
||
| /// The error type for `removeDotDirsSanitized` | ||
| pub const RemoveDotDirsError = error{TooManyParentDirs}; | ||
|
|
||
| /// Removes '.' and '..' path components from a "sanitized relative path". | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function looks like it belongs in std.fs.path. it might be a duple of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's specific to windows...only works with backslashes. We could also make it work with forward slashes but I think posix has other better ways to perform this operation.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Os-specific code is all over the standard library: it doesn't need to all get dumped into os/windows.zig. |
||
| /// A "sanitized path" is one where: | ||
| /// 1) all forward slashes have been replaced with back slashes | ||
| /// 2) all repeating back slashes have been collapsed | ||
| /// 3) the path is a relative one (does not start with a back slash) | ||
| pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize { | ||
| std.debug.assert(path.len == 0 or path[0] != '\\'); | ||
|
|
||
| var write_idx: usize = 0; | ||
| var read_idx: usize = 0; | ||
| while (read_idx < path.len) { | ||
| if (path[read_idx] == '.') { | ||
| if (read_idx + 1 == path.len) | ||
| return write_idx; | ||
|
|
||
| const after_dot = path[read_idx + 1]; | ||
| if (after_dot == '\\') { | ||
| read_idx += 2; | ||
| continue; | ||
| } | ||
| if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) { | ||
| if (write_idx == 0) return error.TooManyParentDirs; | ||
| std.debug.assert(write_idx >= 2); | ||
| write_idx -= 1; | ||
| while (true) { | ||
| write_idx -= 1; | ||
| if (write_idx == 0) break; | ||
| if (path[write_idx] == '\\') { | ||
| write_idx += 1; | ||
| break; | ||
| } | ||
| } | ||
| if (read_idx + 2 == path.len) | ||
| return write_idx; | ||
| read_idx += 3; | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| // skip to the next path separator | ||
| while (true) : (read_idx += 1) { | ||
| if (read_idx == path.len) | ||
| return write_idx; | ||
| path[write_idx] = path[read_idx]; | ||
| write_idx += 1; | ||
| if (path[read_idx] == '\\') | ||
| break; | ||
| } | ||
| read_idx += 1; | ||
| } | ||
| return write_idx; | ||
| } | ||
|
|
||
| /// Normalizes a Windows path with the following steps: | ||
| /// 1) convert all forward slashes to back slashes | ||
| /// 2) collapse duplicate back slashes | ||
| /// 3) remove '.' and '..' directory parts | ||
| /// Returns the length of the new path. | ||
| pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize { | ||
| mem.replaceScalar(T, path, '/', '\\'); | ||
| const new_len = mem.collapseRepeatsLen(T, path, '\\'); | ||
|
|
||
| const prefix_len: usize = init: { | ||
| if (new_len >= 1 and path[0] == '\\') break :init 1; | ||
| if (new_len >= 2 and path[1] == ':') | ||
| break :init if (new_len >= 3 and path[2] == '\\') @as(usize, 3) else @as(usize, 2); | ||
| break :init 0; | ||
| }; | ||
|
|
||
| return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]); | ||
| } | ||
|
|
||
| /// Same as `sliceToPrefixedFileW` but accepts a pointer | ||
| /// to a null-terminated path. | ||
| pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { | ||
|
|
@@ -1742,28 +1817,42 @@ pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { | |
| else => {}, | ||
| } | ||
| } | ||
| const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; | ||
| const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { | ||
| 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; | ||
| // > 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.len = start_index + (normalizePath(u16, path_space.data[start_index..path_space.len]) catch |err| switch (err) { | ||
| error.TooManyParentDirs => { | ||
| if (!std.fs.path.isAbsolute(s)) { | ||
| var temp_path: PathSpace = undefined; | ||
| temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, s); | ||
| std.debug.assert(temp_path.len == path_space.len); | ||
| temp_path.data[path_space.len] = 0; | ||
| path_space.len = prefix_u16.len + try getFullPathNameW(&temp_path.data, path_space.data[prefix_u16.len..]); | ||
| mem.copy(u16, &path_space.data, &prefix_u16); | ||
| std.debug.assert(path_space.data[path_space.len] == 0); | ||
| return path_space; | ||
| } | ||
| return error.BadPathName; | ||
| }, | ||
| }); | ||
| path_space.data[path_space.len] = 0; | ||
| return path_space; | ||
| } | ||
|
|
||
| fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { | ||
| const result= kernel32.GetFullPathNameW(path, @intCast(u32, out.len), std.meta.assumeSentinel(out.ptr, 0), null); | ||
| if (result == 0) { | ||
| switch (kernel32.GetLastError()) { | ||
| else => |err| return unexpectedError(err), | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| /// Assumes an absolute path. | ||
| pub fn wToPrefixedFileW(s: []const u16) !PathSpace { | ||
| // TODO https://github.com/ziglang/zig/issues/2765 | ||
|
|
@@ -1864,3 +1953,9 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { | |
| } | ||
| return error.Unexpected; | ||
| } | ||
|
|
||
| test "" { | ||
| if (builtin.os.tag == .windows) { | ||
| _ = @import("windows/test.zig"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| // Copyright (c) 2015-2020 Zig Contributors | ||
| // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| // The MIT license requires this copyright notice to be included in all copies | ||
| // and substantial portions of the software. | ||
| const std = @import("../../std.zig"); | ||
| const builtin = @import("builtin"); | ||
| const windows = std.os.windows; | ||
| const mem = std.mem; | ||
| const testing = std.testing; | ||
| const expect = testing.expect; | ||
|
|
||
| fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void { | ||
| const mutable = try testing.allocator.dupe(u8, str); | ||
| defer testing.allocator.free(mutable); | ||
| const actual = mutable[0..try windows.removeDotDirsSanitized(u8, mutable)]; | ||
| try testing.expect(mem.eql(u8, actual, expected)); | ||
| } | ||
| fn testRemoveDotDirsError(err: anyerror, str: []const u8) !void { | ||
| const mutable = try testing.allocator.dupe(u8, str); | ||
| defer testing.allocator.free(mutable); | ||
| try testing.expectError(err, windows.removeDotDirsSanitized(u8, mutable)); | ||
| } | ||
| test "removeDotDirs" { | ||
| try testRemoveDotDirs("", ""); | ||
| try testRemoveDotDirs(".", ""); | ||
| try testRemoveDotDirs(".\\", ""); | ||
| try testRemoveDotDirs(".\\.", ""); | ||
| try testRemoveDotDirs(".\\.\\", ""); | ||
| try testRemoveDotDirs(".\\.\\.", ""); | ||
|
|
||
| try testRemoveDotDirs("a", "a"); | ||
| try testRemoveDotDirs("a\\", "a\\"); | ||
| try testRemoveDotDirs("a\\b", "a\\b"); | ||
| try testRemoveDotDirs("a\\.", "a\\"); | ||
| try testRemoveDotDirs("a\\b\\.", "a\\b\\"); | ||
| try testRemoveDotDirs("a\\.\\b", "a\\b"); | ||
|
|
||
| try testRemoveDotDirs(".a", ".a"); | ||
| try testRemoveDotDirs(".a\\", ".a\\"); | ||
| try testRemoveDotDirs(".a\\.b", ".a\\.b"); | ||
| try testRemoveDotDirs(".a\\.", ".a\\"); | ||
| try testRemoveDotDirs(".a\\.\\.", ".a\\"); | ||
| try testRemoveDotDirs(".a\\.\\.\\.b", ".a\\.b"); | ||
| try testRemoveDotDirs(".a\\.\\.\\.b\\", ".a\\.b\\"); | ||
|
|
||
| try testRemoveDotDirsError(error.TooManyParentDirs, ".."); | ||
| try testRemoveDotDirsError(error.TooManyParentDirs, "..\\"); | ||
| try testRemoveDotDirsError(error.TooManyParentDirs, ".\\..\\"); | ||
| try testRemoveDotDirsError(error.TooManyParentDirs, ".\\.\\..\\"); | ||
|
|
||
| try testRemoveDotDirs("a\\..", ""); | ||
| try testRemoveDotDirs("a\\..\\", ""); | ||
| try testRemoveDotDirs("a\\..\\.", ""); | ||
| try testRemoveDotDirs("a\\..\\.\\", ""); | ||
| try testRemoveDotDirs("a\\..\\.\\.", ""); | ||
| try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\.."); | ||
|
|
||
| try testRemoveDotDirs("a\\..\\.\\.\\b", "b"); | ||
| try testRemoveDotDirs("a\\..\\.\\.\\b\\", "b\\"); | ||
| try testRemoveDotDirs("a\\..\\.\\.\\b\\.", "b\\"); | ||
| try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\", "b\\"); | ||
| try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..", ""); | ||
| try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\", ""); | ||
| try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\.", ""); | ||
| try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\b\\.\\..\\.\\.."); | ||
|
|
||
| try testRemoveDotDirs("a\\b\\..\\", "a\\"); | ||
| try testRemoveDotDirs("a\\b\\..\\c", "a\\c"); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this addressed in the PR description - did you look into using this technique to resolve
..in a way that respects following symlinks?Related: NtQueryObject #1840
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On windows symlinks are resolved after resolving ".." (#7751)