authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-08-19 21:24:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-08-19 21:24:36-07:00
log4c97919e8e032a91e45d44ee438c9f00d16f52a4
treeafae875b1863023f0bbf785393ade03ef7547abb
parentddf5859c22527c6bf5d8bb13310db996fcc58874
parent47343f9bcfb820b24d7e249c5cf52b80c3f9df83
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16885 from squeek502/windows-selfExePath-symlink

`fs.selfExePath`: Make the Windows implementation follow symlinks

5 files changed, 95 insertions(+), 12 deletions(-)

lib/std/fs.zig+16-12
...@@ -2949,8 +2949,12 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {...@@ -2949,8 +2949,12 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
2949 return openFileAbsoluteZ("/proc/self/exe", flags);2949 return openFileAbsoluteZ("/proc/self/exe", flags);
2950 }2950 }
2951 if (builtin.os.tag == .windows) {2951 if (builtin.os.tag == .windows) {
2952 const wide_slice = selfExePathW();2952 // If ImagePathName is a symlink, then it will contain the path of the symlink,
2953 const prefixed_path_w = try os.windows.wToPrefixedFileW(null, wide_slice);2953 // not the path that the symlink points to. However, because we are opening
2954 // the file, we can let the openFileW call follow the symlink for us.
2955 const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
2956 const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
2957 const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name);
2954 return cwd().openFileW(prefixed_path_w.span(), flags);2958 return cwd().openFileW(prefixed_path_w.span(), flags);
2955 }2959 }
2956 // Use of MAX_PATH_BYTES here is valid as the resulting path is immediately2960 // Use of MAX_PATH_BYTES here is valid as the resulting path is immediately
...@@ -2977,7 +2981,7 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {...@@ -2977,7 +2981,7 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
2977 return allocator.dupe(u8, try selfExePath(&buf));2981 return allocator.dupe(u8, try selfExePath(&buf));
2978}2982}
29792983
2980/// Get the path to the current executable.2984/// Get the path to the current executable. Follows symlinks.
2981/// If you only need the directory, use selfExeDirPath.2985/// If you only need the directory, use selfExeDirPath.
2982/// If you only want an open file handle, use openSelfExe.2986/// If you only want an open file handle, use openSelfExe.
2983/// This function may return an error if the current executable2987/// This function may return an error if the current executable
...@@ -3060,21 +3064,21 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -3060,21 +3064,21 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
3060 return error.FileNotFound;3064 return error.FileNotFound;
3061 },3065 },
3062 .windows => {3066 .windows => {
3063 const utf16le_slice = selfExePathW();3067 const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
3064 // Trust that Windows gives us valid UTF-16LE.3068 const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
3065 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;3069
3066 return out_buffer[0..end_index];3070 // If ImagePathName is a symlink, then it will contain the path of the
3071 // symlink, not the path that the symlink points to. We want the path
3072 // that the symlink points to, though, so we need to get the realpath.
3073 const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);
3074 return std.fs.cwd().realpathW(pathname_w.span(), out_buffer);
3067 },3075 },
3068 .wasi => @compileError("std.fs.selfExePath not supported for WASI. Use std.fs.selfExePathAlloc instead."),3076 .wasi => @compileError("std.fs.selfExePath not supported for WASI. Use std.fs.selfExePathAlloc instead."),
3069 else => @compileError("std.fs.selfExePath not supported for this target"),3077 else => @compileError("std.fs.selfExePath not supported for this target"),
3070 }3078 }
3071}3079}
30723080
3073/// The result is UTF16LE-encoded.3081pub const selfExePathW = @compileError("deprecated; use selfExePath instead");
3074pub fn selfExePathW() [:0]const u16 {
3075 const image_path_name = &os.windows.peb().ProcessParameters.ImagePathName;
3076 return image_path_name.Buffer[0 .. image_path_name.Length / 2 :0];
3077}
30783082
3079/// `selfExeDirPath` except allocates the result on the heap.3083/// `selfExeDirPath` except allocates the result on the heap.
3080/// Caller owns returned memory.3084/// Caller owns returned memory.
test/standalone.zig+4
...@@ -198,6 +198,10 @@ pub const build_cases = [_]BuildCase{...@@ -198,6 +198,10 @@ pub const build_cases = [_]BuildCase{
198 .build_root = "test/standalone/windows_spawn",198 .build_root = "test/standalone/windows_spawn",
199 .import = @import("standalone/windows_spawn/build.zig"),199 .import = @import("standalone/windows_spawn/build.zig"),
200 },200 },
201 .{
202 .build_root = "test/standalone/self_exe_symlink",
203 .import = @import("standalone/self_exe_symlink/build.zig"),
204 },
201 .{205 .{
202 .build_root = "test/standalone/c_compiler",206 .build_root = "test/standalone/c_compiler",
203 .import = @import("standalone/c_compiler/build.zig"),207 .import = @import("standalone/c_compiler/build.zig"),
test/standalone/self_exe_symlink/build.zig created+43
...@@ -0,0 +1,43 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target: std.zig.CrossTarget = .{};
11
12 // The test requires getFdPath in order to to get the path of the
13 // File returned by openSelfExe
14 if (!std.os.isGetFdPathSupportedOnTarget(target.getOs())) return;
15
16 const main = b.addExecutable(.{
17 .name = "main",
18 .root_source_file = .{ .path = "main.zig" },
19 .optimize = optimize,
20 .target = target,
21 });
22
23 const create_symlink_exe = b.addExecutable(.{
24 .name = "create-symlink",
25 .root_source_file = .{ .path = "create-symlink.zig" },
26 .optimize = optimize,
27 .target = target,
28 });
29
30 var run_create_symlink = b.addRunArtifact(create_symlink_exe);
31 run_create_symlink.addArtifactArg(main);
32 const symlink_path = run_create_symlink.addOutputFileArg("main-symlink");
33 run_create_symlink.expectExitCode(0);
34 run_create_symlink.skip_foreign_checks = true;
35
36 var run_from_symlink = std.Build.Step.Run.create(b, "run symlink");
37 run_from_symlink.addFileArg(symlink_path);
38 run_from_symlink.expectExitCode(0);
39 run_from_symlink.skip_foreign_checks = true;
40 run_from_symlink.step.dependOn(&run_create_symlink.step);
41
42 test_step.dependOn(&run_from_symlink.step);
43}
test/standalone/self_exe_symlink/create-symlink.zig created+15
...@@ -0,0 +1,15 @@
1const std = @import("std");
2
3pub fn main() anyerror!void {
4 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
5 defer if (gpa.deinit() == .leak) @panic("found memory leaks");
6 const allocator = gpa.allocator();
7
8 var it = try std.process.argsWithAllocator(allocator);
9 defer it.deinit();
10 _ = it.next() orelse unreachable; // skip binary name
11 const exe_path = it.next() orelse unreachable;
12 const symlink_path = it.next() orelse unreachable;
13
14 try std.fs.cwd().symLink(exe_path, symlink_path, .{});
15}
test/standalone/self_exe_symlink/main.zig created+17
...@@ -0,0 +1,17 @@
1const std = @import("std");
2
3pub fn main() !void {
4 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
5 defer std.debug.assert(gpa.deinit() == .ok);
6 const allocator = gpa.allocator();
7
8 const self_path = try std.fs.selfExePathAlloc(allocator);
9 defer allocator.free(self_path);
10
11 var self_exe = try std.fs.openSelfExe(.{});
12 defer self_exe.close();
13 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
14 const self_exe_path = try std.os.getFdPath(self_exe.handle, &buf);
15
16 try std.testing.expectEqualStrings(self_exe_path, self_path);
17}