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}