| author | |
| committer | |
| log | 7cd9b30e0aee01eec148b867e2f949e7449e258d |
| tree | f3a9612c95ddac9bb1a6e5b743d91d2473dadd84 |
| parent | 79dee75b1ccd8f3f595aad0d4150851cff58f691 |
| parent | b0116afd8adbc73d820fee418ec74104f43bff6e |
| signature |
implement nt path conversion for windows6 files changed, 248 insertions(+), 22 deletions(-)
lib/std/fs.zig-9| ... | @@ -1365,15 +1365,6 @@ pub const Dir = struct { | ... | @@ -1365,15 +1365,6 @@ pub const Dir = struct { |
| 1365 | .SecurityDescriptor = null, | 1365 | .SecurityDescriptor = null, |
| 1366 | .SecurityQualityOfService = null, | 1366 | .SecurityQualityOfService = null, |
| 1367 | }; | 1367 | }; |
| 1368 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | ||
| 1369 | // Windows does not recognize this, but it does work with empty string. | ||
| 1370 | nt_name.Length = 0; | ||
| 1371 | } | ||
| 1372 | if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { | ||
| 1373 | // If you're looking to contribute to zig and fix this, see here for an example of how to | ||
| 1374 | // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c | ||
| 1375 | @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows"); | ||
| 1376 | } | ||
| 1377 | const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; | 1368 | const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; |
| 1378 | var io: w.IO_STATUS_BLOCK = undefined; | 1369 | var io: w.IO_STATUS_BLOCK = undefined; |
| 1379 | const rc = w.ntdll.NtCreateFile( | 1370 | const rc = w.ntdll.NtCreateFile( |
lib/std/fs/test.zig+17-1| ... | @@ -79,7 +79,23 @@ test "openDirAbsolute" { | ... | @@ -79,7 +79,23 @@ test "openDirAbsolute" { |
| 79 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); | 79 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); |
| 80 | }; | 80 | }; |
| 81 | 81 | ||
| 82 | var dir = try fs.openDirAbsolute(base_path, .{}); | 82 | { |
| 83 | var dir = try fs.openDirAbsolute(base_path, .{}); | ||
| 84 | defer dir.close(); | ||
| 85 | } | ||
| 86 | |||
| 87 | for ([_][]const u8{ ".", ".." }) |sub_path| { | ||
| 88 | const dir_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, sub_path }); | ||
| 89 | defer arena.allocator.free(dir_path); | ||
| 90 | var dir = try fs.openDirAbsolute(dir_path, .{}); | ||
| 91 | defer dir.close(); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | test "openDir cwd parent .." { | ||
| 96 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 97 | |||
| 98 | var dir = try fs.cwd().openDir("..", .{}); | ||
| 83 | defer dir.close(); | 99 | defer dir.close(); |
| 84 | } | 100 | } |
| 85 | 101 |
lib/std/mem.zig+47| ... | @@ -2120,6 +2120,53 @@ test "replace" { | ... | @@ -2120,6 +2120,53 @@ test "replace" { |
| 2120 | try testing.expectEqualStrings(expected, output[0..expected.len]); | 2120 | try testing.expectEqualStrings(expected, output[0..expected.len]); |
| 2121 | } | 2121 | } |
| 2122 | 2122 | ||
| 2123 | /// Replace all occurences of `needle` with `replacement`. | ||
| 2124 | pub fn replaceScalar(comptime T: type, slice: []T, needle: T, replacement: T) void { | ||
| 2125 | for (slice) |e, i| { | ||
| 2126 | if (e == needle) { | ||
| 2127 | slice[i] = replacement; | ||
| 2128 | } | ||
| 2129 | } | ||
| 2130 | } | ||
| 2131 | |||
| 2132 | /// Collapse consecutive duplicate elements into one entry. | ||
| 2133 | pub fn collapseRepeatsLen(comptime T: type, slice: []T, elem: T) usize { | ||
| 2134 | if (slice.len == 0) return 0; | ||
| 2135 | var write_idx: usize = 1; | ||
| 2136 | var read_idx: usize = 1; | ||
| 2137 | while (read_idx < slice.len) : (read_idx += 1) { | ||
| 2138 | if (slice[read_idx - 1] != elem or slice[read_idx] != elem) { | ||
| 2139 | slice[write_idx] = slice[read_idx]; | ||
| 2140 | write_idx += 1; | ||
| 2141 | } | ||
| 2142 | } | ||
| 2143 | return write_idx; | ||
| 2144 | } | ||
| 2145 | |||
| 2146 | /// Collapse consecutive duplicate elements into one entry. | ||
| 2147 | pub fn collapseRepeats(comptime T: type, slice: []T, elem: T) []T { | ||
| 2148 | return slice[0 .. collapseRepeatsLen(T, slice, elem)]; | ||
| 2149 | } | ||
| 2150 | |||
| 2151 | fn testCollapseRepeats(str: []const u8, elem: u8, expected: []const u8) !void { | ||
| 2152 | const mutable = try std.testing.allocator.dupe(u8, str); | ||
| 2153 | defer std.testing.allocator.free(mutable); | ||
| 2154 | try testing.expect(std.mem.eql(u8, collapseRepeats(u8, mutable, elem), expected)); | ||
| 2155 | } | ||
| 2156 | test "collapseRepeats" { | ||
| 2157 | try testCollapseRepeats("", '/', ""); | ||
| 2158 | try testCollapseRepeats("a", '/', "a"); | ||
| 2159 | try testCollapseRepeats("/", '/', "/"); | ||
| 2160 | try testCollapseRepeats("//", '/', "/"); | ||
| 2161 | try testCollapseRepeats("/a", '/', "/a"); | ||
| 2162 | try testCollapseRepeats("//a", '/', "/a"); | ||
| 2163 | try testCollapseRepeats("a/", '/', "a/"); | ||
| 2164 | try testCollapseRepeats("a//", '/', "a/"); | ||
| 2165 | try testCollapseRepeats("a/a", '/', "a/a"); | ||
| 2166 | try testCollapseRepeats("a//a", '/', "a/a"); | ||
| 2167 | try testCollapseRepeats("//a///a////", '/', "/a/a/"); | ||
| 2168 | } | ||
| 2169 | |||
| 2123 | /// Calculate the size needed in an output buffer to perform a replacement. | 2170 | /// Calculate the size needed in an output buffer to perform a replacement. |
| 2124 | /// The needle must not be empty. | 2171 | /// The needle must not be empty. |
| 2125 | pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize { | 2172 | pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize { |
lib/std/os/windows.zig+107-12| ... | @@ -1723,6 +1723,81 @@ pub const PathSpace = struct { | ... | @@ -1723,6 +1723,81 @@ pub const PathSpace = struct { |
| 1723 | } | 1723 | } |
| 1724 | }; | 1724 | }; |
| 1725 | 1725 | ||
| 1726 | /// The error type for `removeDotDirsSanitized` | ||
| 1727 | pub const RemoveDotDirsError = error{TooManyParentDirs}; | ||
| 1728 | |||
| 1729 | /// Removes '.' and '..' path components from a "sanitized relative path". | ||
| 1730 | /// A "sanitized path" is one where: | ||
| 1731 | /// 1) all forward slashes have been replaced with back slashes | ||
| 1732 | /// 2) all repeating back slashes have been collapsed | ||
| 1733 | /// 3) the path is a relative one (does not start with a back slash) | ||
| 1734 | pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize { | ||
| 1735 | std.debug.assert(path.len == 0 or path[0] != '\\'); | ||
| 1736 | |||
| 1737 | var write_idx: usize = 0; | ||
| 1738 | var read_idx: usize = 0; | ||
| 1739 | while (read_idx < path.len) { | ||
| 1740 | if (path[read_idx] == '.') { | ||
| 1741 | if (read_idx + 1 == path.len) | ||
| 1742 | return write_idx; | ||
| 1743 | |||
| 1744 | const after_dot = path[read_idx + 1]; | ||
| 1745 | if (after_dot == '\\') { | ||
| 1746 | read_idx += 2; | ||
| 1747 | continue; | ||
| 1748 | } | ||
| 1749 | if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) { | ||
| 1750 | if (write_idx == 0) return error.TooManyParentDirs; | ||
| 1751 | std.debug.assert(write_idx >= 2); | ||
| 1752 | write_idx -= 1; | ||
| 1753 | while (true) { | ||
| 1754 | write_idx -= 1; | ||
| 1755 | if (write_idx == 0) break; | ||
| 1756 | if (path[write_idx] == '\\') { | ||
| 1757 | write_idx += 1; | ||
| 1758 | break; | ||
| 1759 | } | ||
| 1760 | } | ||
| 1761 | if (read_idx + 2 == path.len) | ||
| 1762 | return write_idx; | ||
| 1763 | read_idx += 3; | ||
| 1764 | continue; | ||
| 1765 | } | ||
| 1766 | } | ||
| 1767 | |||
| 1768 | // skip to the next path separator | ||
| 1769 | while (true) : (read_idx += 1) { | ||
| 1770 | if (read_idx == path.len) | ||
| 1771 | return write_idx; | ||
| 1772 | path[write_idx] = path[read_idx]; | ||
| 1773 | write_idx += 1; | ||
| 1774 | if (path[read_idx] == '\\') | ||
| 1775 | break; | ||
| 1776 | } | ||
| 1777 | read_idx += 1; | ||
| 1778 | } | ||
| 1779 | return write_idx; | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | /// Normalizes a Windows path with the following steps: | ||
| 1783 | /// 1) convert all forward slashes to back slashes | ||
| 1784 | /// 2) collapse duplicate back slashes | ||
| 1785 | /// 3) remove '.' and '..' directory parts | ||
| 1786 | /// Returns the length of the new path. | ||
| 1787 | pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize { | ||
| 1788 | mem.replaceScalar(T, path, '/', '\\'); | ||
| 1789 | const new_len = mem.collapseRepeatsLen(T, path, '\\'); | ||
| 1790 | |||
| 1791 | const prefix_len: usize = init: { | ||
| 1792 | if (new_len >= 1 and path[0] == '\\') break :init 1; | ||
| 1793 | if (new_len >= 2 and path[1] == ':') | ||
| 1794 | break :init if (new_len >= 3 and path[2] == '\\') @as(usize, 3) else @as(usize, 2); | ||
| 1795 | break :init 0; | ||
| 1796 | }; | ||
| 1797 | |||
| 1798 | return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]); | ||
| 1799 | } | ||
| 1800 | |||
| 1726 | /// Same as `sliceToPrefixedFileW` but accepts a pointer | 1801 | /// Same as `sliceToPrefixedFileW` but accepts a pointer |
| 1727 | /// to a null-terminated path. | 1802 | /// to a null-terminated path. |
| 1728 | pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { | 1803 | pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| ... | @@ -1742,28 +1817,42 @@ pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { | ... | @@ -1742,28 +1817,42 @@ pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1742 | else => {}, | 1817 | else => {}, |
| 1743 | } | 1818 | } |
| 1744 | } | 1819 | } |
| 1820 | const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; | ||
| 1745 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { | 1821 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { |
| 1746 | const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; | ||
| 1747 | mem.copy(u16, path_space.data[0..], prefix_u16[0..]); | 1822 | mem.copy(u16, path_space.data[0..], prefix_u16[0..]); |
| 1748 | break :blk prefix_u16.len; | 1823 | break :blk prefix_u16.len; |
| 1749 | }; | 1824 | }; |
| 1750 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); | 1825 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); |
| 1751 | if (path_space.len > path_space.data.len) return error.NameTooLong; | 1826 | if (path_space.len > path_space.data.len) return error.NameTooLong; |
| 1752 | // > File I/O functions in the Windows API convert "/" to "\" as part of | 1827 | path_space.len = start_index + (normalizePath(u16, path_space.data[start_index..path_space.len]) catch |err| switch (err) { |
| 1753 | // > converting the name to an NT-style name, except when using the "\\?\" | 1828 | error.TooManyParentDirs => { |
| 1754 | // > prefix as detailed in the following sections. | 1829 | if (!std.fs.path.isAbsolute(s)) { |
| 1755 | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation | 1830 | var temp_path: PathSpace = undefined; |
| 1756 | // Because we want the larger maximum path length for absolute paths, we | 1831 | temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, s); |
| 1757 | // convert forward slashes to backward slashes here. | 1832 | std.debug.assert(temp_path.len == path_space.len); |
| 1758 | for (path_space.data[0..path_space.len]) |*elem| { | 1833 | temp_path.data[path_space.len] = 0; |
| 1759 | if (elem.* == '/') { | 1834 | path_space.len = prefix_u16.len + try getFullPathNameW(&temp_path.data, path_space.data[prefix_u16.len..]); |
| 1760 | elem.* = '\\'; | 1835 | mem.copy(u16, &path_space.data, &prefix_u16); |
| 1761 | } | 1836 | std.debug.assert(path_space.data[path_space.len] == 0); |
| 1762 | } | 1837 | return path_space; |
| 1838 | } | ||
| 1839 | return error.BadPathName; | ||
| 1840 | }, | ||
| 1841 | }); | ||
| 1763 | path_space.data[path_space.len] = 0; | 1842 | path_space.data[path_space.len] = 0; |
| 1764 | return path_space; | 1843 | return path_space; |
| 1765 | } | 1844 | } |
| 1766 | 1845 | ||
| 1846 | fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { | ||
| 1847 | const result= kernel32.GetFullPathNameW(path, @intCast(u32, out.len), std.meta.assumeSentinel(out.ptr, 0), null); | ||
| 1848 | if (result == 0) { | ||
| 1849 | switch (kernel32.GetLastError()) { | ||
| 1850 | else => |err| return unexpectedError(err), | ||
| 1851 | } | ||
| 1852 | } | ||
| 1853 | return result; | ||
| 1854 | } | ||
| 1855 | |||
| 1767 | /// Assumes an absolute path. | 1856 | /// Assumes an absolute path. |
| 1768 | pub fn wToPrefixedFileW(s: []const u16) !PathSpace { | 1857 | pub fn wToPrefixedFileW(s: []const u16) !PathSpace { |
| 1769 | // TODO https://github.com/ziglang/zig/issues/2765 | 1858 | // TODO https://github.com/ziglang/zig/issues/2765 |
| ... | @@ -1864,3 +1953,9 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { | ... | @@ -1864,3 +1953,9 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { |
| 1864 | } | 1953 | } |
| 1865 | return error.Unexpected; | 1954 | return error.Unexpected; |
| 1866 | } | 1955 | } |
| 1956 | |||
| 1957 | test "" { | ||
| 1958 | if (builtin.os.tag == .windows) { | ||
| 1959 | _ = @import("windows/test.zig"); | ||
| 1960 | } | ||
| 1961 | } |
lib/std/os/windows/kernel32.zig+7| ... | @@ -136,6 +136,13 @@ pub extern "kernel32" fn GetFinalPathNameByHandleW( | ... | @@ -136,6 +136,13 @@ pub extern "kernel32" fn GetFinalPathNameByHandleW( |
| 136 | dwFlags: DWORD, | 136 | dwFlags: DWORD, |
| 137 | ) callconv(WINAPI) DWORD; | 137 | ) callconv(WINAPI) DWORD; |
| 138 | 138 | ||
| 139 | pub extern "kernel32" fn GetFullPathNameW( | ||
| 140 | lpFileName: [*:0]const u16, | ||
| 141 | nBufferLength: u32, | ||
| 142 | lpBuffer: ?[*:0]u16, | ||
| 143 | lpFilePart: ?*?[*:0]u16, | ||
| 144 | ) callconv(@import("std").os.windows.WINAPI) u32; | ||
| 145 | |||
| 139 | pub extern "kernel32" fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) callconv(WINAPI) BOOL; | 146 | pub extern "kernel32" fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) callconv(WINAPI) BOOL; |
| 140 | 147 | ||
| 141 | pub extern "kernel32" fn GetProcessHeap() callconv(WINAPI) ?HANDLE; | 148 | pub extern "kernel32" fn GetProcessHeap() callconv(WINAPI) ?HANDLE; |
lib/std/os/windows/test.zig created+70| ... | @@ -0,0 +1,70 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | const std = @import("../../std.zig"); | ||
| 7 | const builtin = @import("builtin"); | ||
| 8 | const windows = std.os.windows; | ||
| 9 | const mem = std.mem; | ||
| 10 | const testing = std.testing; | ||
| 11 | const expect = testing.expect; | ||
| 12 | |||
| 13 | fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void { | ||
| 14 | const mutable = try testing.allocator.dupe(u8, str); | ||
| 15 | defer testing.allocator.free(mutable); | ||
| 16 | const actual = mutable[0..try windows.removeDotDirsSanitized(u8, mutable)]; | ||
| 17 | try testing.expect(mem.eql(u8, actual, expected)); | ||
| 18 | } | ||
| 19 | fn testRemoveDotDirsError(err: anyerror, str: []const u8) !void { | ||
| 20 | const mutable = try testing.allocator.dupe(u8, str); | ||
| 21 | defer testing.allocator.free(mutable); | ||
| 22 | try testing.expectError(err, windows.removeDotDirsSanitized(u8, mutable)); | ||
| 23 | } | ||
| 24 | test "removeDotDirs" { | ||
| 25 | try testRemoveDotDirs("", ""); | ||
| 26 | try testRemoveDotDirs(".", ""); | ||
| 27 | try testRemoveDotDirs(".\\", ""); | ||
| 28 | try testRemoveDotDirs(".\\.", ""); | ||
| 29 | try testRemoveDotDirs(".\\.\\", ""); | ||
| 30 | try testRemoveDotDirs(".\\.\\.", ""); | ||
| 31 | |||
| 32 | try testRemoveDotDirs("a", "a"); | ||
| 33 | try testRemoveDotDirs("a\\", "a\\"); | ||
| 34 | try testRemoveDotDirs("a\\b", "a\\b"); | ||
| 35 | try testRemoveDotDirs("a\\.", "a\\"); | ||
| 36 | try testRemoveDotDirs("a\\b\\.", "a\\b\\"); | ||
| 37 | try testRemoveDotDirs("a\\.\\b", "a\\b"); | ||
| 38 | |||
| 39 | try testRemoveDotDirs(".a", ".a"); | ||
| 40 | try testRemoveDotDirs(".a\\", ".a\\"); | ||
| 41 | try testRemoveDotDirs(".a\\.b", ".a\\.b"); | ||
| 42 | try testRemoveDotDirs(".a\\.", ".a\\"); | ||
| 43 | try testRemoveDotDirs(".a\\.\\.", ".a\\"); | ||
| 44 | try testRemoveDotDirs(".a\\.\\.\\.b", ".a\\.b"); | ||
| 45 | try testRemoveDotDirs(".a\\.\\.\\.b\\", ".a\\.b\\"); | ||
| 46 | |||
| 47 | try testRemoveDotDirsError(error.TooManyParentDirs, ".."); | ||
| 48 | try testRemoveDotDirsError(error.TooManyParentDirs, "..\\"); | ||
| 49 | try testRemoveDotDirsError(error.TooManyParentDirs, ".\\..\\"); | ||
| 50 | try testRemoveDotDirsError(error.TooManyParentDirs, ".\\.\\..\\"); | ||
| 51 | |||
| 52 | try testRemoveDotDirs("a\\..", ""); | ||
| 53 | try testRemoveDotDirs("a\\..\\", ""); | ||
| 54 | try testRemoveDotDirs("a\\..\\.", ""); | ||
| 55 | try testRemoveDotDirs("a\\..\\.\\", ""); | ||
| 56 | try testRemoveDotDirs("a\\..\\.\\.", ""); | ||
| 57 | try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\.."); | ||
| 58 | |||
| 59 | try testRemoveDotDirs("a\\..\\.\\.\\b", "b"); | ||
| 60 | try testRemoveDotDirs("a\\..\\.\\.\\b\\", "b\\"); | ||
| 61 | try testRemoveDotDirs("a\\..\\.\\.\\b\\.", "b\\"); | ||
| 62 | try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\", "b\\"); | ||
| 63 | try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..", ""); | ||
| 64 | try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\", ""); | ||
| 65 | try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\.", ""); | ||
| 66 | try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\b\\.\\..\\.\\.."); | ||
| 67 | |||
| 68 | try testRemoveDotDirs("a\\b\\..\\", "a\\"); | ||
| 69 | try testRemoveDotDirs("a\\b\\..\\c", "a\\c"); | ||
| 70 | } | ||