| 1 | const std = @import("std"); |
| 2 | const Io = std.Io; |
| 3 | |
| 4 | pub fn main(init: std.process.Init) !void { |
| 5 | const arena = init.arena.allocator(); |
| 6 | const args = try init.minimal.args.toSlice(arena); |
| 7 | const io = init.io; |
| 8 | |
| 9 | if (args.len < 2) return error.MissingArgs; |
| 10 | |
| 11 | const exe_path = args[1]; |
| 12 | |
| 13 | const cwd_path = try std.process.currentPathAlloc(io, arena); |
| 14 | const parsed_cwd_path = std.fs.path.parsePathWindows(u8, cwd_path); |
| 15 | |
| 16 | if (parsed_cwd_path.kind == .drive_absolute and !std.ascii.isAlphabetic(cwd_path[0])) { |
| 17 | // Technically possible, but not worth supporting here |
| 18 | return error.NonAlphabeticDriveLetter; |
| 19 | } |
| 20 | |
| 21 | const alt_drive_letter = try getAltDriveLetter(cwd_path); |
| 22 | const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter}); |
| 23 | const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter}); |
| 24 | var alt_drive_env_map = std.process.Environ.Map.init(arena); |
| 25 | try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd); |
| 26 | |
| 27 | const empty_env = std.process.Environ.Map.init(arena); |
| 28 | |
| 29 | { |
| 30 | const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter}); |
| 31 | const drive_abs = try std.fmt.allocPrint(arena, "{c}:\\bar", .{alt_drive_letter}); |
| 32 | |
| 33 | // With the special =X: environment variable set, drive-relative paths that |
| 34 | // don't match the CWD's drive letter are resolved against that env var. |
| 35 | try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, &alt_drive_env_map); |
| 36 | try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, &alt_drive_env_map); |
| 37 | |
| 38 | // Without that environment variable set, drive-relative paths that don't match the |
| 39 | // CWD's drive letter are resolved against the root of the drive. |
| 40 | try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, &empty_env); |
| 41 | try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, &empty_env); |
| 42 | |
| 43 | // Bare drive-relative path with no components |
| 44 | try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, &empty_env); |
| 45 | try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, &empty_env); |
| 46 | |
| 47 | // Bare drive-relative path with no components, drive-CWD set |
| 48 | try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, &alt_drive_env_map); |
| 49 | try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, &alt_drive_env_map); |
| 50 | |
| 51 | // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set |
| 52 | try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, &alt_drive_env_map); |
| 53 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, &alt_drive_env_map); |
| 54 | |
| 55 | // Bare drive-relative should always be equivalent to itself |
| 56 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &alt_drive_env_map); |
| 57 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &alt_drive_env_map); |
| 58 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &empty_env); |
| 59 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &empty_env); |
| 60 | } |
| 61 | |
| 62 | if (parsed_cwd_path.kind == .unc_absolute) { |
| 63 | const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter}); |
| 64 | |
| 65 | { |
| 66 | try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, &empty_env); |
| 67 | try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, &empty_env); |
| 68 | } |
| 69 | } else if (parsed_cwd_path.kind == .drive_absolute) { |
| 70 | const cur_drive_letter = parsed_cwd_path.root[0]; |
| 71 | const path_beyond_root = cwd_path[3..]; |
| 72 | const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root }); |
| 73 | |
| 74 | { |
| 75 | try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, &empty_env); |
| 76 | try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, &empty_env); |
| 77 | } |
| 78 | { |
| 79 | const drive_abs = cwd_path; |
| 80 | const drive_rel = parsed_cwd_path.root[0..2]; |
| 81 | try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, &empty_env); |
| 82 | try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, &empty_env); |
| 83 | } |
| 84 | } else { |
| 85 | return error.UnexpectedPathType; |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | fn checkRelative( |
| 90 | allocator: std.mem.Allocator, |
| 91 | io: Io, |
| 92 | expected_stdout: []const u8, |
| 93 | argv: []const []const u8, |
| 94 | environ_map: ?*const std.process.Environ.Map, |
| 95 | ) !void { |
| 96 | const result = try std.process.run(allocator, io, .{ |
| 97 | .argv = argv, |
| 98 | .environ_map = environ_map, |
| 99 | }); |
| 100 | defer allocator.free(result.stdout); |
| 101 | defer allocator.free(result.stderr); |
| 102 | |
| 103 | try std.testing.expectEqualStrings("", result.stderr); |
| 104 | try std.testing.expectEqualStrings(expected_stdout, result.stdout); |
| 105 | } |
| 106 | |
| 107 | fn getAltDriveLetter(path: []const u8) !u8 { |
| 108 | const parsed = std.fs.path.parsePathWindows(u8, path); |
| 109 | return switch (parsed.kind) { |
| 110 | .drive_absolute => { |
| 111 | const cur_drive_letter = parsed.root[0]; |
| 112 | const next_drive_letter_index = (std.ascii.toUpper(cur_drive_letter) - 'A' + 1) % 26; |
| 113 | const next_drive_letter = next_drive_letter_index + 'A'; |
| 114 | return next_drive_letter; |
| 115 | }, |
| 116 | .unc_absolute => { |
| 117 | return 'C'; |
| 118 | }, |
| 119 | else => return error.UnexpectedPathType, |
| 120 | }; |
| 121 | } |
| 122 | |
| 123 | test getAltDriveLetter { |
| 124 | try std.testing.expectEqual('D', try getAltDriveLetter("C:\\")); |
| 125 | try std.testing.expectEqual('B', try getAltDriveLetter("a:\\")); |
| 126 | try std.testing.expectEqual('A', try getAltDriveLetter("Z:\\")); |
| 127 | try std.testing.expectEqual('C', try getAltDriveLetter("\\\\foo\\bar")); |
| 128 | } |