From 2c2ecd624ad6d85d005f4728f834c0e3377f8f99 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 19 Aug 2023 12:48:51 -0700 Subject: [PATCH 1/2] fs.selfExePath: Make the Windows implementation follow symlinks Before, selfExePath would be the path of the symlink on Windows instead of the path of what the symlink points to. This deprecates fs.selfExePathW since it does not provide a separate use-case over selfExePath (before, the W version could return [:0]u16 directly) Fixes #16670 --- lib/std/fs.zig | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index f01bcb4f22..4a0f37b855 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2949,8 +2949,12 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { return openFileAbsoluteZ("/proc/self/exe", flags); } if (builtin.os.tag == .windows) { - const wide_slice = selfExePathW(); - const prefixed_path_w = try os.windows.wToPrefixedFileW(null, wide_slice); + // If ImagePathName is a symlink, then it will contain the path of the symlink, + // not the path that the symlink points to. However, because we are opening + // the file, we can let the openFileW call follow the symlink for us. + const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName; + const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0]; + const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name); return cwd().openFileW(prefixed_path_w.span(), flags); } // Use of MAX_PATH_BYTES here is valid as the resulting path is immediately @@ -2977,7 +2981,7 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 { return allocator.dupe(u8, try selfExePath(&buf)); } -/// Get the path to the current executable. +/// Get the path to the current executable. Follows symlinks. /// If you only need the directory, use selfExeDirPath. /// If you only want an open file handle, use openSelfExe. /// This function may return an error if the current executable @@ -3060,21 +3064,21 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { return error.FileNotFound; }, .windows => { - const utf16le_slice = selfExePathW(); - // Trust that Windows gives us valid UTF-16LE. - const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; - return out_buffer[0..end_index]; + const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName; + const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0]; + + // 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 os.windows.wToPrefixedFileW(null, image_path_name); + return std.fs.cwd().realpathW(pathname_w.span(), out_buffer); }, .wasi => @compileError("std.fs.selfExePath not supported for WASI. Use std.fs.selfExePathAlloc instead."), else => @compileError("std.fs.selfExePath not supported for this target"), } } -/// The result is UTF16LE-encoded. -pub fn selfExePathW() [:0]const u16 { - const image_path_name = &os.windows.peb().ProcessParameters.ImagePathName; - return image_path_name.Buffer[0 .. image_path_name.Length / 2 :0]; -} +pub const selfExePathW = @compileError("deprecated; use selfExePath instead"); /// `selfExeDirPath` except allocates the result on the heap. /// Caller owns returned memory. From 47343f9bcfb820b24d7e249c5cf52b80c3f9df83 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 19 Aug 2023 17:13:07 -0700 Subject: [PATCH 2/2] Add self_exe_symlink standalone test Tests that fs.selfExePath follows symlinks by comparing it to the path of the File returned from fs.openSelfExe --- test/standalone.zig | 4 ++ test/standalone/self_exe_symlink/build.zig | 43 +++++++++++++++++++ .../self_exe_symlink/create-symlink.zig | 15 +++++++ test/standalone/self_exe_symlink/main.zig | 17 ++++++++ 4 files changed, 79 insertions(+) create mode 100644 test/standalone/self_exe_symlink/build.zig create mode 100644 test/standalone/self_exe_symlink/create-symlink.zig create mode 100644 test/standalone/self_exe_symlink/main.zig diff --git a/test/standalone.zig b/test/standalone.zig index 3725456fa1..4f27106e6d 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -198,6 +198,10 @@ pub const build_cases = [_]BuildCase{ .build_root = "test/standalone/windows_spawn", .import = @import("standalone/windows_spawn/build.zig"), }, + .{ + .build_root = "test/standalone/self_exe_symlink", + .import = @import("standalone/self_exe_symlink/build.zig"), + }, .{ .build_root = "test/standalone/c_compiler", .import = @import("standalone/c_compiler/build.zig"), diff --git a/test/standalone/self_exe_symlink/build.zig b/test/standalone/self_exe_symlink/build.zig new file mode 100644 index 0000000000..10e0a6a512 --- /dev/null +++ b/test/standalone/self_exe_symlink/build.zig @@ -0,0 +1,43 @@ +const std = @import("std"); + +pub const requires_symlinks = true; + +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: std.zig.CrossTarget = .{}; + + // The test requires getFdPath in order to to get the path of the + // File returned by openSelfExe + if (!std.os.isGetFdPathSupportedOnTarget(target.getOs())) return; + + const main = b.addExecutable(.{ + .name = "main", + .root_source_file = .{ .path = "main.zig" }, + .optimize = optimize, + .target = target, + }); + + const create_symlink_exe = b.addExecutable(.{ + .name = "create-symlink", + .root_source_file = .{ .path = "create-symlink.zig" }, + .optimize = optimize, + .target = target, + }); + + var run_create_symlink = b.addRunArtifact(create_symlink_exe); + run_create_symlink.addArtifactArg(main); + const symlink_path = run_create_symlink.addOutputFileArg("main-symlink"); + run_create_symlink.expectExitCode(0); + run_create_symlink.skip_foreign_checks = true; + + var run_from_symlink = std.Build.Step.Run.create(b, "run symlink"); + run_from_symlink.addFileArg(symlink_path); + run_from_symlink.expectExitCode(0); + run_from_symlink.skip_foreign_checks = true; + run_from_symlink.step.dependOn(&run_create_symlink.step); + + test_step.dependOn(&run_from_symlink.step); +} diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig new file mode 100644 index 0000000000..e558df04d6 --- /dev/null +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +pub fn main() anyerror!void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer if (gpa.deinit() == .leak) @panic("found memory leaks"); + const allocator = gpa.allocator(); + + var it = try std.process.argsWithAllocator(allocator); + defer it.deinit(); + _ = it.next() orelse unreachable; // skip binary name + const exe_path = it.next() orelse unreachable; + const symlink_path = it.next() orelse unreachable; + + try std.fs.cwd().symLink(exe_path, symlink_path, .{}); +} diff --git a/test/standalone/self_exe_symlink/main.zig b/test/standalone/self_exe_symlink/main.zig new file mode 100644 index 0000000000..d84860a8f8 --- /dev/null +++ b/test/standalone/self_exe_symlink/main.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer std.debug.assert(gpa.deinit() == .ok); + const allocator = gpa.allocator(); + + const self_path = try std.fs.selfExePathAlloc(allocator); + defer allocator.free(self_path); + + var self_exe = try std.fs.openSelfExe(.{}); + defer self_exe.close(); + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const self_exe_path = try std.os.getFdPath(self_exe.handle, &buf); + + try std.testing.expectEqualStrings(self_exe_path, self_path); +}