From f2227ae6776112f952f7db1c0a0eb2a0a27cf576 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Sat, 26 Apr 2025 09:05:26 -0400 Subject: [PATCH 1/5] Return WTF16LE encoded path from realpathW --- lib/std/fs.zig | 11 ++++++++++- lib/std/fs/Dir.zig | 34 +++++++++++++++++++++++----------- lib/std/posix.zig | 41 +++++++++++++++-------------------------- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 03fd679495..136927cac2 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -643,10 +643,19 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. const pathname_w = try windows.wToPrefixedFileW(null, image_path_name); - return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) { + + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = std.fs.cwd().realpathW(pathname_w.span(), &wide_buf) catch |err| switch (err) { error.InvalidWtf8 => unreachable, else => |e| return e, }; + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; }, else => @compileError("std.fs.selfExePath not supported for this target"), } diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index f5df4a969f..c3f5ea922a 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1370,7 +1370,16 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError } if (native_os == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); - return self.realpathW(pathname_w.span(), out_buffer); + + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try self.realpathW(pathname_w.span(), &wide_buf); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } const pathname_c = try posix.toPosixPath(pathname); return self.realpathZ(&pathname_c, out_buffer); @@ -1381,7 +1390,16 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 { if (native_os == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); - return self.realpathW(pathname_w.span(), out_buffer); + + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try self.realpathW(pathname_w.span(), &wide_buf); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } var flags: posix.O = .{}; @@ -1411,9 +1429,9 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE } /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as WTF16 LE. /// See also `Dir.realpath`, `realpathW`. -pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 { +pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 { const w = windows; const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; @@ -1434,13 +1452,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr }; defer w.CloseHandle(h_file); - var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); - const len = std.unicode.calcWtf8Len(wide_slice); - if (len > out_buffer.len) - return error.NameTooLong; - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; + return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); } pub const RealPathAllocError = RealPathError || Allocator.Error; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 3e4c7f05ed..476383fc8e 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -5676,7 +5676,12 @@ pub const RealPathError = error{ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { if (native_os == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(null, pathname); - return realpathW(pathname_w.span(), out_buffer); + + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try realpathW(pathname_w.span(), &wide_buf); + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } else if (native_os == .wasi and !builtin.link_libc) { @compileError("WASI does not support os.realpath"); } @@ -5690,7 +5695,12 @@ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathE pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { if (native_os == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); - return realpathW(pathname_w.span(), out_buffer); + + var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try realpathW(pathname_w.span(), &wide_buf); + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } else if (native_os == .wasi and !builtin.link_libc) { return realpath(mem.sliceTo(pathname, 0), out_buffer); } @@ -5736,32 +5746,11 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP /// Same as `realpath` except `pathname` is WTF16LE-encoded. /// -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as WTF16LE. /// /// Calling this function is usually a bug. -pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { - const w = windows; - - const dir = fs.cwd().fd; - const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; - const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; - const creation = w.FILE_OPEN; - const h_file = blk: { - const res = w.OpenFile(pathname, .{ - .dir = dir, - .access_mask = access_mask, - .share_access = share_access, - .creation = creation, - .filter = .any, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, - else => |e| return e, - }; - break :blk res; - }; - defer w.CloseHandle(h_file); - - return std.os.getFdPath(h_file, out_buffer); +pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u16) RealPathError![]u16 { + return fs.cwd().realpathW(pathname, out_buffer); } /// Spurious wakeups are possible and no precision of timing is guaranteed. From 2886a8bf45d3e340282c7f0dc8b35455e935f0b2 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Wed, 23 Apr 2025 10:32:21 -0400 Subject: [PATCH 2/5] Improve documentation on Dir.realpathW --- lib/std/fs/Dir.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index c3f5ea922a..2948434917 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1428,8 +1428,13 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE return result; } -/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded. -/// The result is encoded as WTF16 LE. +/// Windows-only. Same as `Dir.realpath` except +/// * `pathname` and the result are WTF-16 LE encoded +/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details. +/// +/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it +/// is safe to reuse a single buffer for both. +/// /// See also `Dir.realpath`, `realpathW`. pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 { const w = windows; From a7dfc6470f0cb73c53f11590cf89d6348a81030e Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Wed, 23 Apr 2025 10:38:35 -0400 Subject: [PATCH 3/5] Reuse pathname_w buffer as out_buffer when calling realpathW --- lib/std/fs.zig | 5 ++--- lib/std/fs/Dir.zig | 10 ++++------ lib/std/posix.zig | 10 ++++------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 136927cac2..e8baffc3bf 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -642,10 +642,9 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. - const pathname_w = try windows.wToPrefixedFileW(null, image_path_name); + var pathname_w = try windows.wToPrefixedFileW(null, image_path_name); - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = std.fs.cwd().realpathW(pathname_w.span(), &wide_buf) catch |err| switch (err) { + const wide_slice = std.fs.cwd().realpathW(pathname_w.span(), &pathname_w.data) catch |err| switch (err) { error.InvalidWtf8 => unreachable, else => |e| return e, }; diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 2948434917..394ac10fd7 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1369,10 +1369,9 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError @compileError("realpath is not available on WASI"); } if (native_os == .windows) { - const pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); + var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try self.realpathW(pathname_w.span(), &wide_buf); + const wide_slice = try self.realpathW(pathname_w.span(), &pathname_w.data); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -1389,10 +1388,9 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError /// See also `Dir.realpath`, `realpathZ`. pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 { if (native_os == .windows) { - const pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); + var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try self.realpathW(pathname_w.span(), &wide_buf); + const wide_slice = try self.realpathW(pathname_w.span(), &pathname_w.data); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 476383fc8e..f2f1b4ef52 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -5675,10 +5675,9 @@ pub const RealPathError = error{ /// Calling this function is usually a bug. pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { if (native_os == .windows) { - const pathname_w = try windows.sliceToPrefixedFileW(null, pathname); + var pathname_w = try windows.sliceToPrefixedFileW(null, pathname); - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try realpathW(pathname_w.span(), &wide_buf); + const wide_slice = try realpathW(pathname_w.span(), &pathname_w.data); const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return out_buffer[0..end_index]; @@ -5694,10 +5693,9 @@ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathE /// Calling this function is usually a bug. pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { if (native_os == .windows) { - const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); + var pathname_w = try windows.cStrToPrefixedFileW(null, pathname); - var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try realpathW(pathname_w.span(), &wide_buf); + const wide_slice = try realpathW(pathname_w.span(), &pathname_w.data); const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return out_buffer[0..end_index]; From eb46bbba34c1ccb0fbbbd755fd190e1b6eb939ec Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Fri, 25 Apr 2025 22:34:05 -0400 Subject: [PATCH 4/5] Fix realpathW out_buffer size --- lib/std/posix.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f2f1b4ef52..97807faa71 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -5747,7 +5747,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP /// The result is encoded as WTF16LE. /// /// Calling this function is usually a bug. -pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u16) RealPathError![]u16 { +pub fn realpathW(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 { return fs.cwd().realpathW(pathname, out_buffer); } From 7bf740ee718f4b6109cd9fe7014d1784d48ada48 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Fri, 25 Apr 2025 22:55:18 -0400 Subject: [PATCH 5/5] Deprecate old realpathW correctly - Rename modified `realpathW` to `realpathW2` - Re-add original `realpathW` - Add deprecation notice to `realpathW` --- lib/std/fs.zig | 3 ++- lib/std/fs/Dir.zig | 25 ++++++++++++++++++++++--- lib/std/posix.zig | 19 +++++++++++++++---- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index e8baffc3bf..39f26effc7 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -31,6 +31,7 @@ pub const wasi = @import("fs/wasi.zig"); pub const realpath = posix.realpath; pub const realpathZ = posix.realpathZ; pub const realpathW = posix.realpathW; +pub const realpathW2 = posix.realpathW2; pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; @@ -644,7 +645,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { // that the symlink points to, though, so we need to get the realpath. var pathname_w = try windows.wToPrefixedFileW(null, image_path_name); - const wide_slice = std.fs.cwd().realpathW(pathname_w.span(), &pathname_w.data) catch |err| switch (err) { + const wide_slice = std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data) catch |err| switch (err) { error.InvalidWtf8 => unreachable, else => |e| return e, }; diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 394ac10fd7..7c5be3003e 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1371,7 +1371,7 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError if (native_os == .windows) { var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); - const wide_slice = try self.realpathW(pathname_w.span(), &pathname_w.data); + const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -1390,7 +1390,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE if (native_os == .windows) { var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); - const wide_slice = try self.realpathW(pathname_w.span(), &pathname_w.data); + const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -1426,6 +1426,25 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE return result; } +/// Deprecated: use `realpathW2`. +/// +/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded. +/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// See also `Dir.realpath`, `realpathW`. +pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 { + var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined; + + const wide_slice = try self.realpathW2(pathname, &wide_buf); + + var big_out_buf: [fs.max_path_bytes]u8 = undefined; + const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice); + if (end_index > out_buffer.len) + return error.NameTooLong; + const result = out_buffer[0..end_index]; + @memcpy(result, big_out_buf[0..end_index]); + return result; +} + /// Windows-only. Same as `Dir.realpath` except /// * `pathname` and the result are WTF-16 LE encoded /// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details. @@ -1434,7 +1453,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE /// is safe to reuse a single buffer for both. /// /// See also `Dir.realpath`, `realpathW`. -pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 { +pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 { const w = windows; const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 97807faa71..9ba8e010e8 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -5677,7 +5677,7 @@ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathE if (native_os == .windows) { var pathname_w = try windows.sliceToPrefixedFileW(null, pathname); - const wide_slice = try realpathW(pathname_w.span(), &pathname_w.data); + const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return out_buffer[0..end_index]; @@ -5695,7 +5695,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP if (native_os == .windows) { var pathname_w = try windows.cStrToPrefixedFileW(null, pathname); - const wide_slice = try realpathW(pathname_w.span(), &pathname_w.data); + const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); return out_buffer[0..end_index]; @@ -5742,13 +5742,24 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP return mem.sliceTo(result_path, 0); } +/// Deprecated: use `realpathW2`. +/// +/// Same as `realpath` except `pathname` is WTF16LE-encoded. +/// +/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// +/// Calling this function is usually a bug. +pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { + return fs.cwd().realpathW(pathname, out_buffer); +} + /// Same as `realpath` except `pathname` is WTF16LE-encoded. /// /// The result is encoded as WTF16LE. /// /// Calling this function is usually a bug. -pub fn realpathW(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 { - return fs.cwd().realpathW(pathname, out_buffer); +pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 { + return fs.cwd().realpathW2(pathname, out_buffer); } /// Spurious wakeups are possible and no precision of timing is guaranteed.