authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-20 15:06:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-22 20:57:56-07:00
logd24aaf8847336e12b6571e13d57f6d112452d97d
treea806cdf1d5dc112af321ff54041a4f7f9a247020
parent21bd13626d66c36c327bb317bd09cad979d92327

std.fs.path.resolve: eliminate getcwd() syscall

This is a breaking change to the API. Instead of the first path implicitly being the current working directory, it now asserts that the number of paths passed is greater than zero. Importantly, it never calls getcwd(); instead, it can possibly return ".", or a series of "../". This changes the error set to only be `error{OutOfMemory}`. closes #13613

3 files changed, 183 insertions(+), 216 deletions(-)

lib/std/fs/path.zig+179-211
...@@ -467,55 +467,49 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) ![]u8 {...@@ -467,55 +467,49 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) ![]u8 {
467/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.467/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
468/// Note: all usage of this function should be audited due to the existence of symlinks.468/// Note: all usage of this function should be audited due to the existence of symlinks.
469/// Without performing actual syscalls, resolving `..` could be incorrect.469/// Without performing actual syscalls, resolving `..` could be incorrect.
470/// This API may break in the future: https://github.com/ziglang/zig/issues/13613
470pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {471pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
471 if (paths.len == 0) {472 assert(paths.len > 0);
472 assert(native_os == .windows); // resolveWindows called on non windows can't use getCwd
473 return process.getCwdAlloc(allocator);
474 }
475473
476 // determine which disk designator we will result with, if any474 // determine which disk designator we will result with, if any
477 var result_drive_buf = "_:".*;475 var result_drive_buf = "_:".*;
478 var result_disk_designator: []const u8 = "";476 var disk_designator: []const u8 = "";
479 var have_drive_kind = WindowsPath.Kind.None;477 var drive_kind = WindowsPath.Kind.None;
480 var have_abs_path = false;478 var have_abs_path = false;
481 var first_index: usize = 0;479 var first_index: usize = 0;
482 var max_size: usize = 0;
483 for (paths) |p, i| {480 for (paths) |p, i| {
484 const parsed = windowsParsePath(p);481 const parsed = windowsParsePath(p);
485 if (parsed.is_abs) {482 if (parsed.is_abs) {
486 have_abs_path = true;483 have_abs_path = true;
487 first_index = i;484 first_index = i;
488 max_size = result_disk_designator.len;
489 }485 }
490 switch (parsed.kind) {486 switch (parsed.kind) {
491 WindowsPath.Kind.Drive => {487 .Drive => {
492 result_drive_buf[0] = ascii.toUpper(parsed.disk_designator[0]);488 result_drive_buf[0] = ascii.toUpper(parsed.disk_designator[0]);
493 result_disk_designator = result_drive_buf[0..];489 disk_designator = result_drive_buf[0..];
494 have_drive_kind = WindowsPath.Kind.Drive;490 drive_kind = WindowsPath.Kind.Drive;
495 },491 },
496 WindowsPath.Kind.NetworkShare => {492 .NetworkShare => {
497 result_disk_designator = parsed.disk_designator;493 disk_designator = parsed.disk_designator;
498 have_drive_kind = WindowsPath.Kind.NetworkShare;494 drive_kind = WindowsPath.Kind.NetworkShare;
499 },495 },
500 WindowsPath.Kind.None => {},496 .None => {},
501 }497 }
502 max_size += p.len + 1;
503 }498 }
504499
505 // if we will result with a disk designator, loop again to determine500 // if we will result with a disk designator, loop again to determine
506 // which is the last time the disk designator is absolutely specified, if any501 // which is the last time the disk designator is absolutely specified, if any
507 // and count up the max bytes for paths related to this disk designator502 // and count up the max bytes for paths related to this disk designator
508 if (have_drive_kind != WindowsPath.Kind.None) {503 if (drive_kind != WindowsPath.Kind.None) {
509 have_abs_path = false;504 have_abs_path = false;
510 first_index = 0;505 first_index = 0;
511 max_size = result_disk_designator.len;
512 var correct_disk_designator = false;506 var correct_disk_designator = false;
513507
514 for (paths) |p, i| {508 for (paths) |p, i| {
515 const parsed = windowsParsePath(p);509 const parsed = windowsParsePath(p);
516 if (parsed.kind != WindowsPath.Kind.None) {510 if (parsed.kind != WindowsPath.Kind.None) {
517 if (parsed.kind == have_drive_kind) {511 if (parsed.kind == drive_kind) {
518 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);512 correct_disk_designator = compareDiskDesignators(drive_kind, disk_designator, parsed.disk_designator);
519 } else {513 } else {
520 continue;514 continue;
521 }515 }
...@@ -525,92 +519,51 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {...@@ -525,92 +519,51 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
525 }519 }
526 if (parsed.is_abs) {520 if (parsed.is_abs) {
527 first_index = i;521 first_index = i;
528 max_size = result_disk_designator.len;
529 have_abs_path = true;522 have_abs_path = true;
530 }523 }
531 max_size += p.len + 1;
532 }524 }
533 }525 }
534526
535 // Allocate result and fill in the disk designator, calling getCwd if we have to.527 // Allocate result and fill in the disk designator.
536 var result: []u8 = undefined;528 var result = std.ArrayList(u8).init(allocator);
537 var result_index: usize = 0;529 defer result.deinit();
538
539 if (have_abs_path) {
540 switch (have_drive_kind) {
541 WindowsPath.Kind.Drive => {
542 result = try allocator.alloc(u8, max_size);
543530
544 mem.copy(u8, result, result_disk_designator);531 const disk_designator_len: usize = l: {
545 result_index += result_disk_designator.len;532 if (!have_abs_path) break :l 0;
533 switch (drive_kind) {
534 .Drive => {
535 try result.appendSlice(disk_designator);
536 break :l disk_designator.len;
546 },537 },
547 WindowsPath.Kind.NetworkShare => {538 .NetworkShare => {
548 result = try allocator.alloc(u8, max_size);
549 var it = mem.tokenize(u8, paths[first_index], "/\\");539 var it = mem.tokenize(u8, paths[first_index], "/\\");
550 const server_name = it.next().?;540 const server_name = it.next().?;
551 const other_name = it.next().?;541 const other_name = it.next().?;
552542
553 result[result_index] = '\\';543 try result.ensureUnusedCapacity(2 + 1 + server_name.len + other_name.len);
554 result_index += 1;544 result.appendSliceAssumeCapacity("\\\\");
555 result[result_index] = '\\';545 result.appendSliceAssumeCapacity(server_name);
556 result_index += 1;546 result.appendAssumeCapacity('\\');
557 mem.copy(u8, result[result_index..], server_name);547 result.appendSliceAssumeCapacity(other_name);
558 result_index += server_name.len;548
559 result[result_index] = '\\';549 break :l result.items.len;
560 result_index += 1;
561 mem.copy(u8, result[result_index..], other_name);
562 result_index += other_name.len;
563
564 result_disk_designator = result[0..result_index];
565 },550 },
566 WindowsPath.Kind.None => {551 .None => {
567 assert(native_os == .windows); // resolveWindows called on non windows can't use getCwd552 break :l 1;
568 const cwd = try process.getCwdAlloc(allocator);
569 defer allocator.free(cwd);
570 const parsed_cwd = windowsParsePath(cwd);
571 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
572 mem.copy(u8, result, parsed_cwd.disk_designator);
573 result_index += parsed_cwd.disk_designator.len;
574 result_disk_designator = result[0..parsed_cwd.disk_designator.len];
575 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
576 result[0] = ascii.toUpper(result[0]);
577 }
578 have_drive_kind = parsed_cwd.kind;
579 },553 },
580 }554 }
581 } else {555 };
582 assert(native_os == .windows); // resolveWindows called on non windows can't use getCwd
583 // TODO call get cwd for the result_disk_designator instead of the global one
584 const cwd = try process.getCwdAlloc(allocator);
585 defer allocator.free(cwd);
586
587 result = try allocator.alloc(u8, max_size + cwd.len + 1);
588
589 mem.copy(u8, result, cwd);
590 result_index += cwd.len;
591 const parsed_cwd = windowsParsePath(result[0..result_index]);
592 result_disk_designator = parsed_cwd.disk_designator;
593 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
594 result[0] = ascii.toUpper(result[0]);
595 // Remove the trailing slash if present, eg. if the cwd is a root
596 // directory.
597 if (cwd.len > 0 and cwd[cwd.len - 1] == sep_windows) {
598 result_index -= 1;
599 }
600 }
601 have_drive_kind = parsed_cwd.kind;
602 }
603 errdefer allocator.free(result);
604556
605 // Now we know the disk designator to use, if any, and what kind it is. And our result
606 // is big enough to append all the paths to.
607 var correct_disk_designator = true;557 var correct_disk_designator = true;
558 var negative_count: usize = 0;
559
608 for (paths[first_index..]) |p| {560 for (paths[first_index..]) |p| {
609 const parsed = windowsParsePath(p);561 const parsed = windowsParsePath(p);
610562
611 if (parsed.kind != WindowsPath.Kind.None) {563 if (parsed.kind != .None) {
612 if (parsed.kind == have_drive_kind) {564 if (parsed.kind == drive_kind) {
613 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);565 const dd = result.items[0..disk_designator_len];
566 correct_disk_designator = compareDiskDesignators(drive_kind, dd, parsed.disk_designator);
614 } else {567 } else {
615 continue;568 continue;
616 }569 }
...@@ -619,154 +572,167 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {...@@ -619,154 +572,167 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
619 continue;572 continue;
620 }573 }
621 var it = mem.tokenize(u8, p[parsed.disk_designator.len..], "/\\");574 var it = mem.tokenize(u8, p[parsed.disk_designator.len..], "/\\");
622 while (it.next()) |component| {575 component: while (it.next()) |component| {
623 if (mem.eql(u8, component, ".")) {576 if (mem.eql(u8, component, ".")) {
624 continue;577 continue;
625 } else if (mem.eql(u8, component, "..")) {578 } else if (mem.eql(u8, component, "..")) {
626 while (true) {579 while (true) {
627 if (result_index == 0 or result_index == result_disk_designator.len)580 if (result.items.len == 0) {
628 break;581 negative_count += 1;
629 result_index -= 1;582 continue :component;
630 if (result[result_index] == '\\' or result[result_index] == '/')583 }
584 if (result.items.len == disk_designator_len) {
631 break;585 break;
586 }
587 const end_with_sep = switch (result.items[result.items.len - 1]) {
588 '\\', '/' => true,
589 else => false,
590 };
591 result.items.len -= 1;
592 if (end_with_sep) break;
632 }593 }
594 } else if (!have_abs_path and result.items.len == 0) {
595 try result.appendSlice(component);
633 } else {596 } else {
634 result[result_index] = sep_windows;597 try result.ensureUnusedCapacity(1 + component.len);
635 result_index += 1;598 result.appendAssumeCapacity('\\');
636 mem.copy(u8, result[result_index..], component);599 result.appendSliceAssumeCapacity(component);
637 result_index += component.len;
638 }600 }
639 }601 }
640 }602 }
641603
642 if (result_index == result_disk_designator.len) {604 if (disk_designator_len != 0 and result.items.len == disk_designator_len) {
643 result[result_index] = '\\';605 try result.append('\\');
644 result_index += 1;606 return result.toOwnedSlice();
607 }
608
609 if (result.items.len == 0) {
610 if (negative_count == 0) {
611 return allocator.dupe(u8, ".");
612 } else {
613 const real_result = try allocator.alloc(u8, 3 * negative_count - 1);
614 var count = negative_count - 1;
615 var i: usize = 0;
616 while (count > 0) : (count -= 1) {
617 real_result[i..][0..3].* = "..\\".*;
618 i += 3;
619 }
620 real_result[i..][0..2].* = "..".*;
621 return real_result;
622 }
645 }623 }
646624
647 return allocator.shrink(result, result_index);625 if (negative_count == 0) {
626 return result.toOwnedSlice();
627 } else {
628 const real_result = try allocator.alloc(u8, 3 * negative_count + result.items.len);
629 var count = negative_count;
630 var i: usize = 0;
631 while (count > 0) : (count -= 1) {
632 real_result[i..][0..3].* = "..\\".*;
633 i += 3;
634 }
635 mem.copy(u8, real_result[i..], result.items);
636 return real_result;
637 }
648}638}
649639
650/// This function is like a series of `cd` statements executed one after another.640/// This function is like a series of `cd` statements executed one after another.
651/// It resolves "." and "..".641/// It resolves "." and "..".
652/// The result does not have a trailing path separator.642/// The result does not have a trailing path separator.
653/// If all paths are relative it uses the current working directory as a starting point.643/// This function does not perform any syscalls. Executing this series of path
654/// Note: all usage of this function should be audited due to the existence of symlinks.644/// lookups on the actual filesystem may produce different results due to
655/// Without performing actual syscalls, resolving `..` could be incorrect.645/// symlinks.
656pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {646pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 {
657 if (paths.len == 0) {647 assert(paths.len > 0);
658 assert(native_os != .windows); // resolvePosix called on windows can't use getCwd
659 return process.getCwdAlloc(allocator);
660 }
661648
662 var first_index: usize = 0;649 var result = std.ArrayList(u8).init(allocator);
663 var have_abs = false;650 defer result.deinit();
664 var max_size: usize = 0;
665 for (paths) |p, i| {
666 if (isAbsolutePosix(p)) {
667 first_index = i;
668 have_abs = true;
669 max_size = 0;
670 }
671 max_size += p.len + 1;
672 }
673
674 var result: []u8 = undefined;
675 var result_index: usize = 0;
676651
677 if (have_abs) {652 var negative_count: usize = 0;
678 result = try allocator.alloc(u8, max_size);653 var is_abs = false;
679 } else {
680 assert(native_os != .windows); // resolvePosix called on windows can't use getCwd
681 const cwd = try process.getCwdAlloc(allocator);
682 defer allocator.free(cwd);
683 result = try allocator.alloc(u8, max_size + cwd.len + 1);
684 mem.copy(u8, result, cwd);
685 result_index += cwd.len;
686 }
687 errdefer allocator.free(result);
688654
689 for (paths[first_index..]) |p| {655 for (paths) |p| {
656 if (isAbsolutePosix(p)) {
657 is_abs = true;
658 negative_count = 0;
659 result.clearRetainingCapacity();
660 }
690 var it = mem.tokenize(u8, p, "/");661 var it = mem.tokenize(u8, p, "/");
691 while (it.next()) |component| {662 component: while (it.next()) |component| {
692 if (mem.eql(u8, component, ".")) {663 if (mem.eql(u8, component, ".")) {
693 continue;664 continue;
694 } else if (mem.eql(u8, component, "..")) {665 } else if (mem.eql(u8, component, "..")) {
695 while (true) {666 while (true) {
696 if (result_index == 0)667 if (result.items.len == 0) {
697 break;668 negative_count += @boolToInt(!is_abs);
698 result_index -= 1;669 continue :component;
699 if (result[result_index] == '/')670 }
700 break;671 const ends_with_slash = result.items[result.items.len - 1] == '/';
672 result.items.len -= 1;
673 if (ends_with_slash) break;
701 }674 }
675 } else if (result.items.len > 0 or is_abs) {
676 try result.ensureUnusedCapacity(1 + component.len);
677 result.appendAssumeCapacity('/');
678 result.appendSliceAssumeCapacity(component);
702 } else {679 } else {
703 result[result_index] = '/';680 try result.appendSlice(component);
704 result_index += 1;
705 mem.copy(u8, result[result_index..], component);
706 result_index += component.len;
707 }681 }
708 }682 }
709 }683 }
710684
711 if (result_index == 0) {685 if (result.items.len == 0) {
712 result[0] = '/';686 if (is_abs) {
713 result_index += 1;687 return allocator.dupe(u8, "/");
688 }
689 if (negative_count == 0) {
690 return allocator.dupe(u8, ".");
691 } else {
692 const real_result = try allocator.alloc(u8, 3 * negative_count - 1);
693 var count = negative_count - 1;
694 var i: usize = 0;
695 while (count > 0) : (count -= 1) {
696 real_result[i..][0..3].* = "../".*;
697 i += 3;
698 }
699 real_result[i..][0..2].* = "..".*;
700 return real_result;
701 }
714 }702 }
715703
716 return allocator.shrink(result, result_index);704 if (negative_count == 0) {
705 return result.toOwnedSlice();
706 } else {
707 const real_result = try allocator.alloc(u8, 3 * negative_count + result.items.len);
708 var count = negative_count;
709 var i: usize = 0;
710 while (count > 0) : (count -= 1) {
711 real_result[i..][0..3].* = "../".*;
712 i += 3;
713 }
714 mem.copy(u8, real_result[i..], result.items);
715 return real_result;
716 }
717}717}
718718
719test "resolve" {719test "resolve" {
720 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;720 try testResolveWindows(&[_][]const u8{ "a\\b\\c\\", "..\\..\\.." }, "..");
721 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");721 try testResolveWindows(&[_][]const u8{"."}, ".");
722722
723 const cwd = try process.getCwdAlloc(testing.allocator);723 try testResolvePosix(&[_][]const u8{ "a/b/c/", "../../.." }, "..");
724 defer testing.allocator.free(cwd);724 try testResolvePosix(&[_][]const u8{"."}, ".");
725 if (native_os == .windows) {
726 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
727 cwd[0] = ascii.toUpper(cwd[0]);
728 }
729 try testResolveWindows(&[_][]const u8{"."}, cwd);
730 } else {
731 try testResolvePosix(&[_][]const u8{ "a/b/c/", "../../.." }, cwd);
732 try testResolvePosix(&[_][]const u8{"."}, cwd);
733 }
734}725}
735726
736test "resolveWindows" {727test "resolveWindows" {
737 if (builtin.target.cpu.arch == .aarch64) {728 try testResolveWindows(
738 // TODO https://github.com/ziglang/zig/issues/3288729 &[_][]const u8{ "Z:\\", "/usr/local", "lib\\zig\\std\\array_list.zig" },
739 return error.SkipZigTest;730 "Z:\\usr\\local\\lib\\zig\\std\\array_list.zig",
740 }731 );
741 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;732 try testResolveWindows(
742 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");733 &[_][]const u8{ "z:\\", "usr/local", "lib\\zig" },
743 if (native_os == .windows) {734 "Z:\\usr\\local\\lib\\zig",
744 const cwd = try process.getCwdAlloc(testing.allocator);735 );
745 defer testing.allocator.free(cwd);
746 const parsed_cwd = windowsParsePath(cwd);
747 {
748 const expected = try join(testing.allocator, &[_][]const u8{
749 parsed_cwd.disk_designator,
750 "usr\\local\\lib\\zig\\std\\array_list.zig",
751 });
752 defer testing.allocator.free(expected);
753 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
754 expected[0] = ascii.toUpper(parsed_cwd.disk_designator[0]);
755 }
756 try testResolveWindows(&[_][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }, expected);
757 }
758 {
759 const expected = try join(testing.allocator, &[_][]const u8{
760 cwd,
761 "usr\\local\\lib\\zig",
762 });
763 defer testing.allocator.free(expected);
764 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
765 expected[0] = ascii.toUpper(parsed_cwd.disk_designator[0]);
766 }
767 try testResolveWindows(&[_][]const u8{ "usr/local", "lib\\zig" }, expected);
768 }
769 }
770736
771 try testResolveWindows(&[_][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }, "C:\\hi\\ok");737 try testResolveWindows(&[_][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }, "C:\\hi\\ok");
772 try testResolveWindows(&[_][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }, "C:\\blah\\a");738 try testResolveWindows(&[_][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }, "C:\\blah\\a");
...@@ -781,12 +747,12 @@ test "resolveWindows" {...@@ -781,12 +747,12 @@ test "resolveWindows" {
781 try testResolveWindows(&[_][]const u8{ "c:/", "//server//share" }, "\\\\server\\share\\");747 try testResolveWindows(&[_][]const u8{ "c:/", "//server//share" }, "\\\\server\\share\\");
782 try testResolveWindows(&[_][]const u8{ "c:/", "///some//dir" }, "C:\\some\\dir");748 try testResolveWindows(&[_][]const u8{ "c:/", "///some//dir" }, "C:\\some\\dir");
783 try testResolveWindows(&[_][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }, "C:\\foo\\tmp.3\\cycles\\root.js");749 try testResolveWindows(&[_][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }, "C:\\foo\\tmp.3\\cycles\\root.js");
750
751 // Keep relative paths relative.
752 try testResolveWindows(&[_][]const u8{"a/b"}, "a\\b");
784}753}
785754
786test "resolvePosix" {755test "resolvePosix" {
787 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
788 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
789
790 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");756 try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
791 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");757 try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
792 try testResolvePosix(&[_][]const u8{ "/a/b/c", "..", "../" }, "/a");758 try testResolvePosix(&[_][]const u8{ "/a/b/c", "..", "../" }, "/a");
...@@ -797,18 +763,21 @@ test "resolvePosix" {...@@ -797,18 +763,21 @@ test "resolvePosix" {
797 try testResolvePosix(&[_][]const u8{ "/var/lib", "/../", "file/" }, "/file");763 try testResolvePosix(&[_][]const u8{ "/var/lib", "/../", "file/" }, "/file");
798 try testResolvePosix(&[_][]const u8{ "/some/dir", ".", "/absolute/" }, "/absolute");764 try testResolvePosix(&[_][]const u8{ "/some/dir", ".", "/absolute/" }, "/absolute");
799 try testResolvePosix(&[_][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }, "/foo/tmp.3/cycles/root.js");765 try testResolvePosix(&[_][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }, "/foo/tmp.3/cycles/root.js");
766
767 // Keep relative paths relative.
768 try testResolvePosix(&[_][]const u8{"a/b"}, "a/b");
800}769}
801770
802fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void {771fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void {
803 const actual = try resolveWindows(testing.allocator, paths);772 const actual = try resolveWindows(testing.allocator, paths);
804 defer testing.allocator.free(actual);773 defer testing.allocator.free(actual);
805 try testing.expect(mem.eql(u8, actual, expected));774 try testing.expectEqualStrings(expected, actual);
806}775}
807776
808fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void {777fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void {
809 const actual = try resolvePosix(testing.allocator, paths);778 const actual = try resolvePosix(testing.allocator, paths);
810 defer testing.allocator.free(actual);779 defer testing.allocator.free(actual);
811 try testing.expect(mem.eql(u8, actual, expected));780 try testing.expectEqualStrings(expected, actual);
812}781}
813782
814/// Strip the last component from a file path.783/// Strip the last component from a file path.
...@@ -1089,13 +1058,15 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1089,13 +1058,15 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1089 if (parsed_from.kind != parsed_to.kind) {1058 if (parsed_from.kind != parsed_to.kind) {
1090 break :x true;1059 break :x true;
1091 } else switch (parsed_from.kind) {1060 } else switch (parsed_from.kind) {
1092 WindowsPath.Kind.NetworkShare => {1061 .NetworkShare => {
1093 break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);1062 break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
1094 },1063 },
1095 WindowsPath.Kind.Drive => {1064 .Drive => {
1096 break :x ascii.toUpper(parsed_from.disk_designator[0]) != ascii.toUpper(parsed_to.disk_designator[0]);1065 break :x ascii.toUpper(parsed_from.disk_designator[0]) != ascii.toUpper(parsed_to.disk_designator[0]);
1097 },1066 },
1098 else => unreachable,1067 .None => {
1068 break :x false;
1069 },
1099 }1070 }
1100 };1071 };
11011072
...@@ -1194,13 +1165,6 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]...@@ -1194,13 +1165,6 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]
1194}1165}
11951166
1196test "relative" {1167test "relative" {
1197 if (builtin.target.cpu.arch == .aarch64) {
1198 // TODO https://github.com/ziglang/zig/issues/3288
1199 return error.SkipZigTest;
1200 }
1201 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1202 if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
1203
1204 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");1168 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1205 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");1169 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
1206 try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");1170 try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
...@@ -1226,6 +1190,10 @@ test "relative" {...@@ -1226,6 +1190,10 @@ test "relative" {
1226 try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");1190 try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
1227 try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");1191 try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
12281192
1193 try testRelativeWindows("a/b/c", "a\\b", "..");
1194 try testRelativeWindows("a/b/c", "a", "..\\..");
1195 try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");
1196
1229 try testRelativePosix("/var/lib", "/var", "..");1197 try testRelativePosix("/var/lib", "/var", "..");
1230 try testRelativePosix("/var/lib", "/bin", "../../bin");1198 try testRelativePosix("/var/lib", "/bin", "../../bin");
1231 try testRelativePosix("/var/lib", "/var/lib", "");1199 try testRelativePosix("/var/lib", "/var/lib", "");
...@@ -1243,13 +1211,13 @@ test "relative" {...@@ -1243,13 +1211,13 @@ test "relative" {
1243fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {1211fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {
1244 const result = try relativePosix(testing.allocator, from, to);1212 const result = try relativePosix(testing.allocator, from, to);
1245 defer testing.allocator.free(result);1213 defer testing.allocator.free(result);
1246 try testing.expectEqualSlices(u8, expected_output, result);1214 try testing.expectEqualStrings(expected_output, result);
1247}1215}
12481216
1249fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {1217fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {
1250 const result = try relativeWindows(testing.allocator, from, to);1218 const result = try relativeWindows(testing.allocator, from, to);
1251 defer testing.allocator.free(result);1219 defer testing.allocator.free(result);
1252 try testing.expectEqualSlices(u8, expected_output, result);1220 try testing.expectEqualStrings(expected_output, result);
1253}1221}
12541222
1255/// Returns the extension of the file name (if any).1223/// Returns the extension of the file name (if any).
lib/std/fs/test.zig+3-1
...@@ -1095,7 +1095,9 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {...@@ -1095,7 +1095,9 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
10951095
1096 const allocator = testing.allocator;1096 const allocator = testing.allocator;
10971097
1098 const file_paths: [1][]const u8 = .{"zig-test-absolute-paths.txt"};1098 const cwd = try std.process.getCwdAlloc(allocator);
1099 defer allocator.free(cwd);
1100 const file_paths: [2][]const u8 = .{ cwd, "zig-test-absolute-paths.txt" };
1099 const filename = try fs.path.resolve(allocator, &file_paths);1101 const filename = try fs.path.resolve(allocator, &file_paths);
1100 defer allocator.free(filename);1102 defer allocator.free(filename);
11011103
lib/std/fs/wasi.zig+1-4
...@@ -202,10 +202,7 @@ pub const PreopenList = struct {...@@ -202,10 +202,7 @@ pub const PreopenList = struct {
202 // POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "."202 // POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "."
203 const path = if (cwd_root) |cwd| blk: {203 const path = if (cwd_root) |cwd| blk: {
204 const resolve_paths: []const []const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path };204 const resolve_paths: []const []const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path };
205 break :blk fs.path.resolve(self.buffer.allocator, resolve_paths) catch |err| switch (err) {205 break :blk try fs.path.resolve(self.buffer.allocator, resolve_paths);
206 error.CurrentWorkingDirectoryUnlinked => unreachable, // root is absolute, so CWD not queried
207 else => |e| return e,
208 };
209 } else blk: {206 } else blk: {
210 // If we were provided no CWD root, we preserve the preopen dir without resolving207 // If we were provided no CWD root, we preserve the preopen dir without resolving
211 break :blk try self.buffer.allocator.dupe(u8, raw_path);208 break :blk try self.buffer.allocator.dupe(u8, raw_path);