std: fix path joining on UEFI

UEFI uses `\` for paths exclusively. This changes std.fs.path to use `\`
for UEFI path joining. Also adds a few tests regarding it, specifically
in making sure double-separators do not result from path joining, as the
UEFI spec says to convert any that result from joining into single
separators (UEFI Spec Version 2.7, pg. 448).
This commit is contained in:
fifty-six 2022-01-11 01:05:41 -05:00 committed by Andrew Kelley
parent f4b3f1d602
commit 41fd343508

View File

@ -12,13 +12,13 @@ const fs = std.fs;
const process = std.process;
const native_os = builtin.target.os.tag;
pub const sep_windows = '\\';
pub const sep_windows_uefi = '\\';
pub const sep_posix = '/';
pub const sep = if (native_os == .windows) sep_windows else sep_posix;
pub const sep = if (native_os == .windows or native_os == .uefi) sep_windows_uefi else sep_posix;
pub const sep_str_windows = "\\";
pub const sep_str_windows_uefi = "\\";
pub const sep_str_posix = "/";
pub const sep_str = if (native_os == .windows) sep_str_windows else sep_str_posix;
pub const sep_str = if (native_os == .windows or native_os == .uefi) sep_str_windows_uefi else sep_str_posix;
pub const delimiter_windows = ';';
pub const delimiter_posix = ':';
@ -26,11 +26,11 @@ pub const delimiter = if (native_os == .windows) delimiter_windows else delimite
/// Returns if the given byte is a valid path separator
pub fn isSep(byte: u8) bool {
if (native_os == .windows) {
return byte == '/' or byte == '\\';
} else {
return byte == '/';
}
return switch (native_os) {
.windows => byte == '/' or byte == '\\',
.uefi => byte == '\\',
else => byte == '/',
};
}
/// This is different from mem.join in that the separator will not be repeated if
@ -110,13 +110,24 @@ pub fn joinZ(allocator: Allocator, paths: []const []const u8) ![:0]u8 {
return out[0 .. out.len - 1 :0];
}
fn testJoinMaybeZUefi(paths: []const []const u8, expected: []const u8, zero: bool) !void {
const uefiIsSep = struct {
fn isSep(byte: u8) bool {
return byte == '\\';
}
}.isSep;
const actual = try joinSepMaybeZ(testing.allocator, sep_windows_uefi, uefiIsSep, paths, zero);
defer testing.allocator.free(actual);
try testing.expectEqualSlices(u8, expected, if (zero) actual[0 .. actual.len - 1 :0] else actual);
}
fn testJoinMaybeZWindows(paths: []const []const u8, expected: []const u8, zero: bool) !void {
const windowsIsSep = struct {
fn isSep(byte: u8) bool {
return byte == '/' or byte == '\\';
}
}.isSep;
const actual = try joinSepMaybeZ(testing.allocator, sep_windows, windowsIsSep, paths, zero);
const actual = try joinSepMaybeZ(testing.allocator, sep_windows_uefi, windowsIsSep, paths, zero);
defer testing.allocator.free(actual);
try testing.expectEqualSlices(u8, expected, if (zero) actual[0 .. actual.len - 1 :0] else actual);
}
@ -158,6 +169,11 @@ test "join" {
zero,
);
try testJoinMaybeZUefi(&[_][]const u8{ "EFI", "Boot", "bootx64.efi" }, "EFI\\Boot\\bootx64.efi", zero);
try testJoinMaybeZUefi(&[_][]const u8{ "EFI\\Boot", "bootx64.efi" }, "EFI\\Boot\\bootx64.efi", zero);
try testJoinMaybeZUefi(&[_][]const u8{ "EFI\\", "\\Boot", "bootx64.efi" }, "EFI\\Boot\\bootx64.efi", zero);
try testJoinMaybeZUefi(&[_][]const u8{ "EFI\\", "\\Boot\\", "\\bootx64.efi" }, "EFI\\Boot\\bootx64.efi", zero);
try testJoinMaybeZWindows(&[_][]const u8{ "c:\\", "a", "b/", "c" }, "c:\\a\\b/c", zero);
try testJoinMaybeZWindows(&[_][]const u8{ "c:\\a/", "b\\", "/c" }, "c:\\a/b\\c", zero);
@ -588,7 +604,7 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
result[0] = asciiUpper(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) {
if (cwd.len > 0 and cwd[cwd.len - 1] == sep_windows_uefi) {
result_index -= 1;
}
}
@ -625,7 +641,7 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) ![]u8 {
break;
}
} else {
result[result_index] = sep_windows;
result[result_index] = sep_windows_uefi;
result_index += 1;
mem.copy(u8, result[result_index..], component);
result_index += component.len;