zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 162faffc (43923B) - Raw


      1 const std = @import("../index.zig");
      2 const builtin = @import("builtin");
      3 const Os = builtin.Os;
      4 const debug = std.debug;
      5 const assert = debug.assert;
      6 const mem = std.mem;
      7 const fmt = std.fmt;
      8 const Allocator = mem.Allocator;
      9 const os = std.os;
     10 const math = std.math;
     11 const posix = os.posix;
     12 const windows = os.windows;
     13 const cstr = std.cstr;
     14 
     15 pub const sep_windows = '\\';
     16 pub const sep_posix = '/';
     17 pub const sep = if (is_windows) sep_windows else sep_posix;
     18 
     19 pub const delimiter_windows = ';';
     20 pub const delimiter_posix = ':';
     21 pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
     22 
     23 const is_windows = builtin.os == builtin.Os.windows;
     24 
     25 pub fn isSep(byte: u8) bool {
     26     if (is_windows) {
     27         return byte == '/' or byte == '\\';
     28     } else {
     29         return byte == '/';
     30     }
     31 }
     32 
     33 /// Naively combines a series of paths with the native path seperator.
     34 /// Allocates memory for the result, which must be freed by the caller.
     35 pub fn join(allocator: &Allocator, paths: ...) ![]u8 {
     36     if (is_windows) {
     37         return joinWindows(allocator, paths);
     38     } else {
     39         return joinPosix(allocator, paths);
     40     }
     41 }
     42 
     43 pub fn joinWindows(allocator: &Allocator, paths: ...) ![]u8 {
     44     return mem.join(allocator, sep_windows, paths);
     45 }
     46 
     47 pub fn joinPosix(allocator: &Allocator, paths: ...) ![]u8 {
     48     return mem.join(allocator, sep_posix, paths);
     49 }
     50 
     51 test "os.path.join" {
     52     assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\b", "c"), "c:\\a\\b\\c"));
     53     assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\b\\", "c"), "c:\\a\\b\\c"));
     54 
     55     assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\", "a", "b\\", "c"), "c:\\a\\b\\c"));
     56     assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\", "b\\", "c"), "c:\\a\\b\\c"));
     57 
     58     assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig"), "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig"));
     59 
     60     assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/b", "c"), "/a/b/c"));
     61     assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/b/", "c"), "/a/b/c"));
     62 
     63     assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c"));
     64     assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/", "b/", "c"), "/a/b/c"));
     65 
     66     assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"), "/home/andy/dev/zig/build/lib/zig/std/io.zig"));
     67 }
     68 
     69 pub fn isAbsolute(path: []const u8) bool {
     70     if (is_windows) {
     71         return isAbsoluteWindows(path);
     72     } else {
     73         return isAbsolutePosix(path);
     74     }
     75 }
     76 
     77 pub fn isAbsoluteWindows(path: []const u8) bool {
     78     if (path[0] == '/')
     79         return true;
     80 
     81     if (path[0] == '\\') {
     82         return true;
     83     }
     84     if (path.len < 3) {
     85         return false;
     86     }
     87     if (path[1] == ':') {
     88         if (path[2] == '/')
     89             return true;
     90         if (path[2] == '\\')
     91             return true;
     92     }
     93     return false;
     94 }
     95 
     96 pub fn isAbsolutePosix(path: []const u8) bool {
     97     return path[0] == sep_posix;
     98 }
     99 
    100 test "os.path.isAbsoluteWindows" {
    101     testIsAbsoluteWindows("/", true);
    102     testIsAbsoluteWindows("//", true);
    103     testIsAbsoluteWindows("//server", true);
    104     testIsAbsoluteWindows("//server/file", true);
    105     testIsAbsoluteWindows("\\\\server\\file", true);
    106     testIsAbsoluteWindows("\\\\server", true);
    107     testIsAbsoluteWindows("\\\\", true);
    108     testIsAbsoluteWindows("c", false);
    109     testIsAbsoluteWindows("c:", false);
    110     testIsAbsoluteWindows("c:\\", true);
    111     testIsAbsoluteWindows("c:/", true);
    112     testIsAbsoluteWindows("c://", true);
    113     testIsAbsoluteWindows("C:/Users/", true);
    114     testIsAbsoluteWindows("C:\\Users\\", true);
    115     testIsAbsoluteWindows("C:cwd/another", false);
    116     testIsAbsoluteWindows("C:cwd\\another", false);
    117     testIsAbsoluteWindows("directory/directory", false);
    118     testIsAbsoluteWindows("directory\\directory", false);
    119     testIsAbsoluteWindows("/usr/local", true);
    120 }
    121 
    122 test "os.path.isAbsolutePosix" {
    123     testIsAbsolutePosix("/home/foo", true);
    124     testIsAbsolutePosix("/home/foo/..", true);
    125     testIsAbsolutePosix("bar/", false);
    126     testIsAbsolutePosix("./baz", false);
    127 }
    128 
    129 fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) void {
    130     assert(isAbsoluteWindows(path) == expected_result);
    131 }
    132 
    133 fn testIsAbsolutePosix(path: []const u8, expected_result: bool) void {
    134     assert(isAbsolutePosix(path) == expected_result);
    135 }
    136 
    137 pub const WindowsPath = struct {
    138     is_abs: bool,
    139     kind: Kind,
    140     disk_designator: []const u8,
    141 
    142     pub const Kind = enum {
    143         None,
    144         Drive,
    145         NetworkShare,
    146     };
    147 };
    148 
    149 pub fn windowsParsePath(path: []const u8) WindowsPath {
    150     if (path.len >= 2 and path[1] == ':') {
    151         return WindowsPath{
    152             .is_abs = isAbsoluteWindows(path),
    153             .kind = WindowsPath.Kind.Drive,
    154             .disk_designator = path[0..2],
    155         };
    156     }
    157     if (path.len >= 1 and (path[0] == '/' or path[0] == '\\') and
    158         (path.len == 1 or (path[1] != '/' and path[1] != '\\')))
    159     {
    160         return WindowsPath{
    161             .is_abs = true,
    162             .kind = WindowsPath.Kind.None,
    163             .disk_designator = path[0..0],
    164         };
    165     }
    166     const relative_path = WindowsPath{
    167         .kind = WindowsPath.Kind.None,
    168         .disk_designator = []u8{},
    169         .is_abs = false,
    170     };
    171     if (path.len < "//a/b".len) {
    172         return relative_path;
    173     }
    174 
    175     // TODO when I combined these together with `inline for` the compiler crashed
    176     {
    177         const this_sep = '/';
    178         const two_sep = []u8{ this_sep, this_sep };
    179         if (mem.startsWith(u8, path, two_sep)) {
    180             if (path[2] == this_sep) {
    181                 return relative_path;
    182             }
    183 
    184             var it = mem.split(path, []u8{this_sep});
    185             _ = (it.next() ?? return relative_path);
    186             _ = (it.next() ?? return relative_path);
    187             return WindowsPath{
    188                 .is_abs = isAbsoluteWindows(path),
    189                 .kind = WindowsPath.Kind.NetworkShare,
    190                 .disk_designator = path[0..it.index],
    191             };
    192         }
    193     }
    194     {
    195         const this_sep = '\\';
    196         const two_sep = []u8{ this_sep, this_sep };
    197         if (mem.startsWith(u8, path, two_sep)) {
    198             if (path[2] == this_sep) {
    199                 return relative_path;
    200             }
    201 
    202             var it = mem.split(path, []u8{this_sep});
    203             _ = (it.next() ?? return relative_path);
    204             _ = (it.next() ?? return relative_path);
    205             return WindowsPath{
    206                 .is_abs = isAbsoluteWindows(path),
    207                 .kind = WindowsPath.Kind.NetworkShare,
    208                 .disk_designator = path[0..it.index],
    209             };
    210         }
    211     }
    212     return relative_path;
    213 }
    214 
    215 test "os.path.windowsParsePath" {
    216     {
    217         const parsed = windowsParsePath("//a/b");
    218         assert(parsed.is_abs);
    219         assert(parsed.kind == WindowsPath.Kind.NetworkShare);
    220         assert(mem.eql(u8, parsed.disk_designator, "//a/b"));
    221     }
    222     {
    223         const parsed = windowsParsePath("\\\\a\\b");
    224         assert(parsed.is_abs);
    225         assert(parsed.kind == WindowsPath.Kind.NetworkShare);
    226         assert(mem.eql(u8, parsed.disk_designator, "\\\\a\\b"));
    227     }
    228     {
    229         const parsed = windowsParsePath("\\\\a\\");
    230         assert(!parsed.is_abs);
    231         assert(parsed.kind == WindowsPath.Kind.None);
    232         assert(mem.eql(u8, parsed.disk_designator, ""));
    233     }
    234     {
    235         const parsed = windowsParsePath("/usr/local");
    236         assert(parsed.is_abs);
    237         assert(parsed.kind == WindowsPath.Kind.None);
    238         assert(mem.eql(u8, parsed.disk_designator, ""));
    239     }
    240     {
    241         const parsed = windowsParsePath("c:../");
    242         assert(!parsed.is_abs);
    243         assert(parsed.kind == WindowsPath.Kind.Drive);
    244         assert(mem.eql(u8, parsed.disk_designator, "c:"));
    245     }
    246 }
    247 
    248 pub fn diskDesignator(path: []const u8) []const u8 {
    249     if (is_windows) {
    250         return diskDesignatorWindows(path);
    251     } else {
    252         return "";
    253     }
    254 }
    255 
    256 pub fn diskDesignatorWindows(path: []const u8) []const u8 {
    257     return windowsParsePath(path).disk_designator;
    258 }
    259 
    260 fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
    261     const sep1 = ns1[0];
    262     const sep2 = ns2[0];
    263 
    264     var it1 = mem.split(ns1, []u8{sep1});
    265     var it2 = mem.split(ns2, []u8{sep2});
    266 
    267     // TODO ASCII is wrong, we actually need full unicode support to compare paths.
    268     return asciiEqlIgnoreCase(??it1.next(), ??it2.next());
    269 }
    270 
    271 fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
    272     switch (kind) {
    273         WindowsPath.Kind.None => {
    274             assert(p1.len == 0);
    275             assert(p2.len == 0);
    276             return true;
    277         },
    278         WindowsPath.Kind.Drive => {
    279             return asciiUpper(p1[0]) == asciiUpper(p2[0]);
    280         },
    281         WindowsPath.Kind.NetworkShare => {
    282             const sep1 = p1[0];
    283             const sep2 = p2[0];
    284 
    285             var it1 = mem.split(p1, []u8{sep1});
    286             var it2 = mem.split(p2, []u8{sep2});
    287 
    288             // TODO ASCII is wrong, we actually need full unicode support to compare paths.
    289             return asciiEqlIgnoreCase(??it1.next(), ??it2.next()) and asciiEqlIgnoreCase(??it1.next(), ??it2.next());
    290         },
    291     }
    292 }
    293 
    294 fn asciiUpper(byte: u8) u8 {
    295     return switch (byte) {
    296         'a'...'z' => 'A' + (byte - 'a'),
    297         else => byte,
    298     };
    299 }
    300 
    301 fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool {
    302     if (s1.len != s2.len)
    303         return false;
    304     var i: usize = 0;
    305     while (i < s1.len) : (i += 1) {
    306         if (asciiUpper(s1[i]) != asciiUpper(s2[i]))
    307             return false;
    308     }
    309     return true;
    310 }
    311 
    312 /// Converts the command line arguments into a slice and calls `resolveSlice`.
    313 pub fn resolve(allocator: &Allocator, args: ...) ![]u8 {
    314     var paths: [args.len][]const u8 = undefined;
    315     comptime var arg_i = 0;
    316     inline while (arg_i < args.len) : (arg_i += 1) {
    317         paths[arg_i] = args[arg_i];
    318     }
    319     return resolveSlice(allocator, paths);
    320 }
    321 
    322 /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
    323 pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) ![]u8 {
    324     if (is_windows) {
    325         return resolveWindows(allocator, paths);
    326     } else {
    327         return resolvePosix(allocator, paths);
    328     }
    329 }
    330 
    331 /// This function is like a series of `cd` statements executed one after another.
    332 /// It resolves "." and "..".
    333 /// The result does not have a trailing path separator.
    334 /// If all paths are relative it uses the current working directory as a starting point.
    335 /// Each drive has its own current working directory.
    336 /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
    337 pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) ![]u8 {
    338     if (paths.len == 0) {
    339         assert(is_windows); // resolveWindows called on non windows can't use getCwd
    340         return os.getCwd(allocator);
    341     }
    342 
    343     // determine which disk designator we will result with, if any
    344     var result_drive_buf = "_:";
    345     var result_disk_designator: []const u8 = "";
    346     var have_drive_kind = WindowsPath.Kind.None;
    347     var have_abs_path = false;
    348     var first_index: usize = 0;
    349     var max_size: usize = 0;
    350     for (paths) |p, i| {
    351         const parsed = windowsParsePath(p);
    352         if (parsed.is_abs) {
    353             have_abs_path = true;
    354             first_index = i;
    355             max_size = result_disk_designator.len;
    356         }
    357         switch (parsed.kind) {
    358             WindowsPath.Kind.Drive => {
    359                 result_drive_buf[0] = asciiUpper(parsed.disk_designator[0]);
    360                 result_disk_designator = result_drive_buf[0..];
    361                 have_drive_kind = WindowsPath.Kind.Drive;
    362             },
    363             WindowsPath.Kind.NetworkShare => {
    364                 result_disk_designator = parsed.disk_designator;
    365                 have_drive_kind = WindowsPath.Kind.NetworkShare;
    366             },
    367             WindowsPath.Kind.None => {},
    368         }
    369         max_size += p.len + 1;
    370     }
    371 
    372     // if we will result with a disk designator, loop again to determine
    373     // which is the last time the disk designator is absolutely specified, if any
    374     // and count up the max bytes for paths related to this disk designator
    375     if (have_drive_kind != WindowsPath.Kind.None) {
    376         have_abs_path = false;
    377         first_index = 0;
    378         max_size = result_disk_designator.len;
    379         var correct_disk_designator = false;
    380 
    381         for (paths) |p, i| {
    382             const parsed = windowsParsePath(p);
    383             if (parsed.kind != WindowsPath.Kind.None) {
    384                 if (parsed.kind == have_drive_kind) {
    385                     correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);
    386                 } else {
    387                     continue;
    388                 }
    389             }
    390             if (!correct_disk_designator) {
    391                 continue;
    392             }
    393             if (parsed.is_abs) {
    394                 first_index = i;
    395                 max_size = result_disk_designator.len;
    396                 have_abs_path = true;
    397             }
    398             max_size += p.len + 1;
    399         }
    400     }
    401 
    402     // Allocate result and fill in the disk designator, calling getCwd if we have to.
    403     var result: []u8 = undefined;
    404     var result_index: usize = 0;
    405 
    406     if (have_abs_path) {
    407         switch (have_drive_kind) {
    408             WindowsPath.Kind.Drive => {
    409                 result = try allocator.alloc(u8, max_size);
    410 
    411                 mem.copy(u8, result, result_disk_designator);
    412                 result_index += result_disk_designator.len;
    413             },
    414             WindowsPath.Kind.NetworkShare => {
    415                 result = try allocator.alloc(u8, max_size);
    416                 var it = mem.split(paths[first_index], "/\\");
    417                 const server_name = ??it.next();
    418                 const other_name = ??it.next();
    419 
    420                 result[result_index] = '\\';
    421                 result_index += 1;
    422                 result[result_index] = '\\';
    423                 result_index += 1;
    424                 mem.copy(u8, result[result_index..], server_name);
    425                 result_index += server_name.len;
    426                 result[result_index] = '\\';
    427                 result_index += 1;
    428                 mem.copy(u8, result[result_index..], other_name);
    429                 result_index += other_name.len;
    430 
    431                 result_disk_designator = result[0..result_index];
    432             },
    433             WindowsPath.Kind.None => {
    434                 assert(is_windows); // resolveWindows called on non windows can't use getCwd
    435                 const cwd = try os.getCwd(allocator);
    436                 defer allocator.free(cwd);
    437                 const parsed_cwd = windowsParsePath(cwd);
    438                 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
    439                 mem.copy(u8, result, parsed_cwd.disk_designator);
    440                 result_index += parsed_cwd.disk_designator.len;
    441                 result_disk_designator = result[0..parsed_cwd.disk_designator.len];
    442                 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
    443                     result[0] = asciiUpper(result[0]);
    444                 }
    445                 have_drive_kind = parsed_cwd.kind;
    446             },
    447         }
    448     } else {
    449         assert(is_windows); // resolveWindows called on non windows can't use getCwd
    450         // TODO call get cwd for the result_disk_designator instead of the global one
    451         const cwd = try os.getCwd(allocator);
    452         defer allocator.free(cwd);
    453 
    454         result = try allocator.alloc(u8, max_size + cwd.len + 1);
    455 
    456         mem.copy(u8, result, cwd);
    457         result_index += cwd.len;
    458         const parsed_cwd = windowsParsePath(result[0..result_index]);
    459         result_disk_designator = parsed_cwd.disk_designator;
    460         if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
    461             result[0] = asciiUpper(result[0]);
    462         }
    463         have_drive_kind = parsed_cwd.kind;
    464     }
    465     errdefer allocator.free(result);
    466 
    467     // Now we know the disk designator to use, if any, and what kind it is. And our result
    468     // is big enough to append all the paths to.
    469     var correct_disk_designator = true;
    470     for (paths[first_index..]) |p, i| {
    471         const parsed = windowsParsePath(p);
    472 
    473         if (parsed.kind != WindowsPath.Kind.None) {
    474             if (parsed.kind == have_drive_kind) {
    475                 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);
    476             } else {
    477                 continue;
    478             }
    479         }
    480         if (!correct_disk_designator) {
    481             continue;
    482         }
    483         var it = mem.split(p[parsed.disk_designator.len..], "/\\");
    484         while (it.next()) |component| {
    485             if (mem.eql(u8, component, ".")) {
    486                 continue;
    487             } else if (mem.eql(u8, component, "..")) {
    488                 while (true) {
    489                     if (result_index == 0 or result_index == result_disk_designator.len)
    490                         break;
    491                     result_index -= 1;
    492                     if (result[result_index] == '\\' or result[result_index] == '/')
    493                         break;
    494                 }
    495             } else {
    496                 result[result_index] = sep_windows;
    497                 result_index += 1;
    498                 mem.copy(u8, result[result_index..], component);
    499                 result_index += component.len;
    500             }
    501         }
    502     }
    503 
    504     if (result_index == result_disk_designator.len) {
    505         result[result_index] = '\\';
    506         result_index += 1;
    507     }
    508 
    509     return result[0..result_index];
    510 }
    511 
    512 /// This function is like a series of `cd` statements executed one after another.
    513 /// It resolves "." and "..".
    514 /// The result does not have a trailing path separator.
    515 /// If all paths are relative it uses the current working directory as a starting point.
    516 pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) ![]u8 {
    517     if (paths.len == 0) {
    518         assert(!is_windows); // resolvePosix called on windows can't use getCwd
    519         return os.getCwd(allocator);
    520     }
    521 
    522     var first_index: usize = 0;
    523     var have_abs = false;
    524     var max_size: usize = 0;
    525     for (paths) |p, i| {
    526         if (isAbsolutePosix(p)) {
    527             first_index = i;
    528             have_abs = true;
    529             max_size = 0;
    530         }
    531         max_size += p.len + 1;
    532     }
    533 
    534     var result: []u8 = undefined;
    535     var result_index: usize = 0;
    536 
    537     if (have_abs) {
    538         result = try allocator.alloc(u8, max_size);
    539     } else {
    540         assert(!is_windows); // resolvePosix called on windows can't use getCwd
    541         const cwd = try os.getCwd(allocator);
    542         defer allocator.free(cwd);
    543         result = try allocator.alloc(u8, max_size + cwd.len + 1);
    544         mem.copy(u8, result, cwd);
    545         result_index += cwd.len;
    546     }
    547     errdefer allocator.free(result);
    548 
    549     for (paths[first_index..]) |p, i| {
    550         var it = mem.split(p, "/");
    551         while (it.next()) |component| {
    552             if (mem.eql(u8, component, ".")) {
    553                 continue;
    554             } else if (mem.eql(u8, component, "..")) {
    555                 while (true) {
    556                     if (result_index == 0)
    557                         break;
    558                     result_index -= 1;
    559                     if (result[result_index] == '/')
    560                         break;
    561                 }
    562             } else {
    563                 result[result_index] = '/';
    564                 result_index += 1;
    565                 mem.copy(u8, result[result_index..], component);
    566                 result_index += component.len;
    567             }
    568         }
    569     }
    570 
    571     if (result_index == 0) {
    572         result[0] = '/';
    573         result_index += 1;
    574     }
    575 
    576     return result[0..result_index];
    577 }
    578 
    579 test "os.path.resolve" {
    580     const cwd = try os.getCwd(debug.global_allocator);
    581     if (is_windows) {
    582         if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
    583             cwd[0] = asciiUpper(cwd[0]);
    584         }
    585         assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
    586     } else {
    587         assert(mem.eql(u8, testResolvePosix([][]const u8{ "a/b/c/", "../../.." }), cwd));
    588         assert(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd));
    589     }
    590 }
    591 
    592 test "os.path.resolveWindows" {
    593     if (is_windows) {
    594         const cwd = try os.getCwd(debug.global_allocator);
    595         const parsed_cwd = windowsParsePath(cwd);
    596         {
    597             const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });
    598             const expected = try join(debug.global_allocator, parsed_cwd.disk_designator, "usr\\local\\lib\\zig\\std\\array_list.zig");
    599             if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
    600                 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]);
    601             }
    602             assert(mem.eql(u8, result, expected));
    603         }
    604         {
    605             const result = testResolveWindows([][]const u8{ "usr/local", "lib\\zig" });
    606             const expected = try join(debug.global_allocator, cwd, "usr\\local\\lib\\zig");
    607             if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
    608                 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]);
    609             }
    610             assert(mem.eql(u8, result, expected));
    611         }
    612     }
    613 
    614     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }), "C:\\hi\\ok"));
    615     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }), "C:\\blah\\a"));
    616     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "C:../a" }), "C:\\blah\\a"));
    617     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "d:\\a/b\\c/d", "\\e.exe" }), "D:\\e.exe"));
    618     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "c:/some/file" }), "C:\\some\\file"));
    619     assert(mem.eql(u8, testResolveWindows([][]const u8{ "d:/ignore", "d:some/dir//" }), "D:\\ignore\\some\\dir"));
    620     assert(mem.eql(u8, testResolveWindows([][]const u8{ "//server/share", "..", "relative\\" }), "\\\\server\\share\\relative"));
    621     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//" }), "C:\\"));
    622     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//dir" }), "C:\\dir"));
    623     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server/share" }), "\\\\server\\share\\"));
    624     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server//share" }), "\\\\server\\share\\"));
    625     assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "///some//dir" }), "C:\\some\\dir"));
    626     assert(mem.eql(u8, testResolveWindows([][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }), "C:\\foo\\tmp.3\\cycles\\root.js"));
    627 }
    628 
    629 test "os.path.resolvePosix" {
    630     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c" }), "/a/b/c"));
    631     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c", "//d", "e///" }), "/d/e"));
    632     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b/c", "..", "../" }), "/a"));
    633     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/", "..", ".." }), "/"));
    634     assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c"));
    635 
    636     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "../", "file/" }), "/var/file"));
    637     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "/../", "file/" }), "/file"));
    638     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/some/dir", ".", "/absolute/" }), "/absolute"));
    639     assert(mem.eql(u8, testResolvePosix([][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }), "/foo/tmp.3/cycles/root.js"));
    640 }
    641 
    642 fn testResolveWindows(paths: []const []const u8) []u8 {
    643     return resolveWindows(debug.global_allocator, paths) catch unreachable;
    644 }
    645 
    646 fn testResolvePosix(paths: []const []const u8) []u8 {
    647     return resolvePosix(debug.global_allocator, paths) catch unreachable;
    648 }
    649 
    650 /// If the path is a file in the current directory (no directory component)
    651 /// then the returned slice has .len = 0.
    652 pub fn dirname(path: []const u8) []const u8 {
    653     if (is_windows) {
    654         return dirnameWindows(path);
    655     } else {
    656         return dirnamePosix(path);
    657     }
    658 }
    659 
    660 pub fn dirnameWindows(path: []const u8) []const u8 {
    661     if (path.len == 0)
    662         return path[0..0];
    663 
    664     const root_slice = diskDesignatorWindows(path);
    665     if (path.len == root_slice.len)
    666         return path;
    667 
    668     const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
    669 
    670     var end_index: usize = path.len - 1;
    671 
    672     while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) {
    673         if (end_index == 0)
    674             return path[0..0];
    675         end_index -= 1;
    676     }
    677 
    678     while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) {
    679         if (end_index == 0)
    680             return path[0..0];
    681         end_index -= 1;
    682     }
    683 
    684     if (have_root_slash and end_index == root_slice.len) {
    685         end_index += 1;
    686     }
    687 
    688     return path[0..end_index];
    689 }
    690 
    691 pub fn dirnamePosix(path: []const u8) []const u8 {
    692     if (path.len == 0)
    693         return path[0..0];
    694 
    695     var end_index: usize = path.len - 1;
    696     while (path[end_index] == '/') {
    697         if (end_index == 0)
    698             return path[0..1];
    699         end_index -= 1;
    700     }
    701 
    702     while (path[end_index] != '/') {
    703         if (end_index == 0)
    704             return path[0..0];
    705         end_index -= 1;
    706     }
    707 
    708     if (end_index == 0 and path[end_index] == '/')
    709         return path[0..1];
    710 
    711     return path[0..end_index];
    712 }
    713 
    714 test "os.path.dirnamePosix" {
    715     testDirnamePosix("/a/b/c", "/a/b");
    716     testDirnamePosix("/a/b/c///", "/a/b");
    717     testDirnamePosix("/a", "/");
    718     testDirnamePosix("/", "/");
    719     testDirnamePosix("////", "/");
    720     testDirnamePosix("", "");
    721     testDirnamePosix("a", "");
    722     testDirnamePosix("a/", "");
    723     testDirnamePosix("a//", "");
    724 }
    725 
    726 test "os.path.dirnameWindows" {
    727     testDirnameWindows("c:\\", "c:\\");
    728     testDirnameWindows("c:\\foo", "c:\\");
    729     testDirnameWindows("c:\\foo\\", "c:\\");
    730     testDirnameWindows("c:\\foo\\bar", "c:\\foo");
    731     testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
    732     testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
    733     testDirnameWindows("\\", "\\");
    734     testDirnameWindows("\\foo", "\\");
    735     testDirnameWindows("\\foo\\", "\\");
    736     testDirnameWindows("\\foo\\bar", "\\foo");
    737     testDirnameWindows("\\foo\\bar\\", "\\foo");
    738     testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
    739     testDirnameWindows("c:", "c:");
    740     testDirnameWindows("c:foo", "c:");
    741     testDirnameWindows("c:foo\\", "c:");
    742     testDirnameWindows("c:foo\\bar", "c:foo");
    743     testDirnameWindows("c:foo\\bar\\", "c:foo");
    744     testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
    745     testDirnameWindows("file:stream", "");
    746     testDirnameWindows("dir\\file:stream", "dir");
    747     testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");
    748     testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
    749     testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
    750     testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
    751     testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo");
    752     testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar");
    753     testDirnameWindows("/a/b/", "/a");
    754     testDirnameWindows("/a/b", "/a");
    755     testDirnameWindows("/a", "/");
    756     testDirnameWindows("", "");
    757     testDirnameWindows("/", "/");
    758     testDirnameWindows("////", "/");
    759     testDirnameWindows("foo", "");
    760 }
    761 
    762 fn testDirnamePosix(input: []const u8, expected_output: []const u8) void {
    763     assert(mem.eql(u8, dirnamePosix(input), expected_output));
    764 }
    765 
    766 fn testDirnameWindows(input: []const u8, expected_output: []const u8) void {
    767     assert(mem.eql(u8, dirnameWindows(input), expected_output));
    768 }
    769 
    770 pub fn basename(path: []const u8) []const u8 {
    771     if (is_windows) {
    772         return basenameWindows(path);
    773     } else {
    774         return basenamePosix(path);
    775     }
    776 }
    777 
    778 pub fn basenamePosix(path: []const u8) []const u8 {
    779     if (path.len == 0)
    780         return []u8{};
    781 
    782     var end_index: usize = path.len - 1;
    783     while (path[end_index] == '/') {
    784         if (end_index == 0)
    785             return []u8{};
    786         end_index -= 1;
    787     }
    788     var start_index: usize = end_index;
    789     end_index += 1;
    790     while (path[start_index] != '/') {
    791         if (start_index == 0)
    792             return path[0..end_index];
    793         start_index -= 1;
    794     }
    795 
    796     return path[start_index + 1 .. end_index];
    797 }
    798 
    799 pub fn basenameWindows(path: []const u8) []const u8 {
    800     if (path.len == 0)
    801         return []u8{};
    802 
    803     var end_index: usize = path.len - 1;
    804     while (true) {
    805         const byte = path[end_index];
    806         if (byte == '/' or byte == '\\') {
    807             if (end_index == 0)
    808                 return []u8{};
    809             end_index -= 1;
    810             continue;
    811         }
    812         if (byte == ':' and end_index == 1) {
    813             return []u8{};
    814         }
    815         break;
    816     }
    817 
    818     var start_index: usize = end_index;
    819     end_index += 1;
    820     while (path[start_index] != '/' and path[start_index] != '\\' and
    821         !(path[start_index] == ':' and start_index == 1))
    822     {
    823         if (start_index == 0)
    824             return path[0..end_index];
    825         start_index -= 1;
    826     }
    827 
    828     return path[start_index + 1 .. end_index];
    829 }
    830 
    831 test "os.path.basename" {
    832     testBasename("", "");
    833     testBasename("/", "");
    834     testBasename("/dir/basename.ext", "basename.ext");
    835     testBasename("/basename.ext", "basename.ext");
    836     testBasename("basename.ext", "basename.ext");
    837     testBasename("basename.ext/", "basename.ext");
    838     testBasename("basename.ext//", "basename.ext");
    839     testBasename("/aaa/bbb", "bbb");
    840     testBasename("/aaa/", "aaa");
    841     testBasename("/aaa/b", "b");
    842     testBasename("/a/b", "b");
    843     testBasename("//a", "a");
    844 
    845     testBasenamePosix("\\dir\\basename.ext", "\\dir\\basename.ext");
    846     testBasenamePosix("\\basename.ext", "\\basename.ext");
    847     testBasenamePosix("basename.ext", "basename.ext");
    848     testBasenamePosix("basename.ext\\", "basename.ext\\");
    849     testBasenamePosix("basename.ext\\\\", "basename.ext\\\\");
    850     testBasenamePosix("foo", "foo");
    851 
    852     testBasenameWindows("\\dir\\basename.ext", "basename.ext");
    853     testBasenameWindows("\\basename.ext", "basename.ext");
    854     testBasenameWindows("basename.ext", "basename.ext");
    855     testBasenameWindows("basename.ext\\", "basename.ext");
    856     testBasenameWindows("basename.ext\\\\", "basename.ext");
    857     testBasenameWindows("foo", "foo");
    858     testBasenameWindows("C:", "");
    859     testBasenameWindows("C:.", ".");
    860     testBasenameWindows("C:\\", "");
    861     testBasenameWindows("C:\\dir\\base.ext", "base.ext");
    862     testBasenameWindows("C:\\basename.ext", "basename.ext");
    863     testBasenameWindows("C:basename.ext", "basename.ext");
    864     testBasenameWindows("C:basename.ext\\", "basename.ext");
    865     testBasenameWindows("C:basename.ext\\\\", "basename.ext");
    866     testBasenameWindows("C:foo", "foo");
    867     testBasenameWindows("file:stream", "file:stream");
    868 }
    869 
    870 fn testBasename(input: []const u8, expected_output: []const u8) void {
    871     assert(mem.eql(u8, basename(input), expected_output));
    872 }
    873 
    874 fn testBasenamePosix(input: []const u8, expected_output: []const u8) void {
    875     assert(mem.eql(u8, basenamePosix(input), expected_output));
    876 }
    877 
    878 fn testBasenameWindows(input: []const u8, expected_output: []const u8) void {
    879     assert(mem.eql(u8, basenameWindows(input), expected_output));
    880 }
    881 
    882 /// Returns the relative path from `from` to `to`. If `from` and `to` each
    883 /// resolve to the same path (after calling `resolve` on each), a zero-length
    884 /// string is returned.
    885 /// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
    886 pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 {
    887     if (is_windows) {
    888         return relativeWindows(allocator, from, to);
    889     } else {
    890         return relativePosix(allocator, from, to);
    891     }
    892 }
    893 
    894 pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 {
    895     const resolved_from = try resolveWindows(allocator, [][]const u8{from});
    896     defer allocator.free(resolved_from);
    897 
    898     var clean_up_resolved_to = true;
    899     const resolved_to = try resolveWindows(allocator, [][]const u8{to});
    900     defer if (clean_up_resolved_to) allocator.free(resolved_to);
    901 
    902     const parsed_from = windowsParsePath(resolved_from);
    903     const parsed_to = windowsParsePath(resolved_to);
    904     const result_is_to = x: {
    905         if (parsed_from.kind != parsed_to.kind) {
    906             break :x true;
    907         } else switch (parsed_from.kind) {
    908             WindowsPath.Kind.NetworkShare => {
    909                 break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
    910             },
    911             WindowsPath.Kind.Drive => {
    912                 break :x asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
    913             },
    914             else => unreachable,
    915         }
    916     };
    917 
    918     if (result_is_to) {
    919         clean_up_resolved_to = false;
    920         return resolved_to;
    921     }
    922 
    923     var from_it = mem.split(resolved_from, "/\\");
    924     var to_it = mem.split(resolved_to, "/\\");
    925     while (true) {
    926         const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest());
    927         const to_rest = to_it.rest();
    928         if (to_it.next()) |to_component| {
    929             // TODO ASCII is wrong, we actually need full unicode support to compare paths.
    930             if (asciiEqlIgnoreCase(from_component, to_component))
    931                 continue;
    932         }
    933         var up_count: usize = 1;
    934         while (from_it.next()) |_| {
    935             up_count += 1;
    936         }
    937         const up_index_end = up_count * "..\\".len;
    938         const result = try allocator.alloc(u8, up_index_end + to_rest.len);
    939         errdefer allocator.free(result);
    940 
    941         var result_index: usize = 0;
    942         while (result_index < up_index_end) {
    943             result[result_index] = '.';
    944             result_index += 1;
    945             result[result_index] = '.';
    946             result_index += 1;
    947             result[result_index] = '\\';
    948             result_index += 1;
    949         }
    950         // shave off the trailing slash
    951         result_index -= 1;
    952 
    953         var rest_it = mem.split(to_rest, "/\\");
    954         while (rest_it.next()) |to_component| {
    955             result[result_index] = '\\';
    956             result_index += 1;
    957             mem.copy(u8, result[result_index..], to_component);
    958             result_index += to_component.len;
    959         }
    960 
    961         return result[0..result_index];
    962     }
    963 
    964     return []u8{};
    965 }
    966 
    967 pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 {
    968     const resolved_from = try resolvePosix(allocator, [][]const u8{from});
    969     defer allocator.free(resolved_from);
    970 
    971     const resolved_to = try resolvePosix(allocator, [][]const u8{to});
    972     defer allocator.free(resolved_to);
    973 
    974     var from_it = mem.split(resolved_from, "/");
    975     var to_it = mem.split(resolved_to, "/");
    976     while (true) {
    977         const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest());
    978         const to_rest = to_it.rest();
    979         if (to_it.next()) |to_component| {
    980             if (mem.eql(u8, from_component, to_component))
    981                 continue;
    982         }
    983         var up_count: usize = 1;
    984         while (from_it.next()) |_| {
    985             up_count += 1;
    986         }
    987         const up_index_end = up_count * "../".len;
    988         const result = try allocator.alloc(u8, up_index_end + to_rest.len);
    989         errdefer allocator.free(result);
    990 
    991         var result_index: usize = 0;
    992         while (result_index < up_index_end) {
    993             result[result_index] = '.';
    994             result_index += 1;
    995             result[result_index] = '.';
    996             result_index += 1;
    997             result[result_index] = '/';
    998             result_index += 1;
    999         }
   1000         if (to_rest.len == 0) {
   1001             // shave off the trailing slash
   1002             return result[0 .. result_index - 1];
   1003         }
   1004 
   1005         mem.copy(u8, result[result_index..], to_rest);
   1006         return result;
   1007     }
   1008 
   1009     return []u8{};
   1010 }
   1011 
   1012 test "os.path.relative" {
   1013     testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
   1014     testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
   1015     testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
   1016     testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
   1017     testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
   1018     testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
   1019     testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
   1020     testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
   1021     testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
   1022     testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
   1023     testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
   1024     testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
   1025     testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
   1026     testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
   1027     testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
   1028     testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
   1029     testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
   1030     testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
   1031     testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
   1032     testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
   1033     testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz");
   1034     testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux");
   1035     testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
   1036     testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
   1037 
   1038     testRelativePosix("/var/lib", "/var", "..");
   1039     testRelativePosix("/var/lib", "/bin", "../../bin");
   1040     testRelativePosix("/var/lib", "/var/lib", "");
   1041     testRelativePosix("/var/lib", "/var/apache", "../apache");
   1042     testRelativePosix("/var/", "/var/lib", "lib");
   1043     testRelativePosix("/", "/var/lib", "var/lib");
   1044     testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
   1045     testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
   1046     testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
   1047     testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
   1048     testRelativePosix("/baz-quux", "/baz", "../baz");
   1049     testRelativePosix("/baz", "/baz-quux", "../baz-quux");
   1050 }
   1051 
   1052 fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) void {
   1053     const result = relativePosix(debug.global_allocator, from, to) catch unreachable;
   1054     assert(mem.eql(u8, result, expected_output));
   1055 }
   1056 
   1057 fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) void {
   1058     const result = relativeWindows(debug.global_allocator, from, to) catch unreachable;
   1059     assert(mem.eql(u8, result, expected_output));
   1060 }
   1061 
   1062 /// Return the canonicalized absolute pathname.
   1063 /// Expands all symbolic links and resolves references to `.`, `..`, and
   1064 /// extra `/` characters in ::pathname.
   1065 /// Caller must deallocate result.
   1066 pub fn real(allocator: &Allocator, pathname: []const u8) ![]u8 {
   1067     switch (builtin.os) {
   1068         Os.windows => {
   1069             const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
   1070             defer allocator.free(pathname_buf);
   1071 
   1072             mem.copy(u8, pathname_buf, pathname);
   1073             pathname_buf[pathname.len] = 0;
   1074 
   1075             const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null);
   1076             if (h_file == windows.INVALID_HANDLE_VALUE) {
   1077                 const err = windows.GetLastError();
   1078                 return switch (err) {
   1079                     windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
   1080                     windows.ERROR.ACCESS_DENIED => error.AccessDenied,
   1081                     windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong,
   1082                     else => os.unexpectedErrorWindows(err),
   1083                 };
   1084             }
   1085             defer os.close(h_file);
   1086             var buf = try allocator.alloc(u8, 256);
   1087             errdefer allocator.free(buf);
   1088             while (true) {
   1089                 const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong;
   1090                 const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS);
   1091 
   1092                 if (result == 0) {
   1093                     const err = windows.GetLastError();
   1094                     return switch (err) {
   1095                         windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
   1096                         windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory,
   1097                         windows.ERROR.INVALID_PARAMETER => unreachable,
   1098                         else => os.unexpectedErrorWindows(err),
   1099                     };
   1100                 }
   1101 
   1102                 if (result > buf.len) {
   1103                     buf = try allocator.realloc(u8, buf, result);
   1104                     continue;
   1105                 }
   1106 
   1107                 // windows returns \\?\ prepended to the path
   1108                 // we strip it because nobody wants \\?\ prepended to their path
   1109                 const final_len = x: {
   1110                     if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) {
   1111                         var i: usize = 4;
   1112                         while (i < result) : (i += 1) {
   1113                             buf[i - 4] = buf[i];
   1114                         }
   1115                         break :x result - 4;
   1116                     } else {
   1117                         break :x result;
   1118                     }
   1119                 };
   1120 
   1121                 return allocator.shrink(u8, buf, final_len);
   1122             }
   1123         },
   1124         Os.macosx, Os.ios => {
   1125             // TODO instead of calling the libc function here, port the implementation
   1126             // to Zig, and then remove the NameTooLong error possibility.
   1127             const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
   1128             defer allocator.free(pathname_buf);
   1129 
   1130             const result_buf = try allocator.alloc(u8, posix.PATH_MAX);
   1131             errdefer allocator.free(result_buf);
   1132 
   1133             mem.copy(u8, pathname_buf, pathname);
   1134             pathname_buf[pathname.len] = 0;
   1135 
   1136             const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr));
   1137             if (err > 0) {
   1138                 return switch (err) {
   1139                     posix.EINVAL => unreachable,
   1140                     posix.EBADF => unreachable,
   1141                     posix.EFAULT => unreachable,
   1142                     posix.EACCES => error.AccessDenied,
   1143                     posix.ENOENT => error.FileNotFound,
   1144                     posix.ENOTSUP => error.NotSupported,
   1145                     posix.ENOTDIR => error.NotDir,
   1146                     posix.ENAMETOOLONG => error.NameTooLong,
   1147                     posix.ELOOP => error.SymLinkLoop,
   1148                     posix.EIO => error.InputOutput,
   1149                     else => os.unexpectedErrorPosix(err),
   1150                 };
   1151             }
   1152             return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
   1153         },
   1154         Os.linux => {
   1155             const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
   1156             defer os.close(fd);
   1157 
   1158             var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
   1159             const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable;
   1160 
   1161             return os.readLink(allocator, proc_path);
   1162         },
   1163         else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),
   1164     }
   1165 }
   1166 
   1167 test "os.path.real" {
   1168     // at least call it so it gets compiled
   1169     _ = real(debug.global_allocator, "some_path");
   1170 }