mirror of
https://github.com/ziglang/zig.git
synced 2024-11-16 17:15:37 +00:00
Merge pull request #7664 from marler8997/fixWindowsPaths
implement nt path conversion for windows
This commit is contained in:
commit
7cd9b30e0a
@ -1365,15 +1365,6 @@ pub const Dir = struct {
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
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;
|
||||
}
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||||
// If you're looking to contribute to zig and fix this, see here for an example of how to
|
||||
// implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
|
||||
@panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
|
||||
}
|
||||
const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
|
||||
var io: w.IO_STATUS_BLOCK = undefined;
|
||||
const rc = w.ntdll.NtCreateFile(
|
||||
|
@ -79,7 +79,23 @@ test "openDirAbsolute" {
|
||||
break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
|
||||
};
|
||||
|
||||
var dir = try fs.openDirAbsolute(base_path, .{});
|
||||
{
|
||||
var dir = try fs.openDirAbsolute(base_path, .{});
|
||||
defer dir.close();
|
||||
}
|
||||
|
||||
for ([_][]const u8{ ".", ".." }) |sub_path| {
|
||||
const dir_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, sub_path });
|
||||
defer arena.allocator.free(dir_path);
|
||||
var dir = try fs.openDirAbsolute(dir_path, .{});
|
||||
defer dir.close();
|
||||
}
|
||||
}
|
||||
|
||||
test "openDir cwd parent .." {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var dir = try fs.cwd().openDir("..", .{});
|
||||
defer dir.close();
|
||||
}
|
||||
|
||||
|
@ -2120,6 +2120,53 @@ test "replace" {
|
||||
try testing.expectEqualStrings(expected, output[0..expected.len]);
|
||||
}
|
||||
|
||||
/// Replace all occurences of `needle` with `replacement`.
|
||||
pub fn replaceScalar(comptime T: type, slice: []T, needle: T, replacement: T) void {
|
||||
for (slice) |e, i| {
|
||||
if (e == needle) {
|
||||
slice[i] = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collapse consecutive duplicate elements into one entry.
|
||||
pub fn collapseRepeatsLen(comptime T: type, slice: []T, elem: T) usize {
|
||||
if (slice.len == 0) return 0;
|
||||
var write_idx: usize = 1;
|
||||
var read_idx: usize = 1;
|
||||
while (read_idx < slice.len) : (read_idx += 1) {
|
||||
if (slice[read_idx - 1] != elem or slice[read_idx] != elem) {
|
||||
slice[write_idx] = slice[read_idx];
|
||||
write_idx += 1;
|
||||
}
|
||||
}
|
||||
return write_idx;
|
||||
}
|
||||
|
||||
/// Collapse consecutive duplicate elements into one entry.
|
||||
pub fn collapseRepeats(comptime T: type, slice: []T, elem: T) []T {
|
||||
return slice[0 .. collapseRepeatsLen(T, slice, elem)];
|
||||
}
|
||||
|
||||
fn testCollapseRepeats(str: []const u8, elem: u8, expected: []const u8) !void {
|
||||
const mutable = try std.testing.allocator.dupe(u8, str);
|
||||
defer std.testing.allocator.free(mutable);
|
||||
try testing.expect(std.mem.eql(u8, collapseRepeats(u8, mutable, elem), expected));
|
||||
}
|
||||
test "collapseRepeats" {
|
||||
try testCollapseRepeats("", '/', "");
|
||||
try testCollapseRepeats("a", '/', "a");
|
||||
try testCollapseRepeats("/", '/', "/");
|
||||
try testCollapseRepeats("//", '/', "/");
|
||||
try testCollapseRepeats("/a", '/', "/a");
|
||||
try testCollapseRepeats("//a", '/', "/a");
|
||||
try testCollapseRepeats("a/", '/', "a/");
|
||||
try testCollapseRepeats("a//", '/', "a/");
|
||||
try testCollapseRepeats("a/a", '/', "a/a");
|
||||
try testCollapseRepeats("a//a", '/', "a/a");
|
||||
try testCollapseRepeats("//a///a////", '/', "/a/a/");
|
||||
}
|
||||
|
||||
/// Calculate the size needed in an output buffer to perform a replacement.
|
||||
/// The needle must not be empty.
|
||||
pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize {
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
70
lib/std/os/windows/test.zig
Normal file
70
lib/std/os/windows/test.zig
Normal file
@ -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");
|
||||
}
|
Loading…
Reference in New Issue
Block a user