mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Compare commits
9 Commits
289f2f0d34
...
53e615b920
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53e615b920 | ||
|
|
32dc46aae5 | ||
|
|
476d7d939c | ||
|
|
3c647ca6bb | ||
|
|
822f412424 | ||
|
|
59b8bed222 | ||
|
|
26afcdb7fe | ||
|
|
fb1bd78908 | ||
|
|
adf74ba4fb |
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).
|
||||
// Those path types are something of an unavoidable way to
|
||||
// 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| {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||
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 {
|
||||
const relative = try fs.path.relative(allocator, prefix, path);
|
||||
errdefer allocator.free(relative);
|
||||
var component_iterator = fs.path.NativeComponentIterator.init(relative) catch {
|
||||
return error.NotASubPath;
|
||||
};
|
||||
var component_iterator = fs.path.NativeComponentIterator.init(relative);
|
||||
if (component_iterator.root() != null) {
|
||||
return error.NotASubPath;
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
|
||||
}.lessThan);
|
||||
need_dirs.clearRetainingCapacity();
|
||||
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| {
|
||||
if (need_dirs.contains(component.path)) {
|
||||
// this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
|
||||
|
||||
@ -626,8 +626,9 @@ pub const VTable = struct {
|
||||
/// Thread-safe.
|
||||
cancelRequested: *const fn (?*anyopaque) bool,
|
||||
|
||||
/// Executes `start` asynchronously in a manner such that it cleans itself
|
||||
/// up. This mode does not support results, await, or cancel.
|
||||
/// When this function returns, implementation guarantees that `start` has
|
||||
/// either already been called, or a unit of concurrency has been assigned
|
||||
/// to the task of calling the function.
|
||||
///
|
||||
/// Thread-safe.
|
||||
groupAsync: *const fn (
|
||||
@ -640,6 +641,17 @@ pub const VTable = struct {
|
||||
context_alignment: std.mem.Alignment,
|
||||
start: *const fn (*Group, context: *const anyopaque) void,
|
||||
) void,
|
||||
/// Thread-safe.
|
||||
groupConcurrent: *const fn (
|
||||
/// Corresponds to `Io.userdata`.
|
||||
userdata: ?*anyopaque,
|
||||
/// Owner of the spawned async task.
|
||||
group: *Group,
|
||||
/// Copied and then passed to `start`.
|
||||
context: []const u8,
|
||||
context_alignment: std.mem.Alignment,
|
||||
start: *const fn (*Group, context: *const anyopaque) void,
|
||||
) ConcurrentError!void,
|
||||
groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
|
||||
groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
|
||||
|
||||
@ -1021,8 +1033,8 @@ pub const Group = struct {
|
||||
/// Threadsafe.
|
||||
///
|
||||
/// See also:
|
||||
/// * `Io.async`
|
||||
/// * `concurrent`
|
||||
/// * `Io.async`
|
||||
pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
|
||||
const Args = @TypeOf(args);
|
||||
const TypeErased = struct {
|
||||
@ -1035,6 +1047,34 @@ pub const Group = struct {
|
||||
io.vtable.groupAsync(io.userdata, g, @ptrCast(&args), .of(Args), TypeErased.start);
|
||||
}
|
||||
|
||||
/// Calls `function` with `args`, such that the function is not guaranteed
|
||||
/// to have returned until `wait` is called, allowing the caller to
|
||||
/// progress while waiting for any `Io` operations.
|
||||
///
|
||||
/// The resource spawned is owned by the group; after this is called,
|
||||
/// `wait` or `cancel` must be called before the group is deinitialized.
|
||||
///
|
||||
/// This has stronger guarantee than `async`, placing restrictions on what kind
|
||||
/// of `Io` implementations are supported. By calling `async` instead, one
|
||||
/// allows, for example, stackful single-threaded blocking I/O.
|
||||
///
|
||||
/// Threadsafe.
|
||||
///
|
||||
/// See also:
|
||||
/// * `async`
|
||||
/// * `Io.concurrent`
|
||||
pub fn concurrent(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) ConcurrentError!void {
|
||||
const Args = @TypeOf(args);
|
||||
const TypeErased = struct {
|
||||
fn start(group: *Group, context: *const anyopaque) void {
|
||||
_ = group;
|
||||
const args_casted: *const Args = @ptrCast(@alignCast(context));
|
||||
@call(.auto, function, args_casted.*);
|
||||
}
|
||||
};
|
||||
return io.vtable.groupConcurrent(io.userdata, g, @ptrCast(&args), .of(Args), TypeErased.start);
|
||||
}
|
||||
|
||||
/// Blocks until all tasks of the group finish. During this time,
|
||||
/// cancellation requests propagate to all members of the group.
|
||||
///
|
||||
|
||||
@ -318,7 +318,7 @@ pub const MakePathStatus = enum { existed, created };
|
||||
/// Same as `makePath` except returns whether the path already existed or was
|
||||
/// successfully created.
|
||||
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 component = it.last() orelse return error.BadPathName;
|
||||
while (true) {
|
||||
|
||||
@ -215,7 +215,7 @@ pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
|
||||
|
||||
pub const ReadPositionalError = Reader.Error || error{Unseekable};
|
||||
|
||||
pub fn readPositional(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
|
||||
pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize {
|
||||
return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
|
||||
}
|
||||
|
||||
|
||||
@ -116,6 +116,7 @@ pub fn init(
|
||||
/// * `Io.VTable.async`
|
||||
/// * `Io.VTable.concurrent`
|
||||
/// * `Io.VTable.groupAsync`
|
||||
/// * `Io.VTable.groupConcurrent`
|
||||
/// If these functions are avoided, then `Allocator.failing` may be passed
|
||||
/// here.
|
||||
gpa: Allocator,
|
||||
@ -221,6 +222,7 @@ pub fn io(t: *Threaded) Io {
|
||||
.select = select,
|
||||
|
||||
.groupAsync = groupAsync,
|
||||
.groupConcurrent = groupConcurrent,
|
||||
.groupWait = groupWait,
|
||||
.groupCancel = groupCancel,
|
||||
|
||||
@ -317,6 +319,7 @@ pub fn ioBasic(t: *Threaded) Io {
|
||||
.select = select,
|
||||
|
||||
.groupAsync = groupAsync,
|
||||
.groupConcurrent = groupConcurrent,
|
||||
.groupWait = groupWait,
|
||||
.groupCancel = groupCancel,
|
||||
|
||||
@ -729,6 +732,57 @@ fn groupAsync(
|
||||
t.cond.signal();
|
||||
}
|
||||
|
||||
fn groupConcurrent(
|
||||
userdata: ?*anyopaque,
|
||||
group: *Io.Group,
|
||||
context: []const u8,
|
||||
context_alignment: Alignment,
|
||||
start: *const fn (*Io.Group, context: *const anyopaque) void,
|
||||
) Io.ConcurrentError!void {
|
||||
if (builtin.single_threaded) return error.ConcurrencyUnavailable;
|
||||
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
|
||||
const gpa = t.allocator;
|
||||
const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch
|
||||
return error.ConcurrencyUnavailable;
|
||||
|
||||
t.mutex.lock();
|
||||
defer t.mutex.unlock();
|
||||
|
||||
const busy_count = t.busy_count;
|
||||
|
||||
if (busy_count >= @intFromEnum(t.concurrent_limit))
|
||||
return error.ConcurrencyUnavailable;
|
||||
|
||||
t.busy_count = busy_count + 1;
|
||||
errdefer t.busy_count = busy_count;
|
||||
|
||||
const pool_size = t.wait_group.value();
|
||||
if (pool_size - busy_count == 0) {
|
||||
t.wait_group.start();
|
||||
errdefer t.wait_group.finish();
|
||||
|
||||
const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
|
||||
return error.ConcurrencyUnavailable;
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
// Append to the group linked list inside the mutex to make `Io.Group.concurrent` thread-safe.
|
||||
gc.node = .{ .next = @ptrCast(@alignCast(group.token)) };
|
||||
group.token = &gc.node;
|
||||
|
||||
t.run_queue.prepend(&gc.closure.node);
|
||||
|
||||
// This needs to be done before unlocking the mutex to avoid a race with
|
||||
// the associated task finishing.
|
||||
const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
|
||||
const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
|
||||
assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
|
||||
|
||||
t.cond.signal();
|
||||
}
|
||||
|
||||
fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const gpa = t.allocator;
|
||||
@ -1156,7 +1210,7 @@ fn dirMakeOpenPathWindows(
|
||||
w.SYNCHRONIZE | w.FILE_TRAVERSE |
|
||||
(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.
|
||||
var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
|
||||
.name = "",
|
||||
|
||||
@ -172,6 +172,32 @@ fn sleep(io: Io, result: *usize) void {
|
||||
result.* = 1;
|
||||
}
|
||||
|
||||
test "Group concurrent" {
|
||||
const io = testing.io;
|
||||
|
||||
var group: Io.Group = .init;
|
||||
defer group.cancel(io);
|
||||
var results: [2]usize = undefined;
|
||||
|
||||
group.concurrent(io, count, .{ 1, 10, &results[0] }) catch |err| switch (err) {
|
||||
error.ConcurrencyUnavailable => {
|
||||
try testing.expect(builtin.single_threaded);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
group.concurrent(io, count, .{ 20, 30, &results[1] }) catch |err| switch (err) {
|
||||
error.ConcurrencyUnavailable => {
|
||||
try testing.expect(builtin.single_threaded);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
group.wait(io);
|
||||
|
||||
try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
|
||||
}
|
||||
|
||||
test "select" {
|
||||
const io = testing.io;
|
||||
|
||||
|
||||
@ -376,7 +376,6 @@ fn Kyber(comptime p: Params) type {
|
||||
/// Except in tests, applications should generally call `generate()` instead of this function.
|
||||
pub fn generateDeterministic(seed: [seed_length]u8) !KeyPair {
|
||||
var ret: KeyPair = undefined;
|
||||
ret.secret_key.z = seed[inner_seed_length..seed_length].*;
|
||||
|
||||
// Generate inner key
|
||||
innerKeyFromSeed(
|
||||
@ -507,8 +506,8 @@ fn Kyber(comptime p: Params) type {
|
||||
fn innerKeyFromSeed(seed: [inner_seed_length]u8, pk: *InnerPk, sk: *InnerSk) void {
|
||||
var expanded_seed: [64]u8 = undefined;
|
||||
var h = sha3.Sha3_512.init(.{});
|
||||
if (p.ml_kem) h.update(&[1]u8{p.k});
|
||||
h.update(&seed);
|
||||
if (p.ml_kem) h.update(&[1]u8{p.k});
|
||||
h.final(&expanded_seed);
|
||||
pk.rho = expanded_seed[0..32].*;
|
||||
const sigma = expanded_seed[32..64];
|
||||
|
||||
1572
lib/std/fs/path.zig
1572
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.
|
||||
var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
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) {
|
||||
.unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
|
||||
.drive_absolute => {
|
||||
|
||||
@ -816,8 +816,11 @@ pub fn CreateSymbolicLink(
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
|
||||
var is_target_absolute = false;
|
||||
const final_target_path = target_path: {
|
||||
switch (getNamespacePrefix(u16, target_path)) {
|
||||
.none => switch (getUnprefixedPathType(u16, target_path)) {
|
||||
if (hasCommonNtPrefix(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
|
||||
// (and they are treated as relative in this context)
|
||||
// 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.
|
||||
else => if (!std.fs.path.isAbsoluteWindowsWtf16(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);
|
||||
// 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
|
||||
/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
|
||||
/// 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) {
|
||||
// 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
|
||||
@ -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 {
|
||||
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(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(b),
|
||||
));
|
||||
}
|
||||
|
||||
test "eqlIgnoreCaseWTF16/Wtf8" {
|
||||
test "eqlIgnoreCaseWtf16/Wtf8" {
|
||||
try testEqlIgnoreCase(true, "\x01 a B Ξ Ι", "\x01 A b Ξ» β±―");
|
||||
// does not do case-insensitive comparison for codepoints >= U+10000
|
||||
try testEqlIgnoreCase(false, "π", "π·");
|
||||
@ -2365,158 +2365,309 @@ pub const Wtf16ToPrefixedFileWError = error{
|
||||
/// - . and space are not stripped from the end of relative paths (potential TODO)
|
||||
pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace {
|
||||
const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
|
||||
switch (getNamespacePrefix(u16, path)) {
|
||||
// TODO: Figure out a way to design an API that can avoid the copy for .nt,
|
||||
if (hasCommonNtPrefix(u16, path)) {
|
||||
// TODO: Figure out a way to design an API that can avoid the copy for NT,
|
||||
// since it is always returned fully unmodified.
|
||||
.nt, .verbatim => {
|
||||
var path_space: PathSpace = undefined;
|
||||
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 => {
|
||||
var path_space: PathSpace = undefined;
|
||||
const path_byte_len = ntdll.RtlGetFullPathName_U(
|
||||
path.ptr,
|
||||
path_space.data.len * 2,
|
||||
&path_space.data,
|
||||
null,
|
||||
);
|
||||
if (path_byte_len == 0) {
|
||||
// TODO: This may not be the right error
|
||||
return error.BadPathName;
|
||||
} else if (path_byte_len / 2 > path_space.data.len) {
|
||||
return error.NameTooLong;
|
||||
}
|
||||
path_space.len = path_byte_len / 2;
|
||||
// Both prefixes will be normalized but retained, so all
|
||||
// we need to do now is replace them with the NT prefix
|
||||
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||
return path_space;
|
||||
},
|
||||
.none => {
|
||||
const path_type = getUnprefixedPathType(u16, path);
|
||||
var path_space: PathSpace = undefined;
|
||||
relative: {
|
||||
if (path_type == .relative) {
|
||||
// TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
|
||||
// See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
|
||||
|
||||
// TODO: Potentially strip all trailing . and space characters from the
|
||||
// end of the path. This is something that both RtlDosPathNameToNtPathName_U
|
||||
// and RtlGetFullPathName_U do. Technically, trailing . and spaces
|
||||
// are allowed, but such paths may not interact well with Windows (i.e.
|
||||
// files with these paths can't be deleted from explorer.exe, etc).
|
||||
// This could be something that normalizePath may want to do.
|
||||
|
||||
@memcpy(path_space.data[0..path.len], path);
|
||||
// Try to normalize, but if we get too many parent directories,
|
||||
// then we need to start over and use RtlGetFullPathName_U instead.
|
||||
path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) {
|
||||
error.TooManyParentDirs => break :relative,
|
||||
};
|
||||
var path_space: PathSpace = undefined;
|
||||
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;
|
||||
} 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 => {
|
||||
const path_byte_len = ntdll.RtlGetFullPathName_U(
|
||||
path.ptr,
|
||||
path_space.data.len * 2,
|
||||
&path_space.data,
|
||||
null,
|
||||
);
|
||||
if (path_byte_len == 0) {
|
||||
// TODO: This may not be the right error
|
||||
return error.BadPathName;
|
||||
} else if (path_byte_len / 2 > path_space.data.len) {
|
||||
return error.NameTooLong;
|
||||
}
|
||||
path_space.len = path_byte_len / 2;
|
||||
// Both prefixes will be normalized but retained, so all
|
||||
// we need to do now is replace them with the NT prefix
|
||||
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||
return path_space;
|
||||
},
|
||||
}
|
||||
// We now know we are going to return an absolute NT path, so
|
||||
// we can unconditionally prefix it with the NT prefix.
|
||||
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||
if (path_type == .root_local_device) {
|
||||
// `\\.` and `\\?` always get converted to `\??\` exactly, so
|
||||
// we can just stop here
|
||||
path_space.len = nt_prefix.len;
|
||||
}
|
||||
relative: {
|
||||
if (path_type == .relative) {
|
||||
// TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
|
||||
// See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
|
||||
|
||||
// TODO: Potentially strip all trailing . and space characters from the
|
||||
// end of the path. This is something that both RtlDosPathNameToNtPathName_U
|
||||
// and RtlGetFullPathName_U do. Technically, trailing . and spaces
|
||||
// are allowed, but such paths may not interact well with Windows (i.e.
|
||||
// files with these paths can't be deleted from explorer.exe, etc).
|
||||
// This could be something that normalizePath may want to do.
|
||||
|
||||
@memcpy(path_space.data[0..path.len], path);
|
||||
// Try to normalize, but if we get too many parent directories,
|
||||
// then we need to start over and use RtlGetFullPathName_U instead.
|
||||
path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) {
|
||||
error.TooManyParentDirs => break :relative,
|
||||
};
|
||||
path_space.data[path_space.len] = 0;
|
||||
return path_space;
|
||||
}
|
||||
const path_buf_offset = switch (path_type) {
|
||||
// UNC paths will always start with `\\`. However, we want to
|
||||
// end up with something like `\??\UNC\server\share`, so to get
|
||||
// RtlGetFullPathName to write into the spot we want the `server`
|
||||
// part to end up, we need to provide an offset such that
|
||||
// the `\\` part gets written where the `C\` of `UNC\` will be
|
||||
// in the final NT path.
|
||||
.unc_absolute => nt_prefix.len + 2,
|
||||
else => nt_prefix.len,
|
||||
}
|
||||
// We now know we are going to return an absolute NT path, so
|
||||
// we can unconditionally prefix it with the NT prefix.
|
||||
path_space.data[0..nt_prefix.len].* = nt_prefix;
|
||||
if (path_type == .root_local_device) {
|
||||
// `\\.` and `\\?` always get converted to `\??\` exactly, so
|
||||
// we can just stop here
|
||||
path_space.len = nt_prefix.len;
|
||||
path_space.data[path_space.len] = 0;
|
||||
return path_space;
|
||||
}
|
||||
const path_buf_offset = switch (path_type) {
|
||||
// UNC paths will always start with `\\`. However, we want to
|
||||
// end up with something like `\??\UNC\server\share`, so to get
|
||||
// RtlGetFullPathName to write into the spot we want the `server`
|
||||
// part to end up, we need to provide an offset such that
|
||||
// the `\\` part gets written where the `C\` of `UNC\` will be
|
||||
// in the final NT path.
|
||||
.unc_absolute => nt_prefix.len + 2,
|
||||
else => nt_prefix.len,
|
||||
};
|
||||
const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset);
|
||||
const path_to_get: [:0]const u16 = path_to_get: {
|
||||
// If dir is null, then we don't need to bother with GetFinalPathNameByHandle because
|
||||
// RtlGetFullPathName_U will resolve relative paths against the CWD for us.
|
||||
if (path_type != .relative or dir == null) {
|
||||
break :path_to_get path;
|
||||
}
|
||||
// We can also skip GetFinalPathNameByHandle if the handle matches
|
||||
// the handle returned by fs.cwd()
|
||||
if (dir.? == std.fs.cwd().fd) {
|
||||
break :path_to_get path;
|
||||
}
|
||||
// At this point, we know we have a relative path that had too many
|
||||
// `..` components to be resolved by normalizePath, so we need to
|
||||
// convert it into an absolute path and let RtlGetFullPathName_U
|
||||
// canonicalize it. We do this by getting the path of the `dir`
|
||||
// and appending the relative path to it.
|
||||
var dir_path_buf: [PATH_MAX_WIDE:0]u16 = undefined;
|
||||
const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) {
|
||||
// This mapping is not correct; it is actually expected
|
||||
// that calling GetFinalPathNameByHandle might return
|
||||
// error.UnrecognizedVolume, and in fact has been observed
|
||||
// in the wild. The problem is that wToPrefixedFileW was
|
||||
// never intended to make *any* OS syscall APIs. It's only
|
||||
// supposed to convert a string to one that is eligible to
|
||||
// be used in the ntdll syscalls.
|
||||
//
|
||||
// To solve this, this function needs to no longer call
|
||||
// GetFinalPathNameByHandle under any conditions, or the
|
||||
// calling function needs to get reworked to not need to
|
||||
// call this function.
|
||||
//
|
||||
// This may involve making breaking API changes.
|
||||
error.UnrecognizedVolume => return error.Unexpected,
|
||||
else => |e| return e,
|
||||
};
|
||||
const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset);
|
||||
const path_to_get: [:0]const u16 = path_to_get: {
|
||||
// If dir is null, then we don't need to bother with GetFinalPathNameByHandle because
|
||||
// RtlGetFullPathName_U will resolve relative paths against the CWD for us.
|
||||
if (path_type != .relative or dir == null) {
|
||||
break :path_to_get path;
|
||||
}
|
||||
// We can also skip GetFinalPathNameByHandle if the handle matches
|
||||
// the handle returned by fs.cwd()
|
||||
if (dir.? == std.fs.cwd().fd) {
|
||||
break :path_to_get path;
|
||||
}
|
||||
// At this point, we know we have a relative path that had too many
|
||||
// `..` components to be resolved by normalizePath, so we need to
|
||||
// convert it into an absolute path and let RtlGetFullPathName_U
|
||||
// canonicalize it. We do this by getting the path of the `dir`
|
||||
// and appending the relative path to it.
|
||||
var dir_path_buf: [PATH_MAX_WIDE:0]u16 = undefined;
|
||||
const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) {
|
||||
// This mapping is not correct; it is actually expected
|
||||
// that calling GetFinalPathNameByHandle might return
|
||||
// error.UnrecognizedVolume, and in fact has been observed
|
||||
// in the wild. The problem is that wToPrefixedFileW was
|
||||
// never intended to make *any* OS syscall APIs. It's only
|
||||
// supposed to convert a string to one that is eligible to
|
||||
// be used in the ntdll syscalls.
|
||||
//
|
||||
// To solve this, this function needs to no longer call
|
||||
// GetFinalPathNameByHandle under any conditions, or the
|
||||
// calling function needs to get reworked to not need to
|
||||
// call this function.
|
||||
//
|
||||
// This may involve making breaking API changes.
|
||||
error.UnrecognizedVolume => return error.Unexpected,
|
||||
else => |e| return e,
|
||||
};
|
||||
if (dir_path.len + 1 + path.len > PATH_MAX_WIDE) {
|
||||
return error.NameTooLong;
|
||||
}
|
||||
// We don't have to worry about potentially doubling up path separators
|
||||
// here since RtlGetFullPathName_U will handle canonicalizing it.
|
||||
dir_path_buf[dir_path.len] = '\\';
|
||||
@memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path);
|
||||
const full_len = dir_path.len + 1 + path.len;
|
||||
dir_path_buf[full_len] = 0;
|
||||
break :path_to_get dir_path_buf[0..full_len :0];
|
||||
};
|
||||
const path_byte_len = ntdll.RtlGetFullPathName_U(
|
||||
path_to_get.ptr,
|
||||
buf_len * 2,
|
||||
path_space.data[path_buf_offset..].ptr,
|
||||
null,
|
||||
);
|
||||
if (path_byte_len == 0) {
|
||||
// TODO: This may not be the right error
|
||||
return error.BadPathName;
|
||||
} else if (path_byte_len / 2 > buf_len) {
|
||||
if (dir_path.len + 1 + path.len > PATH_MAX_WIDE) {
|
||||
return error.NameTooLong;
|
||||
}
|
||||
path_space.len = path_buf_offset + (path_byte_len / 2);
|
||||
if (path_type == .unc_absolute) {
|
||||
// Now add in the UNC, the `C` should overwrite the first `\` of the
|
||||
// FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
|
||||
std.debug.assert(path_space.data[path_buf_offset] == '\\');
|
||||
std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');
|
||||
const unc = [_]u16{ 'U', 'N', 'C' };
|
||||
path_space.data[nt_prefix.len..][0..unc.len].* = unc;
|
||||
}
|
||||
return path_space;
|
||||
},
|
||||
// We don't have to worry about potentially doubling up path separators
|
||||
// here since RtlGetFullPathName_U will handle canonicalizing it.
|
||||
dir_path_buf[dir_path.len] = '\\';
|
||||
@memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path);
|
||||
const full_len = dir_path.len + 1 + path.len;
|
||||
dir_path_buf[full_len] = 0;
|
||||
break :path_to_get dir_path_buf[0..full_len :0];
|
||||
};
|
||||
const path_byte_len = ntdll.RtlGetFullPathName_U(
|
||||
path_to_get.ptr,
|
||||
buf_len * 2,
|
||||
path_space.data[path_buf_offset..].ptr,
|
||||
null,
|
||||
);
|
||||
if (path_byte_len == 0) {
|
||||
// TODO: This may not be the right error
|
||||
return error.BadPathName;
|
||||
} else if (path_byte_len / 2 > buf_len) {
|
||||
return error.NameTooLong;
|
||||
}
|
||||
path_space.len = path_buf_offset + (path_byte_len / 2);
|
||||
if (path_type == .unc_absolute) {
|
||||
// Now add in the UNC, the `C` should overwrite the first `\` of the
|
||||
// FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
|
||||
std.debug.assert(path_space.data[path_buf_offset] == '\\');
|
||||
std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');
|
||||
const unc = [_]u16{ 'U', 'N', 'C' };
|
||||
path_space.data[nt_prefix.len..][0..unc.len].* = unc;
|
||||
}
|
||||
return path_space;
|
||||
}
|
||||
}
|
||||
|
||||
pub const NamespacePrefix = enum {
|
||||
none,
|
||||
/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
|
||||
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 `/`)
|
||||
local_device,
|
||||
/// `\\?\`
|
||||
@ -2529,107 +2680,24 @@ pub const NamespacePrefix = enum {
|
||||
/// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't
|
||||
/// be treated as part of the final path])
|
||||
fake_verbatim,
|
||||
/// `\??\`
|
||||
nt,
|
||||
};
|
||||
|
||||
/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
|
||||
pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
|
||||
if (path.len < 4) return .none;
|
||||
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;
|
||||
|
||||
/// Only relevant for Win32 -> NT path conversion.
|
||||
/// Asserts `path` is of type `Win32PathType.local_device`.
|
||||
fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
|
||||
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;
|
||||
if (windows_path.isSep(T, mem.littleToNative(T, path[0]))) {
|
||||
// \x
|
||||
if (path.len < 2 or !windows_path.isSep(T, mem.littleToNative(T, path[1]))) return .rooted;
|
||||
// exactly \\. or \\? with nothing trailing
|
||||
if (path.len == 3 and (mem.littleToNative(T, path[2]) == '.' or mem.littleToNative(T, path[2]) == '?')) return .root_local_device;
|
||||
// \\x
|
||||
return .unc_absolute;
|
||||
} 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"));
|
||||
const backslash = mem.nativeToLittle(T, '\\');
|
||||
const all_backslash = path[0] == backslash and
|
||||
path[1] == backslash and
|
||||
path[3] == backslash;
|
||||
return switch (path[2]) {
|
||||
mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim,
|
||||
mem.nativeToLittle(T, '.') => .local_device,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation.
|
||||
@ -2646,30 +2714,25 @@ test getUnprefixedPathType {
|
||||
/// 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 {
|
||||
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 after_prefix = path[4..]; // after the `\??\`
|
||||
// The prefix \??\UNC\ means this is a UNC path, in which case the
|
||||
// `\??\UNC\` should be replaced by `\\` (two backslashes)
|
||||
const is_unc = after_prefix.len >= 4 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]));
|
||||
const win32_len = path.len - @as(usize, if (is_unc) 6 else 4);
|
||||
if (out.len < win32_len) return error.NameTooLong;
|
||||
if (is_unc) {
|
||||
out[0] = comptime std.mem.nativeToLittle(u16, '\\');
|
||||
dest_index += 1;
|
||||
// We want to include the last `\` of `\??\UNC\`
|
||||
after_prefix = path[7..];
|
||||
}
|
||||
@memmove(out[dest_index..][0..after_prefix.len], after_prefix);
|
||||
return out[0..win32_len];
|
||||
},
|
||||
else => return error.NotNtPath,
|
||||
var dest_index: usize = 0;
|
||||
var after_prefix = path[4..]; // after the `\??\`
|
||||
// The prefix \??\UNC\ means this is a UNC path, in which case the
|
||||
// `\??\UNC\` should be replaced by `\\` (two backslashes)
|
||||
const is_unc = after_prefix.len >= 4 and
|
||||
eqlIgnoreCaseWtf16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
|
||||
std.fs.path.PathType.windows.isSep(u16, after_prefix[3]);
|
||||
const win32_len = path.len - @as(usize, if (is_unc) 6 else 4);
|
||||
if (out.len < win32_len) return error.NameTooLong;
|
||||
if (is_unc) {
|
||||
out[0] = comptime std.mem.nativeToLittle(u16, '\\');
|
||||
dest_index += 1;
|
||||
// We want to include the last `\` of `\??\UNC\`
|
||||
after_prefix = path[7..];
|
||||
}
|
||||
@memmove(out[dest_index..][0..after_prefix.len], after_prefix);
|
||||
return out[0..win32_len];
|
||||
}
|
||||
|
||||
test ntToWin32Namespace {
|
||||
|
||||
@ -54,8 +54,7 @@ fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void {
|
||||
}
|
||||
|
||||
test "toPrefixedFileW" {
|
||||
if (builtin.os.tag != .windows)
|
||||
return;
|
||||
if (builtin.os.tag != .windows) return error.SkipZigTest;
|
||||
|
||||
// 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
|
||||
@ -237,3 +236,104 @@ test "removeDotDirs" {
|
||||
try testRemoveDotDirs("a\\b\\..\\", "a\\");
|
||||
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`.
|
||||
/// 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.
|
||||
pub fn getCwd(out_buffer: []u8) ![]u8 {
|
||||
pub fn getCwd(out_buffer: []u8) GetCwdError![]u8 {
|
||||
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.
|
||||
/// 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.
|
||||
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
|
||||
// in stack_buf, avoiding an extra allocation in the common case.
|
||||
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.
|
||||
/// The returned slice points to memory in the PEB.
|
||||
///
|
||||
/// 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];
|
||||
if (windows.eqlIgnoreCaseWTF16(key_slice, this_key)) {
|
||||
if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
|
||||
return key_value[equal_index + 1 ..];
|
||||
}
|
||||
|
||||
|
||||
@ -1227,7 +1227,7 @@ fn windowsCreateProcessPathExt(
|
||||
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 = app_name[ext_start..];
|
||||
if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
||||
if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
||||
return error.UnrecoverableInvalidExe;
|
||||
}
|
||||
break :unappended err;
|
||||
@ -1278,7 +1278,7 @@ fn windowsCreateProcessPathExt(
|
||||
// On InvalidExe, if the extension of the app name is .exe then
|
||||
// it's treated as an unrecoverable error. Otherwise, it'll be
|
||||
// skipped as normal.
|
||||
if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
||||
if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
|
||||
return error.UnrecoverableInvalidExe;
|
||||
}
|
||||
continue;
|
||||
|
||||
@ -643,7 +643,7 @@ const MsvcLibDir = struct {
|
||||
|
||||
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
|
||||
_ = path_it.last();
|
||||
const root_path = while (path_it.previous()) |dir_component| {
|
||||
|
||||
@ -3883,7 +3883,7 @@ fn createModule(
|
||||
if (create_module.sysroot) |root| {
|
||||
for (create_module.lib_dir_args.items) |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 });
|
||||
addLibDirectoryWarn(&create_module.lib_directories, full_path);
|
||||
} else {
|
||||
|
||||
@ -126,6 +126,9 @@
|
||||
.windows_bat_args = .{
|
||||
.path = "windows_bat_args",
|
||||
},
|
||||
.windows_paths = .{
|
||||
.path = "windows_paths",
|
||||
},
|
||||
.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