| author | |
| committer | |
| log | 666584067a97da6d39867ff0c57fcd3be2d50e96 |
| tree | 218e2505c071fd11a4485904835b5476845c9aef |
| parent | 34d0542a539a1993a602e3e461f6297fb4d78d0c |
5 files changed, 208 insertions(+), 22 deletions(-)
lib/std/fs.zig-9| ... | ... | @@ -1365,15 +1365,6 @@ pub const Dir = struct { |
| 1365 | 1365 | .SecurityDescriptor = null, |
| 1366 | 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 | 1368 | const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; |
| 1378 | 1369 | var io: w.IO_STATUS_BLOCK = undefined; |
| 1379 | 1370 | const rc = w.ntdll.NtCreateFile( |
lib/std/fs/test.zig+11-2| ... | ... | @@ -79,8 +79,17 @@ test "openDirAbsolute" { |
| 79 | 79 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); |
| 80 | 80 | }; |
| 81 | 81 | |
| 82 | var dir = try fs.openDirAbsolute(base_path, .{}); | |
| 83 | defer dir.close(); | |
| 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 | } | |
| 84 | 93 | } |
| 85 | 94 | |
| 86 | 95 | test "readLinkAbsolute" { |
lib/std/mem.zig+43| ... | ... | @@ -2120,6 +2120,49 @@ test "replace" { |
| 2120 | 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 collapseRepeats(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 | fn testCollapseRepeats(str: []const u8, elem: u8, expected: []const u8) !void { | |
| 2147 | const mutable = try std.testing.allocator.dupe(u8, str); | |
| 2148 | defer std.testing.allocator.free(mutable); | |
| 2149 | const actual = mutable[0..collapseRepeats(u8, mutable, elem)]; | |
| 2150 | testing.expect(std.mem.eql(u8, actual, expected)); | |
| 2151 | } | |
| 2152 | test "collapseRepeats" { | |
| 2153 | try testCollapseRepeats("", '/', ""); | |
| 2154 | try testCollapseRepeats("a", '/', "a"); | |
| 2155 | try testCollapseRepeats("/", '/', "/"); | |
| 2156 | try testCollapseRepeats("//", '/', "/"); | |
| 2157 | try testCollapseRepeats("/a", '/', "/a"); | |
| 2158 | try testCollapseRepeats("//a", '/', "/a"); | |
| 2159 | try testCollapseRepeats("a/", '/', "a/"); | |
| 2160 | try testCollapseRepeats("a//", '/', "a/"); | |
| 2161 | try testCollapseRepeats("a/a", '/', "a/a"); | |
| 2162 | try testCollapseRepeats("a//a", '/', "a/a"); | |
| 2163 | try testCollapseRepeats("//a///a////", '/', "/a/a/"); | |
| 2164 | } | |
| 2165 | ||
| 2123 | 2166 | /// Calculate the size needed in an output buffer to perform a replacement. |
| 2124 | 2167 | /// The needle must not be empty. |
| 2125 | 2168 | pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize { |
lib/std/os/windows.zig+84-11| ... | ... | @@ -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.collapseRepeats(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 | 1801 | /// Same as `sliceToPrefixedFileW` but accepts a pointer |
| 1727 | 1802 | /// to a null-terminated path. |
| 1728 | 1803 | pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| ... | ... | @@ -1749,17 +1824,9 @@ pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1749 | 1824 | }; |
| 1750 | 1825 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); |
| 1751 | 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 | |
| 1753 | // > converting the name to an NT-style name, except when using the "\\?\" | |
| 1754 | // > prefix as detailed in the following sections. | |
| 1755 | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation | |
| 1756 | // Because we want the larger maximum path length for absolute paths, we | |
| 1757 | // convert forward slashes to backward slashes here. | |
| 1758 | for (path_space.data[0..path_space.len]) |*elem| { | |
| 1759 | if (elem.* == '/') { | |
| 1760 | elem.* = '\\'; | |
| 1761 | } | |
| 1762 | } | |
| 1827 | path_space.len = start_index + (normalizePath(u16, path_space.data[start_index..path_space.len]) catch |err| switch (err) { | |
| 1828 | error.TooManyParentDirs => return error.BadPathName, | |
| 1829 | }); | |
| 1763 | 1830 | path_space.data[path_space.len] = 0; |
| 1764 | 1831 | return path_space; |
| 1765 | 1832 | } |
| ... | ... | @@ -1864,3 +1931,9 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { |
| 1864 | 1931 | } |
| 1865 | 1932 | return error.Unexpected; |
| 1866 | 1933 | } |
| 1934 | ||
| 1935 | test "" { | |
| 1936 | if (builtin.os.tag == .windows) { | |
| 1937 | _ = @import("windows/test.zig"); | |
| 1938 | } | |
| 1939 | } |
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 | 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 | 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 | } |