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. diff --git a/test/standalone.zig b/test/standalone.zig index 6618986292..22b9dfba49 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); +}