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
This commit is contained in:
Andrew Kelley 2022-11-20 15:06:14 -07:00
parent 21bd13626d
commit d24aaf8847
3 changed files with 182 additions and 215 deletions

View File

@ -467,55 +467,49 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) ![]u8 {
/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
/// Note: all usage of this function should be audited due to the existence of symlinks. /// Note: all usage of this function should be audited due to the existence of symlinks.
/// Without performing actual syscalls, resolving `..` could be incorrect. /// Without performing actual syscalls, resolving `..` could be incorrect.
/// This API may break in the future: https://github.com/ziglang/zig/issues/13613
pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 { pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) { assert(paths.len > 0);
assert(native_os == .windows); // resolveWindows called on non windows can't use getCwd
return process.getCwdAlloc(allocator);
}
// determine which disk designator we will result with, if any // determine which disk designator we will result with, if any
var result_drive_buf = "_:".*; var result_drive_buf = "_:".*;
var result_disk_designator: []const u8 = ""; var disk_designator: []const u8 = "";
var have_drive_kind = WindowsPath.Kind.None; var drive_kind = WindowsPath.Kind.None;
var have_abs_path = false; var have_abs_path = false;
var first_index: usize = 0; var first_index: usize = 0;
var max_size: usize = 0;
for (paths) |p, i| { for (paths) |p, i| {
const parsed = windowsParsePath(p); const parsed = windowsParsePath(p);
if (parsed.is_abs) { if (parsed.is_abs) {
have_abs_path = true; have_abs_path = true;
first_index = i; first_index = i;
max_size = result_disk_designator.len;
} }
switch (parsed.kind) { switch (parsed.kind) {
WindowsPath.Kind.Drive => { .Drive => {
result_drive_buf[0] = ascii.toUpper(parsed.disk_designator[0]); result_drive_buf[0] = ascii.toUpper(parsed.disk_designator[0]);
result_disk_designator = result_drive_buf[0..]; disk_designator = result_drive_buf[0..];
have_drive_kind = WindowsPath.Kind.Drive; drive_kind = WindowsPath.Kind.Drive;
}, },
WindowsPath.Kind.NetworkShare => { .NetworkShare => {
result_disk_designator = parsed.disk_designator; disk_designator = parsed.disk_designator;
have_drive_kind = WindowsPath.Kind.NetworkShare; drive_kind = WindowsPath.Kind.NetworkShare;
}, },
WindowsPath.Kind.None => {}, .None => {},
} }
max_size += p.len + 1;
} }
// if we will result with a disk designator, loop again to determine // if we will result with a disk designator, loop again to determine
// which is the last time the disk designator is absolutely specified, if any // which is the last time the disk designator is absolutely specified, if any
// and count up the max bytes for paths related to this disk designator // and count up the max bytes for paths related to this disk designator
if (have_drive_kind != WindowsPath.Kind.None) { if (drive_kind != WindowsPath.Kind.None) {
have_abs_path = false; have_abs_path = false;
first_index = 0; first_index = 0;
max_size = result_disk_designator.len;
var correct_disk_designator = false; var correct_disk_designator = false;
for (paths) |p, i| { for (paths) |p, i| {
const parsed = windowsParsePath(p); const parsed = windowsParsePath(p);
if (parsed.kind != WindowsPath.Kind.None) { if (parsed.kind != WindowsPath.Kind.None) {
if (parsed.kind == have_drive_kind) { if (parsed.kind == drive_kind) {
correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator); correct_disk_designator = compareDiskDesignators(drive_kind, disk_designator, parsed.disk_designator);
} else { } else {
continue; continue;
} }
@ -525,92 +519,51 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
} }
if (parsed.is_abs) { if (parsed.is_abs) {
first_index = i; first_index = i;
max_size = result_disk_designator.len;
have_abs_path = true; have_abs_path = true;
} }
max_size += p.len + 1;
} }
} }
// Allocate result and fill in the disk designator, calling getCwd if we have to. // Allocate result and fill in the disk designator.
var result: []u8 = undefined; var result = std.ArrayList(u8).init(allocator);
var result_index: usize = 0; defer result.deinit();
if (have_abs_path) { const disk_designator_len: usize = l: {
switch (have_drive_kind) { if (!have_abs_path) break :l 0;
WindowsPath.Kind.Drive => { switch (drive_kind) {
result = try allocator.alloc(u8, max_size); .Drive => {
try result.appendSlice(disk_designator);
mem.copy(u8, result, result_disk_designator); break :l disk_designator.len;
result_index += result_disk_designator.len;
}, },
WindowsPath.Kind.NetworkShare => { .NetworkShare => {
result = try allocator.alloc(u8, max_size);
var it = mem.tokenize(u8, paths[first_index], "/\\"); var it = mem.tokenize(u8, paths[first_index], "/\\");
const server_name = it.next().?; const server_name = it.next().?;
const other_name = it.next().?; const other_name = it.next().?;
result[result_index] = '\\'; try result.ensureUnusedCapacity(2 + 1 + server_name.len + other_name.len);
result_index += 1; result.appendSliceAssumeCapacity("\\\\");
result[result_index] = '\\'; result.appendSliceAssumeCapacity(server_name);
result_index += 1; result.appendAssumeCapacity('\\');
mem.copy(u8, result[result_index..], server_name); result.appendSliceAssumeCapacity(other_name);
result_index += server_name.len;
result[result_index] = '\\';
result_index += 1;
mem.copy(u8, result[result_index..], other_name);
result_index += other_name.len;
result_disk_designator = result[0..result_index]; break :l result.items.len;
}, },
WindowsPath.Kind.None => { .None => {
assert(native_os == .windows); // resolveWindows called on non windows can't use getCwd break :l 1;
const cwd = try process.getCwdAlloc(allocator);
defer allocator.free(cwd);
const parsed_cwd = windowsParsePath(cwd);
result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
mem.copy(u8, result, parsed_cwd.disk_designator);
result_index += parsed_cwd.disk_designator.len;
result_disk_designator = result[0..parsed_cwd.disk_designator.len];
if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
result[0] = ascii.toUpper(result[0]);
}
have_drive_kind = parsed_cwd.kind;
}, },
} }
} else { };
assert(native_os == .windows); // resolveWindows called on non windows can't use getCwd
// TODO call get cwd for the result_disk_designator instead of the global one
const cwd = try process.getCwdAlloc(allocator);
defer allocator.free(cwd);
result = try allocator.alloc(u8, max_size + cwd.len + 1);
mem.copy(u8, result, cwd);
result_index += cwd.len;
const parsed_cwd = windowsParsePath(result[0..result_index]);
result_disk_designator = parsed_cwd.disk_designator;
if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
result[0] = ascii.toUpper(result[0]);
// Remove the trailing slash if present, eg. if the cwd is a root
// directory.
if (cwd.len > 0 and cwd[cwd.len - 1] == sep_windows) {
result_index -= 1;
}
}
have_drive_kind = parsed_cwd.kind;
}
errdefer allocator.free(result);
// Now we know the disk designator to use, if any, and what kind it is. And our result
// is big enough to append all the paths to.
var correct_disk_designator = true; var correct_disk_designator = true;
var negative_count: usize = 0;
for (paths[first_index..]) |p| { for (paths[first_index..]) |p| {
const parsed = windowsParsePath(p); const parsed = windowsParsePath(p);
if (parsed.kind != WindowsPath.Kind.None) { if (parsed.kind != .None) {
if (parsed.kind == have_drive_kind) { if (parsed.kind == drive_kind) {
correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator); const dd = result.items[0..disk_designator_len];
correct_disk_designator = compareDiskDesignators(drive_kind, dd, parsed.disk_designator);
} else { } else {
continue; continue;
} }
@ -619,154 +572,167 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
continue; continue;
} }
var it = mem.tokenize(u8, p[parsed.disk_designator.len..], "/\\"); var it = mem.tokenize(u8, p[parsed.disk_designator.len..], "/\\");
while (it.next()) |component| { component: while (it.next()) |component| {
if (mem.eql(u8, component, ".")) { if (mem.eql(u8, component, ".")) {
continue; continue;
} else if (mem.eql(u8, component, "..")) { } else if (mem.eql(u8, component, "..")) {
while (true) { while (true) {
if (result_index == 0 or result_index == result_disk_designator.len) if (result.items.len == 0) {
break; negative_count += 1;
result_index -= 1; continue :component;
if (result[result_index] == '\\' or result[result_index] == '/') }
if (result.items.len == disk_designator_len) {
break; break;
} }
const end_with_sep = switch (result.items[result.items.len - 1]) {
'\\', '/' => true,
else => false,
};
result.items.len -= 1;
if (end_with_sep) break;
}
} else if (!have_abs_path and result.items.len == 0) {
try result.appendSlice(component);
} else { } else {
result[result_index] = sep_windows; try result.ensureUnusedCapacity(1 + component.len);
result_index += 1; result.appendAssumeCapacity('\\');
mem.copy(u8, result[result_index..], component); result.appendSliceAssumeCapacity(component);
result_index += component.len;
} }
} }
} }
if (result_index == result_disk_designator.len) { if (disk_designator_len != 0 and result.items.len == disk_designator_len) {
result[result_index] = '\\'; try result.append('\\');
result_index += 1; return result.toOwnedSlice();
} }
return allocator.shrink(result, result_index); if (result.items.len == 0) {
if (negative_count == 0) {
return allocator.dupe(u8, ".");
} else {
const real_result = try allocator.alloc(u8, 3 * negative_count - 1);
var count = negative_count - 1;
var i: usize = 0;
while (count > 0) : (count -= 1) {
real_result[i..][0..3].* = "..\\".*;
i += 3;
}
real_result[i..][0..2].* = "..".*;
return real_result;
}
}
if (negative_count == 0) {
return result.toOwnedSlice();
} else {
const real_result = try allocator.alloc(u8, 3 * negative_count + result.items.len);
var count = negative_count;
var i: usize = 0;
while (count > 0) : (count -= 1) {
real_result[i..][0..3].* = "..\\".*;
i += 3;
}
mem.copy(u8, real_result[i..], result.items);
return real_result;
}
} }
/// This function is like a series of `cd` statements executed one after another. /// This function is like a series of `cd` statements executed one after another.
/// It resolves "." and "..". /// It resolves "." and "..".
/// The result does not have a trailing path separator. /// The result does not have a trailing path separator.
/// If all paths are relative it uses the current working directory as a starting point. /// This function does not perform any syscalls. Executing this series of path
/// Note: all usage of this function should be audited due to the existence of symlinks. /// lookups on the actual filesystem may produce different results due to
/// Without performing actual syscalls, resolving `..` could be incorrect. /// symlinks.
pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 { pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 {
if (paths.len == 0) { assert(paths.len > 0);
assert(native_os != .windows); // resolvePosix called on windows can't use getCwd
return process.getCwdAlloc(allocator);
}
var first_index: usize = 0; var result = std.ArrayList(u8).init(allocator);
var have_abs = false; defer result.deinit();
var max_size: usize = 0;
for (paths) |p, i| { var negative_count: usize = 0;
var is_abs = false;
for (paths) |p| {
if (isAbsolutePosix(p)) { if (isAbsolutePosix(p)) {
first_index = i; is_abs = true;
have_abs = true; negative_count = 0;
max_size = 0; result.clearRetainingCapacity();
} }
max_size += p.len + 1;
}
var result: []u8 = undefined;
var result_index: usize = 0;
if (have_abs) {
result = try allocator.alloc(u8, max_size);
} else {
assert(native_os != .windows); // resolvePosix called on windows can't use getCwd
const cwd = try process.getCwdAlloc(allocator);
defer allocator.free(cwd);
result = try allocator.alloc(u8, max_size + cwd.len + 1);
mem.copy(u8, result, cwd);
result_index += cwd.len;
}
errdefer allocator.free(result);
for (paths[first_index..]) |p| {
var it = mem.tokenize(u8, p, "/"); var it = mem.tokenize(u8, p, "/");
while (it.next()) |component| { component: while (it.next()) |component| {
if (mem.eql(u8, component, ".")) { if (mem.eql(u8, component, ".")) {
continue; continue;
} else if (mem.eql(u8, component, "..")) { } else if (mem.eql(u8, component, "..")) {
while (true) { while (true) {
if (result_index == 0) if (result.items.len == 0) {
break; negative_count += @boolToInt(!is_abs);
result_index -= 1; continue :component;
if (result[result_index] == '/')
break;
} }
const ends_with_slash = result.items[result.items.len - 1] == '/';
result.items.len -= 1;
if (ends_with_slash) break;
}
} else if (result.items.len > 0 or is_abs) {
try result.ensureUnusedCapacity(1 + component.len);
result.appendAssumeCapacity('/');
result.appendSliceAssumeCapacity(component);
} else { } else {
result[result_index] = '/'; try result.appendSlice(component);
result_index += 1;
mem.copy(u8, result[result_index..], component);
result_index += component.len;
} }
} }
} }
if (result_index == 0) { if (result.items.len == 0) {
result[0] = '/'; if (is_abs) {
result_index += 1; return allocator.dupe(u8, "/");
}
if (negative_count == 0) {
return allocator.dupe(u8, ".");
} else {
const real_result = try allocator.alloc(u8, 3 * negative_count - 1);
var count = negative_count - 1;
var i: usize = 0;
while (count > 0) : (count -= 1) {
real_result[i..][0..3].* = "../".*;
i += 3;
}
real_result[i..][0..2].* = "..".*;
return real_result;
}
} }
return allocator.shrink(result, result_index); if (negative_count == 0) {
return result.toOwnedSlice();
} else {
const real_result = try allocator.alloc(u8, 3 * negative_count + result.items.len);
var count = negative_count;
var i: usize = 0;
while (count > 0) : (count -= 1) {
real_result[i..][0..3].* = "../".*;
i += 3;
}
mem.copy(u8, real_result[i..], result.items);
return real_result;
}
} }
test "resolve" { test "resolve" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; try testResolveWindows(&[_][]const u8{ "a\\b\\c\\", "..\\..\\.." }, "..");
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); try testResolveWindows(&[_][]const u8{"."}, ".");
const cwd = try process.getCwdAlloc(testing.allocator); try testResolvePosix(&[_][]const u8{ "a/b/c/", "../../.." }, "..");
defer testing.allocator.free(cwd); try testResolvePosix(&[_][]const u8{"."}, ".");
if (native_os == .windows) {
if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
cwd[0] = ascii.toUpper(cwd[0]);
}
try testResolveWindows(&[_][]const u8{"."}, cwd);
} else {
try testResolvePosix(&[_][]const u8{ "a/b/c/", "../../.." }, cwd);
try testResolvePosix(&[_][]const u8{"."}, cwd);
}
} }
test "resolveWindows" { test "resolveWindows" {
if (builtin.target.cpu.arch == .aarch64) { try testResolveWindows(
// TODO https://github.com/ziglang/zig/issues/3288 &[_][]const u8{ "Z:\\", "/usr/local", "lib\\zig\\std\\array_list.zig" },
return error.SkipZigTest; "Z:\\usr\\local\\lib\\zig\\std\\array_list.zig",
} );
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; try testResolveWindows(
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/"); &[_][]const u8{ "z:\\", "usr/local", "lib\\zig" },
if (native_os == .windows) { "Z:\\usr\\local\\lib\\zig",
const cwd = try process.getCwdAlloc(testing.allocator); );
defer testing.allocator.free(cwd);
const parsed_cwd = windowsParsePath(cwd);
{
const expected = try join(testing.allocator, &[_][]const u8{
parsed_cwd.disk_designator,
"usr\\local\\lib\\zig\\std\\array_list.zig",
});
defer testing.allocator.free(expected);
if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
expected[0] = ascii.toUpper(parsed_cwd.disk_designator[0]);
}
try testResolveWindows(&[_][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }, expected);
}
{
const expected = try join(testing.allocator, &[_][]const u8{
cwd,
"usr\\local\\lib\\zig",
});
defer testing.allocator.free(expected);
if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
expected[0] = ascii.toUpper(parsed_cwd.disk_designator[0]);
}
try testResolveWindows(&[_][]const u8{ "usr/local", "lib\\zig" }, expected);
}
}
try testResolveWindows(&[_][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }, "C:\\hi\\ok"); try testResolveWindows(&[_][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }, "C:\\hi\\ok");
try testResolveWindows(&[_][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }, "C:\\blah\\a"); try testResolveWindows(&[_][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }, "C:\\blah\\a");
@ -781,12 +747,12 @@ test "resolveWindows" {
try testResolveWindows(&[_][]const u8{ "c:/", "//server//share" }, "\\\\server\\share\\"); try testResolveWindows(&[_][]const u8{ "c:/", "//server//share" }, "\\\\server\\share\\");
try testResolveWindows(&[_][]const u8{ "c:/", "///some//dir" }, "C:\\some\\dir"); try testResolveWindows(&[_][]const u8{ "c:/", "///some//dir" }, "C:\\some\\dir");
try testResolveWindows(&[_][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }, "C:\\foo\\tmp.3\\cycles\\root.js"); try testResolveWindows(&[_][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }, "C:\\foo\\tmp.3\\cycles\\root.js");
// Keep relative paths relative.
try testResolveWindows(&[_][]const u8{"a/b"}, "a\\b");
} }
test "resolvePosix" { test "resolvePosix" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c"); try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e"); try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
try testResolvePosix(&[_][]const u8{ "/a/b/c", "..", "../" }, "/a"); try testResolvePosix(&[_][]const u8{ "/a/b/c", "..", "../" }, "/a");
@ -797,18 +763,21 @@ test "resolvePosix" {
try testResolvePosix(&[_][]const u8{ "/var/lib", "/../", "file/" }, "/file"); try testResolvePosix(&[_][]const u8{ "/var/lib", "/../", "file/" }, "/file");
try testResolvePosix(&[_][]const u8{ "/some/dir", ".", "/absolute/" }, "/absolute"); try testResolvePosix(&[_][]const u8{ "/some/dir", ".", "/absolute/" }, "/absolute");
try testResolvePosix(&[_][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }, "/foo/tmp.3/cycles/root.js"); try testResolvePosix(&[_][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }, "/foo/tmp.3/cycles/root.js");
// Keep relative paths relative.
try testResolvePosix(&[_][]const u8{"a/b"}, "a/b");
} }
fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void { fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void {
const actual = try resolveWindows(testing.allocator, paths); const actual = try resolveWindows(testing.allocator, paths);
defer testing.allocator.free(actual); defer testing.allocator.free(actual);
try testing.expect(mem.eql(u8, actual, expected)); try testing.expectEqualStrings(expected, actual);
} }
fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void {
const actual = try resolvePosix(testing.allocator, paths); const actual = try resolvePosix(testing.allocator, paths);
defer testing.allocator.free(actual); defer testing.allocator.free(actual);
try testing.expect(mem.eql(u8, actual, expected)); try testing.expectEqualStrings(expected, actual);
} }
/// Strip the last component from a file path. /// Strip the last component from a file path.
@ -1089,13 +1058,15 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
if (parsed_from.kind != parsed_to.kind) { if (parsed_from.kind != parsed_to.kind) {
break :x true; break :x true;
} else switch (parsed_from.kind) { } else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare => { .NetworkShare => {
break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator); break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
}, },
WindowsPath.Kind.Drive => { .Drive => {
break :x ascii.toUpper(parsed_from.disk_designator[0]) != ascii.toUpper(parsed_to.disk_designator[0]); break :x ascii.toUpper(parsed_from.disk_designator[0]) != ascii.toUpper(parsed_to.disk_designator[0]);
}, },
else => unreachable, .None => {
break :x false;
},
} }
}; };
@ -1194,13 +1165,6 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]
} }
test "relative" { test "relative" {
if (builtin.target.cpu.arch == .aarch64) {
// TODO https://github.com/ziglang/zig/issues/3288
return error.SkipZigTest;
}
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
@ -1226,6 +1190,10 @@ test "relative" {
try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"); try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
try testRelativeWindows("a/b/c", "a\\b", "..");
try testRelativeWindows("a/b/c", "a", "..\\..");
try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");
try testRelativePosix("/var/lib", "/var", ".."); try testRelativePosix("/var/lib", "/var", "..");
try testRelativePosix("/var/lib", "/bin", "../../bin"); try testRelativePosix("/var/lib", "/bin", "../../bin");
try testRelativePosix("/var/lib", "/var/lib", ""); try testRelativePosix("/var/lib", "/var/lib", "");
@ -1243,13 +1211,13 @@ test "relative" {
fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void { fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {
const result = try relativePosix(testing.allocator, from, to); const result = try relativePosix(testing.allocator, from, to);
defer testing.allocator.free(result); defer testing.allocator.free(result);
try testing.expectEqualSlices(u8, expected_output, result); try testing.expectEqualStrings(expected_output, result);
} }
fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void { fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {
const result = try relativeWindows(testing.allocator, from, to); const result = try relativeWindows(testing.allocator, from, to);
defer testing.allocator.free(result); defer testing.allocator.free(result);
try testing.expectEqualSlices(u8, expected_output, result); try testing.expectEqualStrings(expected_output, result);
} }
/// Returns the extension of the file name (if any). /// Returns the extension of the file name (if any).

View File

@ -1095,7 +1095,9 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
const allocator = testing.allocator; const allocator = testing.allocator;
const file_paths: [1][]const u8 = .{"zig-test-absolute-paths.txt"}; const cwd = try std.process.getCwdAlloc(allocator);
defer allocator.free(cwd);
const file_paths: [2][]const u8 = .{ cwd, "zig-test-absolute-paths.txt" };
const filename = try fs.path.resolve(allocator, &file_paths); const filename = try fs.path.resolve(allocator, &file_paths);
defer allocator.free(filename); defer allocator.free(filename);

View File

@ -202,10 +202,7 @@ pub const PreopenList = struct {
// POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "." // POSIX paths, relative to "/" or `cwd_root` depending on whether they start with "."
const path = if (cwd_root) |cwd| blk: { const path = if (cwd_root) |cwd| blk: {
const resolve_paths: []const []const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path }; const resolve_paths: []const []const u8 = if (raw_path[0] == '.') &.{ cwd, raw_path } else &.{ "/", raw_path };
break :blk fs.path.resolve(self.buffer.allocator, resolve_paths) catch |err| switch (err) { break :blk try fs.path.resolve(self.buffer.allocator, resolve_paths);
error.CurrentWorkingDirectoryUnlinked => unreachable, // root is absolute, so CWD not queried
else => |e| return e,
};
} else blk: { } else blk: {
// If we were provided no CWD root, we preserve the preopen dir without resolving // If we were provided no CWD root, we preserve the preopen dir without resolving
break :blk try self.buffer.allocator.dupe(u8, raw_path); break :blk try self.buffer.allocator.dupe(u8, raw_path);