mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Merge pull request #25993 from squeek502/windows-paths
Teach `std.fs.path` about the wonderful world of Windows paths
This commit is contained in:
commit
53e615b920
2
lib/compiler/resinator/compile.zig
vendored
2
lib/compiler/resinator/compile.zig
vendored
@ -2914,7 +2914,7 @@ fn validateSearchPath(path: []const u8) error{BadPathName}!void {
|
|||||||
// (e.g. the NT \??\ prefix, the device \\.\ prefix, etc).
|
// (e.g. the NT \??\ prefix, the device \\.\ prefix, etc).
|
||||||
// Those path types are something of an unavoidable way to
|
// Those path types are something of an unavoidable way to
|
||||||
// still hit unreachable during the openDir call.
|
// still hit unreachable during the openDir call.
|
||||||
var component_iterator = try std.fs.path.componentIterator(path);
|
var component_iterator = std.fs.path.componentIterator(path);
|
||||||
while (component_iterator.next()) |component| {
|
while (component_iterator.next()) |component| {
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||||
if (std.mem.indexOfAny(u8, component.name, "\x00<>:\"|?*") != null) return error.BadPathName;
|
if (std.mem.indexOfAny(u8, component.name, "\x00<>:\"|?*") != null) return error.BadPathName;
|
||||||
|
|||||||
@ -104,9 +104,7 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
|
|||||||
fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
|
fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
|
||||||
const relative = try fs.path.relative(allocator, prefix, path);
|
const relative = try fs.path.relative(allocator, prefix, path);
|
||||||
errdefer allocator.free(relative);
|
errdefer allocator.free(relative);
|
||||||
var component_iterator = fs.path.NativeComponentIterator.init(relative) catch {
|
var component_iterator = fs.path.NativeComponentIterator.init(relative);
|
||||||
return error.NotASubPath;
|
|
||||||
};
|
|
||||||
if (component_iterator.root() != null) {
|
if (component_iterator.root() != null) {
|
||||||
return error.NotASubPath;
|
return error.NotASubPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,7 +167,7 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
|
|||||||
}.lessThan);
|
}.lessThan);
|
||||||
need_dirs.clearRetainingCapacity();
|
need_dirs.clearRetainingCapacity();
|
||||||
for (old_dirs) |dir_path| {
|
for (old_dirs) |dir_path| {
|
||||||
var it: std.fs.path.ComponentIterator(.posix, u8) = try .init(dir_path);
|
var it: std.fs.path.ComponentIterator(.posix, u8) = .init(dir_path);
|
||||||
while (it.next()) |component| {
|
while (it.next()) |component| {
|
||||||
if (need_dirs.contains(component.path)) {
|
if (need_dirs.contains(component.path)) {
|
||||||
// this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
|
// this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
|
||||||
|
|||||||
@ -318,7 +318,7 @@ pub const MakePathStatus = enum { existed, created };
|
|||||||
/// Same as `makePath` except returns whether the path already existed or was
|
/// Same as `makePath` except returns whether the path already existed or was
|
||||||
/// successfully created.
|
/// successfully created.
|
||||||
pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
|
pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
|
||||||
var it = try std.fs.path.componentIterator(sub_path);
|
var it = std.fs.path.componentIterator(sub_path);
|
||||||
var status: MakePathStatus = .existed;
|
var status: MakePathStatus = .existed;
|
||||||
var component = it.last() orelse return error.BadPathName;
|
var component = it.last() orelse return error.BadPathName;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@ -1210,7 +1210,7 @@ fn dirMakeOpenPathWindows(
|
|||||||
w.SYNCHRONIZE | w.FILE_TRAVERSE |
|
w.SYNCHRONIZE | w.FILE_TRAVERSE |
|
||||||
(if (options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
|
(if (options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
|
||||||
|
|
||||||
var it = try std.fs.path.componentIterator(sub_path);
|
var it = std.fs.path.componentIterator(sub_path);
|
||||||
// If there are no components in the path, then create a dummy component with the full path.
|
// If there are no components in the path, then create a dummy component with the full path.
|
||||||
var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
|
var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
|
||||||
.name = "",
|
.name = "",
|
||||||
|
|||||||
1474
lib/std/fs/path.zig
1474
lib/std/fs/path.zig
File diff suppressed because it is too large
Load Diff
@ -56,7 +56,7 @@ const PathType = enum {
|
|||||||
// using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
|
// using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
|
||||||
var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
|
var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
|
||||||
const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
|
const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
|
||||||
const windows_path_type = windows.getUnprefixedPathType(u8, dir_path);
|
const windows_path_type = windows.getWin32PathType(u8, dir_path);
|
||||||
switch (windows_path_type) {
|
switch (windows_path_type) {
|
||||||
.unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
|
.unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
|
||||||
.drive_absolute => {
|
.drive_absolute => {
|
||||||
|
|||||||
@ -816,8 +816,11 @@ pub fn CreateSymbolicLink(
|
|||||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
|
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
|
||||||
var is_target_absolute = false;
|
var is_target_absolute = false;
|
||||||
const final_target_path = target_path: {
|
const final_target_path = target_path: {
|
||||||
switch (getNamespacePrefix(u16, target_path)) {
|
if (hasCommonNtPrefix(u16, target_path)) {
|
||||||
.none => switch (getUnprefixedPathType(u16, target_path)) {
|
// Already an NT path, no need to do anything to it
|
||||||
|
break :target_path target_path;
|
||||||
|
} else {
|
||||||
|
switch (getWin32PathType(u16, target_path)) {
|
||||||
// Rooted paths need to avoid getting put through wToPrefixedFileW
|
// Rooted paths need to avoid getting put through wToPrefixedFileW
|
||||||
// (and they are treated as relative in this context)
|
// (and they are treated as relative in this context)
|
||||||
// Note: It seems that rooted paths in symbolic links are relative to
|
// Note: It seems that rooted paths in symbolic links are relative to
|
||||||
@ -829,10 +832,7 @@ pub fn CreateSymbolicLink(
|
|||||||
// Keep relative paths relative, but anything else needs to get NT-prefixed.
|
// Keep relative paths relative, but anything else needs to get NT-prefixed.
|
||||||
else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
|
else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
|
||||||
break :target_path target_path,
|
break :target_path target_path,
|
||||||
},
|
}
|
||||||
// Already an NT path, no need to do anything to it
|
|
||||||
.nt => break :target_path target_path,
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
var prefixed_target_path = try wToPrefixedFileW(dir, target_path);
|
var prefixed_target_path = try wToPrefixedFileW(dir, target_path);
|
||||||
// We do this after prefixing to ensure that drive-relative paths are treated as absolute
|
// We do this after prefixing to ensure that drive-relative paths are treated as absolute
|
||||||
@ -2145,7 +2145,7 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
|
|||||||
/// Compares two WTF16 strings using the equivalent functionality of
|
/// Compares two WTF16 strings using the equivalent functionality of
|
||||||
/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
|
/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
|
||||||
/// This function can be called on any target.
|
/// This function can be called on any target.
|
||||||
pub fn eqlIgnoreCaseWTF16(a: []const u16, b: []const u16) bool {
|
pub fn eqlIgnoreCaseWtf16(a: []const u16, b: []const u16) bool {
|
||||||
if (@inComptime() or builtin.os.tag != .windows) {
|
if (@inComptime() or builtin.os.tag != .windows) {
|
||||||
// This function compares the strings code unit by code unit (aka u16-to-u16),
|
// This function compares the strings code unit by code unit (aka u16-to-u16),
|
||||||
// so any length difference implies inequality. In other words, there's no possible
|
// so any length difference implies inequality. In other words, there's no possible
|
||||||
@ -2222,19 +2222,19 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
|
|||||||
|
|
||||||
fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
|
fn testEqlIgnoreCase(comptime expect_eql: bool, comptime a: []const u8, comptime b: []const u8) !void {
|
||||||
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
|
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf8(a, b));
|
||||||
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWTF16(
|
try std.testing.expectEqual(expect_eql, eqlIgnoreCaseWtf16(
|
||||||
std.unicode.utf8ToUtf16LeStringLiteral(a),
|
std.unicode.utf8ToUtf16LeStringLiteral(a),
|
||||||
std.unicode.utf8ToUtf16LeStringLiteral(b),
|
std.unicode.utf8ToUtf16LeStringLiteral(b),
|
||||||
));
|
));
|
||||||
|
|
||||||
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
|
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf8(a, b));
|
||||||
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWTF16(
|
try comptime std.testing.expect(expect_eql == eqlIgnoreCaseWtf16(
|
||||||
std.unicode.utf8ToUtf16LeStringLiteral(a),
|
std.unicode.utf8ToUtf16LeStringLiteral(a),
|
||||||
std.unicode.utf8ToUtf16LeStringLiteral(b),
|
std.unicode.utf8ToUtf16LeStringLiteral(b),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "eqlIgnoreCaseWTF16/Wtf8" {
|
test "eqlIgnoreCaseWtf16/Wtf8" {
|
||||||
try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
|
try testEqlIgnoreCase(true, "\x01 a B Λ ɐ", "\x01 A b λ Ɐ");
|
||||||
// does not do case-insensitive comparison for codepoints >= U+10000
|
// does not do case-insensitive comparison for codepoints >= U+10000
|
||||||
try testEqlIgnoreCase(false, "𐓏", "𐓷");
|
try testEqlIgnoreCase(false, "𐓏", "𐓷");
|
||||||
@ -2365,10 +2365,9 @@ pub const Wtf16ToPrefixedFileWError = error{
|
|||||||
/// - . and space are not stripped from the end of relative paths (potential TODO)
|
/// - . and space are not stripped from the end of relative paths (potential TODO)
|
||||||
pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
|
pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
|
||||||
const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
|
const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
|
||||||
switch (getNamespacePrefix(u16, path)) {
|
if (hasCommonNtPrefix(u16, path)) {
|
||||||
// TODO: Figure out a way to design an API that can avoid the copy for .nt,
|
// TODO: Figure out a way to design an API that can avoid the copy for NT,
|
||||||
// since it is always returned fully unmodified.
|
// since it is always returned fully unmodified.
|
||||||
.nt, .verbatim => {
|
|
||||||
var path_space: PathSpace = undefined;
|
var path_space: PathSpace = undefined;
|
||||||
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||||
const len_after_prefix = path.len - nt_prefix.len;
|
const len_after_prefix = path.len - nt_prefix.len;
|
||||||
@ -2376,9 +2375,20 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
|
|||||||
path_space.len = path.len;
|
path_space.len = path.len;
|
||||||
path_space.data[path_space.len] = 0;
|
path_space.data[path_space.len] = 0;
|
||||||
return path_space;
|
return path_space;
|
||||||
|
} else {
|
||||||
|
const path_type = getWin32PathType(u16, path);
|
||||||
|
var path_space: PathSpace = undefined;
|
||||||
|
if (path_type == .local_device) {
|
||||||
|
switch (getLocalDevicePathType(u16, path)) {
|
||||||
|
.verbatim => {
|
||||||
|
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||||
|
const len_after_prefix = path.len - nt_prefix.len;
|
||||||
|
@memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
|
||||||
|
path_space.len = path.len;
|
||||||
|
path_space.data[path_space.len] = 0;
|
||||||
|
return path_space;
|
||||||
},
|
},
|
||||||
.local_device, .fake_verbatim => {
|
.local_device, .fake_verbatim => {
|
||||||
var path_space: PathSpace = undefined;
|
|
||||||
const path_byte_len = ntdll.RtlGetFullPathName_U(
|
const path_byte_len = ntdll.RtlGetFullPathName_U(
|
||||||
path.ptr,
|
path.ptr,
|
||||||
path_space.data.len * 2,
|
path_space.data.len * 2,
|
||||||
@ -2397,9 +2407,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
|
|||||||
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||||
return path_space;
|
return path_space;
|
||||||
},
|
},
|
||||||
.none => {
|
}
|
||||||
const path_type = getUnprefixedPathType(u16, path);
|
}
|
||||||
var path_space: PathSpace = undefined;
|
|
||||||
relative: {
|
relative: {
|
||||||
if (path_type == .relative) {
|
if (path_type == .relative) {
|
||||||
// TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
|
// TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
|
||||||
@ -2511,12 +2520,154 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
|
|||||||
path_space.data[nt_prefix.len..][0..unc.len].* = unc;
|
path_space.data[nt_prefix.len..][0..unc.len].* = unc;
|
||||||
}
|
}
|
||||||
return path_space;
|
return path_space;
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NamespacePrefix = enum {
|
/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
|
||||||
none,
|
pub const Win32PathType = enum {
|
||||||
|
/// `\\server\share\foo`
|
||||||
|
unc_absolute,
|
||||||
|
/// `C:\foo`
|
||||||
|
drive_absolute,
|
||||||
|
/// `C:foo`
|
||||||
|
drive_relative,
|
||||||
|
/// `\foo`
|
||||||
|
rooted,
|
||||||
|
/// `foo`
|
||||||
|
relative,
|
||||||
|
/// `\\.\foo`, `\\?\foo`
|
||||||
|
local_device,
|
||||||
|
/// `\\.`, `\\?`
|
||||||
|
root_local_device,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get the path type of a Win32 namespace path.
|
||||||
|
/// Similar to `RtlDetermineDosPathNameType_U`.
|
||||||
|
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
||||||
|
pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
|
||||||
|
if (path.len < 1) return .relative;
|
||||||
|
|
||||||
|
const windows_path = std.fs.path.PathType.windows;
|
||||||
|
if (windows_path.isSep(T, path[0])) {
|
||||||
|
// \x
|
||||||
|
if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
|
||||||
|
// \\. or \\?
|
||||||
|
if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
|
||||||
|
// exactly \\. or \\? with nothing trailing
|
||||||
|
if (path.len == 3) return .root_local_device;
|
||||||
|
// \\.\x or \\?\x
|
||||||
|
if (windows_path.isSep(T, path[3])) return .local_device;
|
||||||
|
}
|
||||||
|
// \\x
|
||||||
|
return .unc_absolute;
|
||||||
|
} else {
|
||||||
|
// Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
|
||||||
|
// path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
|
||||||
|
// for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
|
||||||
|
// units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
|
||||||
|
// checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
|
||||||
|
//
|
||||||
|
// `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
|
||||||
|
// `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
|
||||||
|
// classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
|
||||||
|
// in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
|
||||||
|
//
|
||||||
|
// The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
|
||||||
|
// WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
|
||||||
|
// drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
|
||||||
|
// to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
|
||||||
|
// allow you to set any WTF-16 code unit as a drive letter.
|
||||||
|
//
|
||||||
|
// Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
|
||||||
|
// `cd /D €:\` will work, filesystem functions still work, etc.
|
||||||
|
//
|
||||||
|
// The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
|
||||||
|
// just check path[0], path[1], path[2].
|
||||||
|
const colon_i: usize = switch (T) {
|
||||||
|
u8 => i: {
|
||||||
|
const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
|
||||||
|
// Conveniently, 4-byte sequences in WTF-8 have the same starting code point
|
||||||
|
// as 2-code-unit sequences in WTF-16.
|
||||||
|
if (code_point_len > 3) return .relative;
|
||||||
|
break :i code_point_len;
|
||||||
|
},
|
||||||
|
u16 => 1,
|
||||||
|
else => @compileError("unsupported type: " ++ @typeName(T)),
|
||||||
|
};
|
||||||
|
// x
|
||||||
|
if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
|
||||||
|
// x:\
|
||||||
|
if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
|
||||||
|
// x:
|
||||||
|
return .drive_relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test getWin32PathType {
|
||||||
|
try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
|
||||||
|
try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
|
||||||
|
try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
|
||||||
|
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
|
||||||
|
try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
|
||||||
|
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
|
||||||
|
try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
|
||||||
|
// local device paths require a path separator after the root, otherwise it is considered a UNC path
|
||||||
|
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
|
||||||
|
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
|
||||||
|
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
|
||||||
|
try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
|
||||||
|
try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
|
||||||
|
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
|
||||||
|
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
|
||||||
|
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
|
||||||
|
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
|
||||||
|
|
||||||
|
// Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
|
||||||
|
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
|
||||||
|
try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
|
||||||
|
try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
|
||||||
|
try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
|
||||||
|
// But code points that are encoded as two WTF-16 code units are not
|
||||||
|
try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
|
||||||
|
try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the path starts with `\??\`, which is indicative of an NT path
|
||||||
|
/// but is not enough to fully distinguish between NT paths and Win32 paths, as
|
||||||
|
/// `\??\` is not actually a distinct prefix but rather the path to a special virtual
|
||||||
|
/// folder in the Object Manager.
|
||||||
|
///
|
||||||
|
/// For example, `\Device\HarddiskVolume2` and `\DosDevices\C:` are also NT paths but
|
||||||
|
/// cannot be distinguished as such by their prefix.
|
||||||
|
///
|
||||||
|
/// So, inferring whether a path is an NT path or a Win32 path is usually a mistake;
|
||||||
|
/// that information should instead be known ahead-of-time.
|
||||||
|
///
|
||||||
|
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
||||||
|
pub fn hasCommonNtPrefix(comptime T: type, path: []const T) bool {
|
||||||
|
// Must be exactly \??\, forward slashes are not allowed
|
||||||
|
const expected_wtf8_prefix = "\\??\\";
|
||||||
|
const expected_prefix = switch (T) {
|
||||||
|
u8 => expected_wtf8_prefix,
|
||||||
|
u16 => std.unicode.wtf8ToWtf16LeStringLiteral(expected_wtf8_prefix),
|
||||||
|
else => @compileError("unsupported type: " ++ @typeName(T)),
|
||||||
|
};
|
||||||
|
return mem.startsWith(T, path, expected_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LocalDevicePathType = enum {
|
||||||
/// `\\.\` (path separators can be `\` or `/`)
|
/// `\\.\` (path separators can be `\` or `/`)
|
||||||
local_device,
|
local_device,
|
||||||
/// `\\?\`
|
/// `\\?\`
|
||||||
@ -2529,107 +2680,24 @@ pub const NamespacePrefix = enum {
|
|||||||
/// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't
|
/// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't
|
||||||
/// be treated as part of the final path])
|
/// be treated as part of the final path])
|
||||||
fake_verbatim,
|
fake_verbatim,
|
||||||
/// `\??\`
|
|
||||||
nt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
/// Only relevant for Win32 -> NT path conversion.
|
||||||
pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
|
/// Asserts `path` is of type `Win32PathType.local_device`.
|
||||||
if (path.len < 4) return .none;
|
fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
|
||||||
var all_backslash = switch (mem.littleToNative(T, path[0])) {
|
|
||||||
'\\' => true,
|
|
||||||
'/' => false,
|
|
||||||
else => return .none,
|
|
||||||
};
|
|
||||||
all_backslash = all_backslash and switch (mem.littleToNative(T, path[3])) {
|
|
||||||
'\\' => true,
|
|
||||||
'/' => false,
|
|
||||||
else => return .none,
|
|
||||||
};
|
|
||||||
switch (mem.littleToNative(T, path[1])) {
|
|
||||||
'?' => if (mem.littleToNative(T, path[2]) == '?' and all_backslash) return .nt else return .none,
|
|
||||||
'\\' => {},
|
|
||||||
'/' => all_backslash = false,
|
|
||||||
else => return .none,
|
|
||||||
}
|
|
||||||
return switch (mem.littleToNative(T, path[2])) {
|
|
||||||
'?' => if (all_backslash) .verbatim else .fake_verbatim,
|
|
||||||
'.' => .local_device,
|
|
||||||
else => .none,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test getNamespacePrefix {
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, ""));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.nt, getNamespacePrefix(u8, "\\??\\"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/??/"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/??\\"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "\\?\\\\"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "\\\\.\\"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "\\\\./"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "/\\./"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "//./"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/.//"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.verbatim, getNamespacePrefix(u8, "\\\\?\\"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "\\/?\\"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "\\/?/"));
|
|
||||||
try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "//?/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const UnprefixedPathType = enum {
|
|
||||||
unc_absolute,
|
|
||||||
drive_absolute,
|
|
||||||
drive_relative,
|
|
||||||
rooted,
|
|
||||||
relative,
|
|
||||||
root_local_device,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Get the path type of a path that is known to not have any namespace prefixes
|
|
||||||
/// (`\\?\`, `\\.\`, `\??\`).
|
|
||||||
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
|
||||||
pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
|
|
||||||
if (path.len < 1) return .relative;
|
|
||||||
|
|
||||||
if (std.debug.runtime_safety) {
|
if (std.debug.runtime_safety) {
|
||||||
std.debug.assert(getNamespacePrefix(T, path) == .none);
|
std.debug.assert(getWin32PathType(T, path) == .local_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
const windows_path = std.fs.path.PathType.windows;
|
const backslash = mem.nativeToLittle(T, '\\');
|
||||||
if (windows_path.isSep(T, mem.littleToNative(T, path[0]))) {
|
const all_backslash = path[0] == backslash and
|
||||||
// \x
|
path[1] == backslash and
|
||||||
if (path.len < 2 or !windows_path.isSep(T, mem.littleToNative(T, path[1]))) return .rooted;
|
path[3] == backslash;
|
||||||
// exactly \\. or \\? with nothing trailing
|
return switch (path[2]) {
|
||||||
if (path.len == 3 and (mem.littleToNative(T, path[2]) == '.' or mem.littleToNative(T, path[2]) == '?')) return .root_local_device;
|
mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim,
|
||||||
// \\x
|
mem.nativeToLittle(T, '.') => .local_device,
|
||||||
return .unc_absolute;
|
else => unreachable,
|
||||||
} else {
|
};
|
||||||
// x
|
|
||||||
if (path.len < 2 or mem.littleToNative(T, path[1]) != ':') return .relative;
|
|
||||||
// x:\
|
|
||||||
if (path.len > 2 and windows_path.isSep(T, mem.littleToNative(T, path[2]))) return .drive_absolute;
|
|
||||||
// x:
|
|
||||||
return .drive_relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test getUnprefixedPathType {
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, ""));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "x"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "x\\"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "//."));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "/\\?"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "\\\\?"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.unc_absolute, getUnprefixedPathType(u8, "\\\\x"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.unc_absolute, getUnprefixedPathType(u8, "//x"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.rooted, getUnprefixedPathType(u8, "\\x"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.rooted, getUnprefixedPathType(u8, "/"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:abc"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:a/b/c"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:\\"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:\\abc"));
|
|
||||||
try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:/a/b/c"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation.
|
/// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation.
|
||||||
@ -2646,17 +2714,15 @@ test getUnprefixedPathType {
|
|||||||
/// Supports in-place modification (`path` and `out` may refer to the same slice).
|
/// Supports in-place modification (`path` and `out` may refer to the same slice).
|
||||||
pub fn ntToWin32Namespace(path: []const u16, out: []u16) error{ NameTooLong, NotNtPath }![]u16 {
|
pub fn ntToWin32Namespace(path: []const u16, out: []u16) error{ NameTooLong, NotNtPath }![]u16 {
|
||||||
if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
|
if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
|
||||||
|
if (!hasCommonNtPrefix(u16, path)) return error.NotNtPath;
|
||||||
|
|
||||||
const namespace_prefix = getNamespacePrefix(u16, path);
|
|
||||||
switch (namespace_prefix) {
|
|
||||||
.nt => {
|
|
||||||
var dest_index: usize = 0;
|
var dest_index: usize = 0;
|
||||||
var after_prefix = path[4..]; // after the `\??\`
|
var after_prefix = path[4..]; // after the `\??\`
|
||||||
// The prefix \??\UNC\ means this is a UNC path, in which case the
|
// The prefix \??\UNC\ means this is a UNC path, in which case the
|
||||||
// `\??\UNC\` should be replaced by `\\` (two backslashes)
|
// `\??\UNC\` should be replaced by `\\` (two backslashes)
|
||||||
const is_unc = after_prefix.len >= 4 and
|
const is_unc = after_prefix.len >= 4 and
|
||||||
eqlIgnoreCaseWTF16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
|
eqlIgnoreCaseWtf16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
|
||||||
std.fs.path.PathType.windows.isSep(u16, std.mem.littleToNative(u16, after_prefix[3]));
|
std.fs.path.PathType.windows.isSep(u16, after_prefix[3]);
|
||||||
const win32_len = path.len - @as(usize, if (is_unc) 6 else 4);
|
const win32_len = path.len - @as(usize, if (is_unc) 6 else 4);
|
||||||
if (out.len < win32_len) return error.NameTooLong;
|
if (out.len < win32_len) return error.NameTooLong;
|
||||||
if (is_unc) {
|
if (is_unc) {
|
||||||
@ -2667,9 +2733,6 @@ pub fn ntToWin32Namespace(path: []const u16, out: []u16) error{ NameTooLong, Not
|
|||||||
}
|
}
|
||||||
@memmove(out[dest_index..][0..after_prefix.len], after_prefix);
|
@memmove(out[dest_index..][0..after_prefix.len], after_prefix);
|
||||||
return out[0..win32_len];
|
return out[0..win32_len];
|
||||||
},
|
|
||||||
else => return error.NotNtPath,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test ntToWin32Namespace {
|
test ntToWin32Namespace {
|
||||||
|
|||||||
@ -54,8 +54,7 @@ fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "toPrefixedFileW" {
|
test "toPrefixedFileW" {
|
||||||
if (builtin.os.tag != .windows)
|
if (builtin.os.tag != .windows) return error.SkipZigTest;
|
||||||
return;
|
|
||||||
|
|
||||||
// Most test cases come from https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
|
// Most test cases come from https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
|
||||||
// Note that these tests do not actually touch the filesystem or care about whether or not
|
// Note that these tests do not actually touch the filesystem or care about whether or not
|
||||||
@ -237,3 +236,104 @@ test "removeDotDirs" {
|
|||||||
try testRemoveDotDirs("a\\b\\..\\", "a\\");
|
try testRemoveDotDirs("a\\b\\..\\", "a\\");
|
||||||
try testRemoveDotDirs("a\\b\\..\\c", "a\\c");
|
try testRemoveDotDirs("a\\b\\..\\c", "a\\c");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RTL_PATH_TYPE = enum(c_int) {
|
||||||
|
Unknown,
|
||||||
|
UncAbsolute,
|
||||||
|
DriveAbsolute,
|
||||||
|
DriveRelative,
|
||||||
|
Rooted,
|
||||||
|
Relative,
|
||||||
|
LocalDevice,
|
||||||
|
RootLocalDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub extern "ntdll" fn RtlDetermineDosPathNameType_U(
|
||||||
|
Path: [*:0]const u16,
|
||||||
|
) callconv(.winapi) RTL_PATH_TYPE;
|
||||||
|
|
||||||
|
test "getWin32PathType vs RtlDetermineDosPathNameType_U" {
|
||||||
|
if (builtin.os.tag != .windows) return error.SkipZigTest;
|
||||||
|
|
||||||
|
var buf: std.ArrayList(u16) = .empty;
|
||||||
|
defer buf.deinit(std.testing.allocator);
|
||||||
|
|
||||||
|
var wtf8_buf: std.ArrayList(u8) = .empty;
|
||||||
|
defer wtf8_buf.deinit(std.testing.allocator);
|
||||||
|
|
||||||
|
var random = std.Random.DefaultPrng.init(std.testing.random_seed);
|
||||||
|
const rand = random.random();
|
||||||
|
|
||||||
|
for (0..1000) |_| {
|
||||||
|
buf.clearRetainingCapacity();
|
||||||
|
const path = try getRandomWtf16Path(std.testing.allocator, &buf, rand);
|
||||||
|
wtf8_buf.clearRetainingCapacity();
|
||||||
|
const wtf8_len = std.unicode.calcWtf8Len(path);
|
||||||
|
try wtf8_buf.ensureTotalCapacity(std.testing.allocator, wtf8_len);
|
||||||
|
wtf8_buf.items.len = wtf8_len;
|
||||||
|
std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len);
|
||||||
|
|
||||||
|
const windows_type = RtlDetermineDosPathNameType_U(path);
|
||||||
|
const wtf16_type = windows.getWin32PathType(u16, path);
|
||||||
|
const wtf8_type = windows.getWin32PathType(u8, wtf8_buf.items);
|
||||||
|
|
||||||
|
checkPathType(windows_type, wtf16_type) catch |err| {
|
||||||
|
std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) });
|
||||||
|
std.debug.print("path bytes:\n", .{});
|
||||||
|
std.debug.dumpHex(std.mem.sliceAsBytes(path));
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (wtf16_type != wtf8_type) {
|
||||||
|
std.debug.print("type mismatch between wtf8: {} and wtf16: {} for path: {f}\n", .{ wtf8_type, wtf16_type, std.unicode.fmtUtf16Le(path) });
|
||||||
|
std.debug.print("wtf-16 path bytes:\n", .{});
|
||||||
|
std.debug.dumpHex(std.mem.sliceAsBytes(path));
|
||||||
|
std.debug.print("wtf-8 path bytes:\n", .{});
|
||||||
|
std.debug.dumpHex(std.mem.sliceAsBytes(wtf8_buf.items));
|
||||||
|
return error.Wtf8Wtf16Mismatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: windows.Win32PathType) !void {
|
||||||
|
const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) {
|
||||||
|
.unc_absolute => .UncAbsolute,
|
||||||
|
.drive_absolute => .DriveAbsolute,
|
||||||
|
.drive_relative => .DriveRelative,
|
||||||
|
.rooted => .Rooted,
|
||||||
|
.relative => .Relative,
|
||||||
|
.local_device => .LocalDevice,
|
||||||
|
.root_local_device => .RootLocalDevice,
|
||||||
|
};
|
||||||
|
if (windows_type != expected_windows_type) return error.PathTypeMismatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getRandomWtf16Path(allocator: std.mem.Allocator, buf: *std.ArrayList(u16), rand: std.Random) ![:0]const u16 {
|
||||||
|
const Choice = enum {
|
||||||
|
backslash,
|
||||||
|
slash,
|
||||||
|
control,
|
||||||
|
printable,
|
||||||
|
non_ascii,
|
||||||
|
};
|
||||||
|
|
||||||
|
const choices = rand.uintAtMostBiased(u16, 32);
|
||||||
|
|
||||||
|
for (0..choices) |_| {
|
||||||
|
const choice = rand.enumValue(Choice);
|
||||||
|
const code_unit = switch (choice) {
|
||||||
|
.backslash => '\\',
|
||||||
|
.slash => '/',
|
||||||
|
.control => switch (rand.uintAtMostBiased(u8, 0x20)) {
|
||||||
|
0x20 => '\x7F',
|
||||||
|
else => |b| b + 1, // no NUL
|
||||||
|
},
|
||||||
|
.printable => '!' + rand.uintAtMostBiased(u8, '~' - '!'),
|
||||||
|
.non_ascii => rand.intRangeAtMostBiased(u16, 0x80, 0xFFFF),
|
||||||
|
};
|
||||||
|
try buf.append(allocator, std.mem.nativeToLittle(u16, code_unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
try buf.append(allocator, 0);
|
||||||
|
return buf.items[0 .. buf.items.len - 1 :0];
|
||||||
|
}
|
||||||
|
|||||||
@ -22,16 +22,17 @@ pub const GetCwdError = posix.GetCwdError;
|
|||||||
/// The result is a slice of `out_buffer`, from index `0`.
|
/// The result is a slice of `out_buffer`, from index `0`.
|
||||||
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||||
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
||||||
pub fn getCwd(out_buffer: []u8) ![]u8 {
|
pub fn getCwd(out_buffer: []u8) GetCwdError![]u8 {
|
||||||
return posix.getcwd(out_buffer);
|
return posix.getcwd(out_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const GetCwdAllocError = Allocator.Error || posix.GetCwdError;
|
// Same as GetCwdError, minus error.NameTooLong + Allocator.Error
|
||||||
|
pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnlinked} || posix.UnexpectedError;
|
||||||
|
|
||||||
/// Caller must free the returned memory.
|
/// Caller must free the returned memory.
|
||||||
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||||
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
||||||
pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
|
pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {
|
||||||
// The use of max_path_bytes here is just a heuristic: most paths will fit
|
// The use of max_path_bytes here is just a heuristic: most paths will fit
|
||||||
// in stack_buf, avoiding an extra allocation in the common case.
|
// in stack_buf, avoiding an extra allocation in the common case.
|
||||||
var stack_buf: [fs.max_path_bytes]u8 = undefined;
|
var stack_buf: [fs.max_path_bytes]u8 = undefined;
|
||||||
@ -529,6 +530,7 @@ pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!b
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
|
/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
|
||||||
|
/// The returned slice points to memory in the PEB.
|
||||||
///
|
///
|
||||||
/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
|
/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
|
||||||
///
|
///
|
||||||
@ -564,7 +566,7 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const this_key = key_value[0..equal_index];
|
const this_key = key_value[0..equal_index];
|
||||||
if (windows.eqlIgnoreCaseWTF16(key_slice, this_key)) {
|
if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
|
||||||
return key_value[equal_index + 1 ..];
|
return key_value[equal_index + 1 ..];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1227,7 +1227,7 @@ fn windowsCreateProcessPathExt(
|
|||||||
const app_name = app_buf.items[0..app_name_len];
|
const app_name = app_buf.items[0..app_name_len];
|
||||||
const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
|
const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
|
||||||
const ext = app_name[ext_start..];
|
const ext = app_name[ext_start..];
|
||||||
if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
||||||
return error.UnrecoverableInvalidExe;
|
return error.UnrecoverableInvalidExe;
|
||||||
}
|
}
|
||||||
break :unappended err;
|
break :unappended err;
|
||||||
@ -1278,7 +1278,7 @@ fn windowsCreateProcessPathExt(
|
|||||||
// On InvalidExe, if the extension of the app name is .exe then
|
// On InvalidExe, if the extension of the app name is .exe then
|
||||||
// it's treated as an unrecoverable error. Otherwise, it'll be
|
// it's treated as an unrecoverable error. Otherwise, it'll be
|
||||||
// skipped as normal.
|
// skipped as normal.
|
||||||
if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
||||||
return error.UnrecoverableInvalidExe;
|
return error.UnrecoverableInvalidExe;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -643,7 +643,7 @@ const MsvcLibDir = struct {
|
|||||||
|
|
||||||
if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound;
|
if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound;
|
||||||
|
|
||||||
var path_it = std.fs.path.componentIterator(dll_path) catch return error.PathNotFound;
|
var path_it = std.fs.path.componentIterator(dll_path);
|
||||||
// the .dll filename
|
// the .dll filename
|
||||||
_ = path_it.last();
|
_ = path_it.last();
|
||||||
const root_path = while (path_it.previous()) |dir_component| {
|
const root_path = while (path_it.previous()) |dir_component| {
|
||||||
|
|||||||
@ -3883,7 +3883,7 @@ fn createModule(
|
|||||||
if (create_module.sysroot) |root| {
|
if (create_module.sysroot) |root| {
|
||||||
for (create_module.lib_dir_args.items) |lib_dir_arg| {
|
for (create_module.lib_dir_args.items) |lib_dir_arg| {
|
||||||
if (fs.path.isAbsolute(lib_dir_arg)) {
|
if (fs.path.isAbsolute(lib_dir_arg)) {
|
||||||
const stripped_dir = lib_dir_arg[fs.path.diskDesignator(lib_dir_arg).len..];
|
const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..];
|
||||||
const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir });
|
const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir });
|
||||||
addLibDirectoryWarn(&create_module.lib_directories, full_path);
|
addLibDirectoryWarn(&create_module.lib_directories, full_path);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -126,6 +126,9 @@
|
|||||||
.windows_bat_args = .{
|
.windows_bat_args = .{
|
||||||
.path = "windows_bat_args",
|
.path = "windows_bat_args",
|
||||||
},
|
},
|
||||||
|
.windows_paths = .{
|
||||||
|
.path = "windows_paths",
|
||||||
|
},
|
||||||
.self_exe_symlink = .{
|
.self_exe_symlink = .{
|
||||||
.path = "self_exe_symlink",
|
.path = "self_exe_symlink",
|
||||||
},
|
},
|
||||||
|
|||||||
37
test/standalone/windows_paths/build.zig
Normal file
37
test/standalone/windows_paths/build.zig
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const test_step = b.step("test", "Test it");
|
||||||
|
b.default_step = test_step;
|
||||||
|
|
||||||
|
const optimize: std.builtin.OptimizeMode = .Debug;
|
||||||
|
const target = b.graph.host;
|
||||||
|
|
||||||
|
if (builtin.os.tag != .windows) return;
|
||||||
|
|
||||||
|
const relative = b.addExecutable(.{
|
||||||
|
.name = "relative",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("relative.zig"),
|
||||||
|
.optimize = optimize,
|
||||||
|
.target = target,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const main = b.addExecutable(.{
|
||||||
|
.name = "test",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("test.zig"),
|
||||||
|
.optimize = optimize,
|
||||||
|
.target = target,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = b.addRunArtifact(main);
|
||||||
|
run.addArtifactArg(relative);
|
||||||
|
run.expectExitCode(0);
|
||||||
|
run.skip_foreign_checks = true;
|
||||||
|
|
||||||
|
test_step.dependOn(&run.step);
|
||||||
|
}
|
||||||
19
test/standalone/windows_paths/relative.zig
Normal file
19
test/standalone/windows_paths/relative.zig
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||||
|
defer std.debug.assert(gpa.deinit() == .ok);
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const args = try std.process.argsAlloc(allocator);
|
||||||
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
|
if (args.len < 3) return error.MissingArgs;
|
||||||
|
|
||||||
|
const relative = try std.fs.path.relative(allocator, args[1], args[2]);
|
||||||
|
defer allocator.free(relative);
|
||||||
|
|
||||||
|
var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
|
||||||
|
const stdout = &stdout_writer.interface;
|
||||||
|
try stdout.writeAll(relative);
|
||||||
|
}
|
||||||
131
test/standalone/windows_paths/test.zig
Normal file
131
test/standalone/windows_paths/test.zig
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() anyerror!void {
|
||||||
|
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
defer arena_state.deinit();
|
||||||
|
const arena = arena_state.allocator();
|
||||||
|
|
||||||
|
const args = try std.process.argsAlloc(arena);
|
||||||
|
|
||||||
|
if (args.len < 2) return error.MissingArgs;
|
||||||
|
|
||||||
|
const exe_path = args[1];
|
||||||
|
|
||||||
|
const cwd_path = try std.process.getCwdAlloc(arena);
|
||||||
|
const parsed_cwd_path = std.fs.path.parsePathWindows(u8, cwd_path);
|
||||||
|
|
||||||
|
if (parsed_cwd_path.kind == .drive_absolute and !std.ascii.isAlphabetic(cwd_path[0])) {
|
||||||
|
// Technically possible, but not worth supporting here
|
||||||
|
return error.NonAlphabeticDriveLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alt_drive_letter = try getAltDriveLetter(cwd_path);
|
||||||
|
const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter});
|
||||||
|
const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter});
|
||||||
|
var alt_drive_env_map = std.process.EnvMap.init(arena);
|
||||||
|
try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd);
|
||||||
|
|
||||||
|
const empty_env = std.process.EnvMap.init(arena);
|
||||||
|
|
||||||
|
{
|
||||||
|
const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter});
|
||||||
|
const drive_abs = try std.fmt.allocPrint(arena, "{c}:\\bar", .{alt_drive_letter});
|
||||||
|
|
||||||
|
// With the special =X: environment variable set, drive-relative paths that
|
||||||
|
// don't match the CWD's drive letter are resolved against that env var.
|
||||||
|
try checkRelative(arena, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map);
|
||||||
|
try checkRelative(arena, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map);
|
||||||
|
|
||||||
|
// Without that environment variable set, drive-relative paths that don't match the
|
||||||
|
// CWD's drive letter are resolved against the root of the drive.
|
||||||
|
try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
|
||||||
|
try checkRelative(arena, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
|
||||||
|
|
||||||
|
// Bare drive-relative path with no components
|
||||||
|
try checkRelative(arena, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env);
|
||||||
|
try checkRelative(arena, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env);
|
||||||
|
|
||||||
|
// Bare drive-relative path with no components, drive-CWD set
|
||||||
|
try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map);
|
||||||
|
try checkRelative(arena, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map);
|
||||||
|
|
||||||
|
// Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map);
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map);
|
||||||
|
|
||||||
|
// Bare drive-relative should always be equivalent to itself
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed_cwd_path.kind == .unc_absolute) {
|
||||||
|
const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter});
|
||||||
|
|
||||||
|
{
|
||||||
|
try checkRelative(arena, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env);
|
||||||
|
try checkRelative(arena, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env);
|
||||||
|
}
|
||||||
|
} else if (parsed_cwd_path.kind == .drive_absolute) {
|
||||||
|
const cur_drive_letter = parsed_cwd_path.root[0];
|
||||||
|
const path_beyond_root = cwd_path[3..];
|
||||||
|
const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root });
|
||||||
|
|
||||||
|
{
|
||||||
|
try checkRelative(arena, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env);
|
||||||
|
try checkRelative(arena, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const drive_abs = cwd_path;
|
||||||
|
const drive_rel = parsed_cwd_path.root[0..2];
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
|
||||||
|
try checkRelative(arena, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.UnexpectedPathType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkRelative(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
expected_stdout: []const u8,
|
||||||
|
argv: []const []const u8,
|
||||||
|
cwd: ?[]const u8,
|
||||||
|
env_map: ?*const std.process.EnvMap,
|
||||||
|
) !void {
|
||||||
|
const result = try std.process.Child.run(.{
|
||||||
|
.allocator = allocator,
|
||||||
|
.argv = argv,
|
||||||
|
.cwd = cwd,
|
||||||
|
.env_map = env_map,
|
||||||
|
});
|
||||||
|
defer allocator.free(result.stdout);
|
||||||
|
defer allocator.free(result.stderr);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("", result.stderr);
|
||||||
|
try std.testing.expectEqualStrings(expected_stdout, result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getAltDriveLetter(path: []const u8) !u8 {
|
||||||
|
const parsed = std.fs.path.parsePathWindows(u8, path);
|
||||||
|
return switch (parsed.kind) {
|
||||||
|
.drive_absolute => {
|
||||||
|
const cur_drive_letter = parsed.root[0];
|
||||||
|
const next_drive_letter_index = (std.ascii.toUpper(cur_drive_letter) - 'A' + 1) % 26;
|
||||||
|
const next_drive_letter = next_drive_letter_index + 'A';
|
||||||
|
return next_drive_letter;
|
||||||
|
},
|
||||||
|
.unc_absolute => {
|
||||||
|
return 'C';
|
||||||
|
},
|
||||||
|
else => return error.UnexpectedPathType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test getAltDriveLetter {
|
||||||
|
try std.testing.expectEqual('D', try getAltDriveLetter("C:\\"));
|
||||||
|
try std.testing.expectEqual('B', try getAltDriveLetter("a:\\"));
|
||||||
|
try std.testing.expectEqual('A', try getAltDriveLetter("Z:\\"));
|
||||||
|
try std.testing.expectEqual('C', try getAltDriveLetter("\\\\foo\\bar"));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user