aboutsummaryrefslogtreecommitdiff
path: root/lib/std/os
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-05-22 17:58:59 -0400
committerGitHub <noreply@github.com>2021-05-22 17:58:59 -0400
commit7cd9b30e0aee01eec148b867e2f949e7449e258d (patch)
treef3a9612c95ddac9bb1a6e5b743d91d2473dadd84 /lib/std/os
parent79dee75b1ccd8f3f595aad0d4150851cff58f691 (diff)
parentb0116afd8adbc73d820fee418ec74104f43bff6e (diff)
downloadzig-7cd9b30e0aee01eec148b867e2f949e7449e258d.tar.gz
zig-7cd9b30e0aee01eec148b867e2f949e7449e258d.zip
Merge pull request #7664 from marler8997/fixWindowsPaths
implement nt path conversion for windows
Diffstat (limited to 'lib/std/os')
-rw-r--r--lib/std/os/windows.zig119
-rw-r--r--lib/std/os/windows/kernel32.zig7
-rw-r--r--lib/std/os/windows/test.zig70
3 files changed, 184 insertions, 12 deletions
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index 9d898dadaf..e02cf4e280 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -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".
+/// 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");
+ }
+}
diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig
index e117f362eb..f2e8b87d74 100644
--- a/lib/std/os/windows/kernel32.zig
+++ b/lib/std/os/windows/kernel32.zig
@@ -136,6 +136,13 @@ pub extern "kernel32" fn GetFinalPathNameByHandleW(
dwFlags: DWORD,
) callconv(WINAPI) DWORD;
+pub extern "kernel32" fn GetFullPathNameW(
+ lpFileName: [*:0]const u16,
+ nBufferLength: u32,
+ lpBuffer: ?[*:0]u16,
+ lpFilePart: ?*?[*:0]u16,
+) callconv(@import("std").os.windows.WINAPI) u32;
+
pub extern "kernel32" fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) callconv(WINAPI) BOOL;
pub extern "kernel32" fn GetProcessHeap() callconv(WINAPI) ?HANDLE;
diff --git a/lib/std/os/windows/test.zig b/lib/std/os/windows/test.zig
new file mode 100644
index 0000000000..8c18d413ca
--- /dev/null
+++ b/lib/std/os/windows/test.zig
@@ -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");
+}