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