Skip to content

Commit 4c97919

Browse files
authored
Merge pull request #16885 from squeek502/windows-selfExePath-symlink
`fs.selfExePath`: Make the Windows implementation follow symlinks
2 parents ddf5859 + 47343f9 commit 4c97919

File tree

5 files changed

+95
-12
lines changed

5 files changed

+95
-12
lines changed

lib/std/fs.zig

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,8 +2949,12 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
29492949
return openFileAbsoluteZ("/proc/self/exe", flags);
29502950
}
29512951
if (builtin.os.tag == .windows) {
2952-
const wide_slice = selfExePathW();
2953-
const prefixed_path_w = try os.windows.wToPrefixedFileW(null, wide_slice);
2952+
// If ImagePathName is a symlink, then it will contain the path of the symlink,
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);
29542958
return cwd().openFileW(prefixed_path_w.span(), flags);
29552959
}
29562960
// Use of MAX_PATH_BYTES here is valid as the resulting path is immediately
@@ -2977,7 +2981,7 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
29772981
return allocator.dupe(u8, try selfExePath(&buf));
29782982
}
29792983

2980-
/// Get the path to the current executable.
2984+
/// Get the path to the current executable. Follows symlinks.
29812985
/// If you only need the directory, use selfExeDirPath.
29822986
/// If you only want an open file handle, use openSelfExe.
29832987
/// This function may return an error if the current executable
@@ -3060,21 +3064,21 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
30603064
return error.FileNotFound;
30613065
},
30623066
.windows => {
3063-
const utf16le_slice = selfExePathW();
3064-
// Trust that Windows gives us valid UTF-16LE.
3065-
const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
3066-
return out_buffer[0..end_index];
3067+
const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
3068+
const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
3069+
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);
30673075
},
30683076
.wasi => @compileError("std.fs.selfExePath not supported for WASI. Use std.fs.selfExePathAlloc instead."),
30693077
else => @compileError("std.fs.selfExePath not supported for this target"),
30703078
}
30713079
}
30723080

3073-
/// The result is UTF16LE-encoded.
3074-
pub 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-
}
3081+
pub const selfExePathW = @compileError("deprecated; use selfExePath instead");
30783082

30793083
/// `selfExeDirPath` except allocates the result on the heap.
30803084
/// Caller owns returned memory.

test/standalone.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ pub const build_cases = [_]BuildCase{
198198
.build_root = "test/standalone/windows_spawn",
199199
.import = @import("standalone/windows_spawn/build.zig"),
200200
},
201+
.{
202+
.build_root = "test/standalone/self_exe_symlink",
203+
.import = @import("standalone/self_exe_symlink/build.zig"),
204+
},
201205
.{
202206
.build_root = "test/standalone/c_compiler",
203207
.import = @import("standalone/c_compiler/build.zig"),
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const std = @import("std");
2+
3+
pub const requires_symlinks = true;
4+
5+
pub 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+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const std = @import("std");
2+
3+
pub 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+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const std = @import("std");
2+
3+
pub 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+
}

0 commit comments

Comments
 (0)