| author | |
| committer | |
| log | 4c97919e8e032a91e45d44ee438c9f00d16f52a4 |
| tree | afae875b1863023f0bbf785393ade03ef7547abb |
| parent | ddf5859c22527c6bf5d8bb13310db996fcc58874 |
| parent | 47343f9bcfb820b24d7e249c5cf52b80c3f9df83 |
| signature |
`fs.selfExePath`: Make the Windows implementation follow symlinks5 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 immediately | 2960 | // 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 | } |
| 2979 | 2983 | ||
| 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 executable | 2987 | /// 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 | } |
| 3072 | 3080 | ||
| 3073 | /// The result is UTF16LE-encoded. | 3081 | pub const selfExePathW = @compileError("deprecated; use selfExePath instead"); |
| 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 | } | ||
| 3078 | 3082 | ||
| 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 @@ | ||
| 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 | } | ||
test/standalone/self_exe_symlink/create-symlink.zig created+15| ... | @@ -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 | } | ||
test/standalone/self_exe_symlink/main.zig created+17| ... | @@ -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 | } | ||