Skip to content

Commit 7354587

Browse files
committed
use ntdll to convert NT paths
1 parent 6e26226 commit 7354587

File tree

5 files changed

+120
-31
lines changed

5 files changed

+120
-31
lines changed

lib/std/fs.zig

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,8 +1169,9 @@ pub const Dir = struct {
11691169
/// Asserts that the path parameter has no null bytes.
11701170
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
11711171
if (builtin.os.tag == .windows) {
1172-
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1173-
return self.openDirW(sub_path_w.span().ptr, args);
1172+
const nt_path = try os.windows.NtPath.initA(sub_path);
1173+
defer nt_path.deinit();
1174+
return self.openDirWindows(std.fs.path.isAbsoluteWindows(sub_path), nt_path.str, args);
11741175
} else if (builtin.os.tag == .wasi) {
11751176
return self.openDirWasi(sub_path, args);
11761177
} else {
@@ -1224,8 +1225,7 @@ pub const Dir = struct {
12241225
/// Same as `openDir` except the parameter is null-terminated.
12251226
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
12261227
if (builtin.os.tag == .windows) {
1227-
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
1228-
return self.openDirW(sub_path_w.span().ptr, args);
1228+
return self.openDir(std.mem.spanZ(sub_path_c), args);
12291229
}
12301230
const symlink_flags: u32 = if (args.no_follow) os.O_NOFOLLOW else 0x0;
12311231
if (!args.iterate) {
@@ -1236,15 +1236,13 @@ pub const Dir = struct {
12361236
}
12371237
}
12381238

1239-
/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1239+
/// Same as `openDir` except the path parameter is WTF-16 encoded.
12401240
/// This function asserts the target OS is Windows.
12411241
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
12421242
const w = os.windows;
1243-
// TODO remove some of these flags if args.access_sub_paths is false
1244-
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1245-
w.SYNCHRONIZE | w.FILE_TRAVERSE;
1246-
const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1247-
return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
1243+
const nt_path = try w.NtPath.init(sub_path_w);
1244+
defer nt_path.deinit();
1245+
return self.openDirWindows(std.fs.path.isAbsoluteWindowsW(sub_path_w), nt_path.str, args);
12481246
}
12491247

12501248
/// `flags` must contain `os.O_DIRECTORY`.
@@ -1265,37 +1263,28 @@ pub const Dir = struct {
12651263
return Dir{ .fd = fd };
12661264
}
12671265

1268-
fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir {
1266+
fn openDirWindows(self: Dir, is_absolute: bool, nt_path: os.windows.UNICODE_STRING, args: OpenDirOptions) OpenError!Dir {
12691267
const w = os.windows;
12701268

1269+
// TODO remove some of these flags if args.access_sub_paths is false
1270+
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1271+
w.SYNCHRONIZE | w.FILE_TRAVERSE;
1272+
const access_mask: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1273+
12711274
var result = Dir{
12721275
.fd = undefined,
12731276
};
12741277

1275-
const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
1276-
var nt_name = w.UNICODE_STRING{
1277-
.Length = path_len_bytes,
1278-
.MaximumLength = path_len_bytes,
1279-
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
1280-
};
1278+
var adjusted_nt_path = if (is_absolute) nt_path else try w.toRelativeNtPath(nt_path);
12811279
var attr = w.OBJECT_ATTRIBUTES{
12821280
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1283-
.RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1281+
.RootDirectory = if (is_absolute) null else self.fd,
12841282
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1285-
.ObjectName = &nt_name,
1283+
.ObjectName = &adjusted_nt_path,
12861284
.SecurityDescriptor = null,
12871285
.SecurityQualityOfService = null,
12881286
};
1289-
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
1290-
// Windows does not recognize this, but it does work with empty string.
1291-
nt_name.Length = 0;
1292-
}
1293-
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
1294-
// If you're looking to contribute to zig and fix this, see here for an example of how to
1295-
// implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
1296-
@panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
1297-
}
1298-
const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1287+
const open_reparse_point: w.DWORD = if (args.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
12991288
var io: w.IO_STATUS_BLOCK = undefined;
13001289
const rc = w.ntdll.NtCreateFile(
13011290
&result.fd,

lib/std/fs/test.zig

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,17 @@ test "openDirAbsolute" {
7979
break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
8080
};
8181

82-
var dir = try fs.openDirAbsolute(base_path, .{});
83-
defer dir.close();
82+
{
83+
var dir = try fs.openDirAbsolute(base_path, .{});
84+
defer dir.close();
85+
}
86+
87+
for ([_][]const u8 { ".", ".." }) |sub_path| {
88+
const dir_path = try fs.path.join(&arena.allocator, &[_][]const u8 { base_path, sub_path });
89+
defer arena.allocator.free(dir_path);
90+
var dir = try fs.openDirAbsolute(dir_path, .{});
91+
defer dir.close();
92+
}
8493
}
8594

8695
test "readLinkAbsolute" {

lib/std/os/windows.zig

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,54 @@ pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
15691569
return path_space;
15701570
}
15711571

1572+
/// Convert `dos_path` to an NT path that can be passed to Nt* functions like NtCreateFile.
1573+
pub const NtPath = struct {
1574+
str: UNICODE_STRING,
1575+
pub fn initA(dos_path: []const u8) !NtPath {
1576+
var path_space : PathSpace = undefined;
1577+
path_space.len = try std.unicode.utf8ToUtf16Le(&path_space.data, dos_path);
1578+
path_space.data[path_space.len] = 0;
1579+
return init(path_space.span());
1580+
}
1581+
pub fn init(dos_path: [*:0]const u16) !NtPath {
1582+
var str : UNICODE_STRING = undefined;
1583+
const status = ntdll.RtlDosPathNameToNtPathName_U_WithStatus(dos_path, &str, null, null);
1584+
if (status != .SUCCESS) return unexpectedStatus(status);
1585+
return NtPath { .str = str };
1586+
}
1587+
// TODO: not sure if the path is guaranteed to be NULL-terminted
1588+
pub fn span(self: NtPath) []const u16 {
1589+
return self.str.Buffer[0 .. self.str.Length / 2];
1590+
}
1591+
pub fn deinit(self: NtPath) void {
1592+
if (TRUE != kernel32.HeapFree(kernel32.GetProcessHeap().?, 0, self.str.Buffer)) {
1593+
std.debug.panic("in NtPath, HeapFree failed with {}\n", .{kernel32.GetLastError()});
1594+
}
1595+
}
1596+
};
1597+
1598+
/// return the relative path of `nt_path` based on CWD
1599+
pub fn toRelativeNtPath(nt_path: UNICODE_STRING) !UNICODE_STRING {
1600+
const cwd_nt_path = try NtPath.init(&[_:0]u16 {'.'});
1601+
defer cwd_nt_path.deinit();
1602+
const cwd_span = cwd_nt_path.span();
1603+
std.debug.assert(mem.startsWith(u16, unicodeSpan(nt_path), cwd_span));
1604+
std.debug.assert(nt_path.Buffer[cwd_span.len] == '\\');
1605+
return unicodeSubstring(nt_path, @intCast(c_ushort, cwd_span.len + 1));
1606+
}
1607+
1608+
pub fn unicodeSpan(str: UNICODE_STRING) []u16 {
1609+
return str.Buffer[0..str.Length/2];
1610+
}
1611+
pub fn unicodeSubstring(str: UNICODE_STRING, char_offset: c_ushort) UNICODE_STRING {
1612+
std.debug.assert(char_offset * 2 <= str.Length);
1613+
return .{
1614+
.Buffer = str.Buffer + char_offset,
1615+
.MaximumLength = str.MaximumLength - (char_offset*2),
1616+
.Length = str.Length - (char_offset*2),
1617+
};
1618+
}
1619+
15721620
/// Assumes an absolute path.
15731621
pub fn wToPrefixedFileW(s: []const u16) !PathSpace {
15741622
// TODO https://github.com/ziglang/zig/issues/2765
@@ -1637,3 +1685,9 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
16371685
}
16381686
return error.Unexpected;
16391687
}
1688+
1689+
test "" {
1690+
if (builtin.os.tag == .windows) {
1691+
_ = @import("windows/test.zig");
1692+
}
1693+
}

lib/std/os/windows/ntdll.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,10 @@ pub extern "NtDll" fn NtWaitForKeyedEvent(
113113
) callconv(WINAPI) NTSTATUS;
114114

115115
pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS;
116+
117+
pub extern "NtDll" fn RtlDosPathNameToNtPathName_U_WithStatus(
118+
DosFileName: [*:0]const WCHAR,
119+
NtFileName: *UNICODE_STRING,
120+
FilePath: ?*[*:0]WCHAR,
121+
cd: ?*CURDIR,
122+
) callconv(WINAPI) NTSTATUS;

lib/std/os/windows/test.zig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2015-2020 Zig Contributors
3+
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4+
// The MIT license requires this copyright notice to be included in all copies
5+
// and substantial portions of the software.
6+
const std = @import("../../std.zig");
7+
const builtin = @import("builtin");
8+
const windows = std.os.windows;
9+
const mem = std.mem;
10+
const testing = std.testing;
11+
const expect = testing.expect;
12+
13+
fn testNtPath(input: []const u8, expected: []const u8) !void {
14+
const input_w = try std.unicode.utf8ToUtf16LeWithNull(testing.allocator, input);
15+
defer testing.allocator.free(input_w);
16+
const expected_w = try std.unicode.utf8ToUtf16LeWithNull(testing.allocator, expected);
17+
defer testing.allocator.free(expected_w);
18+
19+
const nt_path = try windows.NtPath.init(input_w);
20+
defer nt_path.deinit();
21+
const relative_path = try windows.toRelativeNtPath(nt_path.str);
22+
expect(mem.eql(u16, windows.unicodeSpan(relative_path), expected_w));
23+
}
24+
25+
test "NtPath" {
26+
try testNtPath("a", "a");
27+
try testNtPath("a\\b", "a\\b");
28+
try testNtPath("a\\.\\b", "a\\b");
29+
try testNtPath("a\\..\\b", "b");
30+
}

0 commit comments

Comments
 (0)