fs tests: Test multiple different path types in most tests

(which path types will depend on which the target supports)
This commit is contained in:
Ryan Liptak 2023-08-17 00:58:44 -07:00
parent 3819e69376
commit 8f5f1ff25a
2 changed files with 529 additions and 367 deletions

View File

@ -13,38 +13,167 @@ const File = std.fs.File;
const tmpDir = testing.tmpDir; const tmpDir = testing.tmpDir;
const tmpIterableDir = testing.tmpIterableDir; const tmpIterableDir = testing.tmpIterableDir;
const PathType = enum {
relative,
absolute,
unc,
pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
return switch (self) {
.relative => true,
.absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
.unc => target_os.tag == .windows,
};
}
pub const TransformError = std.os.RealPathError || error{OutOfMemory};
pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8;
pub fn getTransformFn(comptime path_type: PathType) TransformFn {
switch (path_type) {
.relative => return struct {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
_ = allocator;
_ = dir;
return relative_path;
}
}.transform,
.absolute => return struct {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
// The final path may not actually exist which would cause realpath to fail.
// So instead, we get the path of the dir and join it with the relative path.
var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
return fs.path.join(allocator, &.{ dir_path, relative_path });
}
}.transform,
.unc => return struct {
fn transform(allocator: mem.Allocator, dir: Dir, relative_path: []const u8) TransformError![]const u8 {
// Any drive absolute path (C:\foo) can be converted into a UNC path by
// using 'localhost' as the server name and '<drive letter>$' as the share name.
var fd_path_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const dir_path = try os.getFdPath(dir.fd, &fd_path_buf);
const windows_path_type = std.os.windows.getUnprefixedPathType(u8, dir_path);
switch (windows_path_type) {
.unc_absolute => return fs.path.join(allocator, &.{ dir_path, relative_path }),
.drive_absolute => {
// `C:\<...>` -> `\\localhost\C$\<...>`
const prepended = "\\\\localhost\\";
var path = try fs.path.join(allocator, &.{ prepended, dir_path, relative_path });
path[prepended.len + 1] = '$';
return path;
},
else => unreachable,
}
}
}.transform,
}
}
};
const TestContext = struct {
path_type: PathType,
arena: ArenaAllocator,
tmp: testing.TmpIterableDir,
dir: std.fs.Dir,
iterable_dir: std.fs.IterableDir,
transform_fn: *const PathType.TransformFn,
pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
var tmp = tmpIterableDir(.{});
return .{
.path_type = path_type,
.arena = ArenaAllocator.init(allocator),
.tmp = tmp,
.dir = tmp.iterable_dir.dir,
.iterable_dir = tmp.iterable_dir,
.transform_fn = transform_fn,
};
}
pub fn deinit(self: *TestContext) void {
self.arena.deinit();
self.tmp.cleanup();
}
/// Returns the `relative_path` transformed into the TestContext's `path_type`.
/// The result is allocated by the TestContext's arena and will be free'd during
/// `TestContext.deinit`.
pub fn transformPath(self: *TestContext, relative_path: []const u8) ![]const u8 {
return self.transform_fn(self.arena.allocator(), self.dir, relative_path);
}
};
/// `test_func` must be a function that takes a `*TestContext` as a parameter and returns `!void`.
/// `test_func` will be called once for each PathType that the current target supports,
/// and will be passed a TestContext that can transform a relative path into the path type under test.
/// The TestContext will also create a tmp directory for you (and will clean it up for you too).
fn testWithAllSupportedPathTypes(test_func: anytype) !void {
inline for (@typeInfo(PathType).Enum.fields) |enum_field| {
const path_type = @field(PathType, enum_field.name);
if (!(comptime path_type.isSupported(builtin.os))) continue;
var ctx = TestContext.init(path_type, testing.allocator, path_type.getTransformFn());
defer ctx.deinit();
test_func(&ctx) catch |err| {
std.debug.print("path type: {s}\n", .{enum_field.name});
return err;
};
}
}
test "Dir.readLink" { test "Dir.readLink" {
var tmp = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp.cleanup(); fn impl(ctx: *TestContext) !void {
// Create some targets // Create some targets
try tmp.dir.writeFile("file.txt", "nonsense"); const file_target_path = try ctx.transformPath("file.txt");
try tmp.dir.makeDir("subdir"); try ctx.dir.writeFile(file_target_path, "nonsense");
const dir_target_path = try ctx.transformPath("subdir");
try ctx.dir.makeDir(dir_target_path);
{ {
// Create symbolic link by path // Create symbolic link by path
tmp.dir.symLink("file.txt", "symlink1", .{}) catch |err| switch (err) { ctx.dir.symLink(file_target_path, "symlink1", .{}) catch |err| switch (err) {
// Symlink requires admin privileges on windows, so this test can legitimately fail. // Symlink requires admin privileges on windows, so this test can legitimately fail.
error.AccessDenied => return error.SkipZigTest, error.AccessDenied => return error.SkipZigTest,
else => return err, else => return err,
}; };
try testReadLink(tmp.dir, "file.txt", "symlink1"); try testReadLink(ctx.dir, file_target_path, "symlink1");
} }
{ {
// Create symbolic link by path // Create symbolic link by path
tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }) catch |err| switch (err) { ctx.dir.symLink(dir_target_path, "symlink2", .{ .is_directory = true }) catch |err| switch (err) {
// Symlink requires admin privileges on windows, so this test can legitimately fail. // Symlink requires admin privileges on windows, so this test can legitimately fail.
error.AccessDenied => return error.SkipZigTest, error.AccessDenied => return error.SkipZigTest,
else => return err, else => return err,
}; };
try testReadLink(tmp.dir, "subdir", "symlink2"); try testReadLink(ctx.dir, dir_target_path, "symlink2");
} }
}
}.impl);
} }
fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
const given = try dir.readLink(symlink_path, buffer[0..]); const given = try dir.readLink(symlink_path, buffer[0..]);
try testing.expect(mem.eql(u8, target_path, given)); try testing.expectEqualStrings(target_path, given);
}
test "openDir" {
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const subdir_path = try ctx.transformPath("subdir");
try ctx.dir.makeDir(subdir_path);
for ([_][]const u8{ "", ".", ".." }) |sub_path| {
const dir_path = try fs.path.join(testing.allocator, &[_][]const u8{ subdir_path, sub_path });
defer testing.allocator.free(dir_path);
var dir = try ctx.dir.openDir(dir_path, .{});
defer dir.close();
}
}
}.impl);
} }
test "accessAbsolute" { test "accessAbsolute" {
@ -349,53 +478,59 @@ fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.En
} }
test "Dir.realpath smoke test" { test "Dir.realpath smoke test" {
switch (builtin.os.tag) { if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
.linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {},
else => return error.SkipZigTest,
}
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_file_path = try ctx.transformPath("test_file");
const test_dir_path = try ctx.transformPath("test_dir");
var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
var file = try tmp_dir.dir.createFile("test_file", .{ .lock = .shared }); // FileNotFound if the path doesn't exist
// We need to close the file immediately as otherwise on Windows we'll end up try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_file_path));
// with a sharing violation. try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
file.close(); try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(testing.allocator, test_dir_path));
try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
try tmp_dir.dir.makeDir("test_dir"); // Now create the file and dir
try ctx.dir.writeFile(test_file_path, "");
try ctx.dir.makeDir(test_dir_path);
var arena = ArenaAllocator.init(testing.allocator); const base_path = try ctx.transformPath(".");
defer arena.deinit(); const base_realpath = try ctx.dir.realpathAlloc(testing.allocator, base_path);
const allocator = arena.allocator(); defer testing.allocator.free(base_realpath);
const expected_file_path = try fs.path.join(
const base_path = blk: { testing.allocator,
const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] }); &[_][]const u8{ base_realpath, "test_file" },
break :blk try fs.realpathAlloc(allocator, relative_path); );
}; defer testing.allocator.free(expected_file_path);
const expected_dir_path = try fs.path.join(
testing.allocator,
&[_][]const u8{ base_realpath, "test_dir" },
);
defer testing.allocator.free(expected_dir_path);
// First, test non-alloc version // First, test non-alloc version
{ {
var buf1: [fs.MAX_PATH_BYTES]u8 = undefined; const file_path = try ctx.dir.realpath(test_file_path, &buf);
const file_path = try tmp_dir.dir.realpath("test_file", buf1[0..]);
const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" });
try testing.expectEqualStrings(expected_file_path, file_path); try testing.expectEqualStrings(expected_file_path, file_path);
const dir_path = try tmp_dir.dir.realpath("test_dir", buf1[0..]); const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" });
try testing.expectEqualStrings(expected_dir_path, dir_path); try testing.expectEqualStrings(expected_dir_path, dir_path);
} }
// Next, test alloc version // Next, test alloc version
{ {
const file_path = try tmp_dir.dir.realpathAlloc(allocator, "test_file"); const file_path = try ctx.dir.realpathAlloc(testing.allocator, test_file_path);
const expected_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_file" }); defer testing.allocator.free(file_path);
try testing.expectEqualStrings(expected_file_path, file_path); try testing.expectEqualStrings(expected_file_path, file_path);
const dir_path = try tmp_dir.dir.realpathAlloc(allocator, "test_dir"); const dir_path = try ctx.dir.realpathAlloc(testing.allocator, test_dir_path);
const expected_dir_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "test_dir" }); defer testing.allocator.free(dir_path);
try testing.expectEqualStrings(expected_dir_path, dir_path); try testing.expectEqualStrings(expected_dir_path, dir_path);
} }
}
}.impl);
} }
test "readAllAlloc" { test "readAllAlloc" {
@ -432,49 +567,43 @@ test "readAllAlloc" {
} }
test "directory operations on files" { test "directory operations on files" {
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_file_name = try ctx.transformPath("test_file");
const test_file_name = "test_file"; var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
file.close(); file.close();
try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.makeDir(test_file_name)); try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
try testing.expectError(error.NotDir, tmp_dir.dir.openDir(test_file_name, .{})); try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
try testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name)); try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
switch (builtin.os.tag) { if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
.wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {}, try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
else => { try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_file_name);
defer testing.allocator.free(absolute_path);
try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path));
try testing.expectError(error.NotDir, fs.deleteDirAbsolute(absolute_path));
},
} }
// ensure the file still exists and is a file as a sanity check // ensure the file still exists and is a file as a sanity check
file = try tmp_dir.dir.openFile(test_file_name, .{}); file = try ctx.dir.openFile(test_file_name, .{});
const stat = try file.stat(); const stat = try file.stat();
try testing.expect(stat.kind == .file); try testing.expect(stat.kind == .file);
file.close(); file.close();
}
}.impl);
} }
test "file operations on directories" { test "file operations on directories" {
// TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
if (builtin.os.tag == .freebsd) return error.SkipZigTest; if (builtin.os.tag == .freebsd) return error.SkipZigTest;
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_dir_name = try ctx.transformPath("test_dir");
const test_dir_name = "test_dir"; try ctx.dir.makeDir(test_dir_name);
try tmp_dir.dir.makeDir(test_dir_name); try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
try testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{}));
try testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name));
switch (builtin.os.tag) { switch (builtin.os.tag) {
// no error when reading a directory. // no error when reading a directory.
.dragonfly, .netbsd => {}, .dragonfly, .netbsd => {},
@ -482,161 +611,177 @@ test "file operations on directories" {
// TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved. // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved.
.wasi => {}, .wasi => {},
else => { else => {
try testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
}, },
} }
// Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
// TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
try testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .mode = .read_write })); try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
switch (builtin.os.tag) { if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
.wasi, .freebsd, .netbsd, .openbsd, .dragonfly => {}, try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
else => { try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
const absolute_path = try tmp_dir.dir.realpathAlloc(testing.allocator, test_dir_name);
defer testing.allocator.free(absolute_path);
try testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{}));
try testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path));
},
} }
// ensure the directory still exists as a sanity check // ensure the directory still exists as a sanity check
var dir = try tmp_dir.dir.openDir(test_dir_name, .{}); var dir = try ctx.dir.openDir(test_dir_name, .{});
dir.close(); dir.close();
}
}.impl);
} }
test "deleteDir" { test "deleteDir" {
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_dir_path = try ctx.transformPath("test_dir");
const test_file_path = try ctx.transformPath("test_dir" ++ std.fs.path.sep_str ++ "test_file");
// deleting a non-existent directory // deleting a non-existent directory
try testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir")); try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});
var file = try dir.createFile("test_file", .{});
file.close();
dir.close();
// deleting a non-empty directory // deleting a non-empty directory
try testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir")); try ctx.dir.makeDir(test_dir_path);
try ctx.dir.writeFile(test_file_path, "");
dir = try tmp_dir.dir.openDir("test_dir", .{}); try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
try dir.deleteFile("test_file");
dir.close();
// deleting an empty directory // deleting an empty directory
try tmp_dir.dir.deleteDir("test_dir"); try ctx.dir.deleteFile(test_file_path);
try ctx.dir.deleteDir(test_dir_path);
}
}.impl);
} }
test "Dir.rename files" { test "Dir.rename files" {
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const missing_file_path = try ctx.transformPath("missing_file_name");
const something_else_path = try ctx.transformPath("something_else");
try testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else")); try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
// Renaming files // Renaming files
const test_file_name = "test_file"; const test_file_name = try ctx.transformPath("test_file");
const renamed_test_file_name = "test_file_renamed"; const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
file.close(); file.close();
try tmp_dir.dir.rename(test_file_name, renamed_test_file_name); try ctx.dir.rename(test_file_name, renamed_test_file_name);
// Ensure the file was renamed // Ensure the file was renamed
try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); file = try ctx.dir.openFile(renamed_test_file_name, .{});
file.close(); file.close();
// Rename to self succeeds // Rename to self succeeds
try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name); try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
// Rename to existing file succeeds // Rename to existing file succeeds
var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true }); const existing_file_path = try ctx.transformPath("existing_file");
var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
existing_file.close(); existing_file.close();
try tmp_dir.dir.rename(renamed_test_file_name, "existing_file"); try ctx.dir.rename(renamed_test_file_name, existing_file_path);
try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{})); try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{}));
file = try tmp_dir.dir.openFile("existing_file", .{}); file = try ctx.dir.openFile(existing_file_path, .{});
file.close(); file.close();
}
}.impl);
} }
test "Dir.rename directories" { test "Dir.rename directories" {
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_dir_path = try ctx.transformPath("test_dir");
const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
// Renaming directories // Renaming directories
try tmp_dir.dir.makeDir("test_dir"); try ctx.dir.makeDir(test_dir_path);
try tmp_dir.dir.rename("test_dir", "test_dir_renamed"); try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
// Ensure the directory was renamed // Ensure the directory was renamed
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{})); try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{}); var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
// Put a file in the directory // Put a file in the directory
var file = try dir.createFile("test_file", .{ .read = true }); var file = try dir.createFile("test_file", .{ .read = true });
file.close(); file.close();
dir.close(); dir.close();
try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again"); const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
// Ensure the directory was renamed and the file still exists in it // Ensure the directory was renamed and the file still exists in it
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{})); try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});
file = try dir.openFile("test_file", .{}); file = try dir.openFile("test_file", .{});
file.close(); file.close();
dir.close(); dir.close();
}
}.impl);
} }
test "Dir.rename directory onto empty dir" { test "Dir.rename directory onto empty dir" {
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
if (builtin.os.tag == .windows) return error.SkipZigTest; if (builtin.os.tag == .windows) return error.SkipZigTest;
var tmp_dir = testing.tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_dir_path = try ctx.transformPath("test_dir");
const target_dir_path = try ctx.transformPath("target_dir_path");
try tmp_dir.dir.makeDir("test_dir"); try ctx.dir.makeDir(test_dir_path);
try tmp_dir.dir.makeDir("target_dir"); try ctx.dir.makeDir(target_dir_path);
try tmp_dir.dir.rename("test_dir", "target_dir"); try ctx.dir.rename(test_dir_path, target_dir_path);
// Ensure the directory was renamed // Ensure the directory was renamed
try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{})); try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
var dir = try tmp_dir.dir.openDir("target_dir", .{}); var dir = try ctx.dir.openDir(target_dir_path, .{});
dir.close(); dir.close();
}
}.impl);
} }
test "Dir.rename directory onto non-empty dir" { test "Dir.rename directory onto non-empty dir" {
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
if (builtin.os.tag == .windows) return error.SkipZigTest; if (builtin.os.tag == .windows) return error.SkipZigTest;
var tmp_dir = testing.tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_dir_path = try ctx.transformPath("test_dir");
const target_dir_path = try ctx.transformPath("target_dir_path");
try tmp_dir.dir.makeDir("test_dir"); try ctx.dir.makeDir(test_dir_path);
var target_dir = try tmp_dir.dir.makeOpenPath("target_dir", .{}); var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
var file = try target_dir.createFile("test_file", .{ .read = true }); var file = try target_dir.createFile("test_file", .{ .read = true });
file.close(); file.close();
target_dir.close(); target_dir.close();
// Rename should fail with PathAlreadyExists if target_dir is non-empty // Rename should fail with PathAlreadyExists if target_dir is non-empty
try testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir", "target_dir")); try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
// Ensure the directory was not renamed // Ensure the directory was not renamed
var dir = try tmp_dir.dir.openDir("test_dir", .{}); var dir = try ctx.dir.openDir(test_dir_path, .{});
dir.close(); dir.close();
}
}.impl);
} }
test "Dir.rename file <-> dir" { test "Dir.rename file <-> dir" {
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
if (builtin.os.tag == .windows) return error.SkipZigTest; if (builtin.os.tag == .windows) return error.SkipZigTest;
var tmp_dir = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp_dir.cleanup(); fn impl(ctx: *TestContext) !void {
const test_file_path = try ctx.transformPath("test_file");
const test_dir_path = try ctx.transformPath("test_dir");
var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
file.close(); file.close();
try tmp_dir.dir.makeDir("test_dir"); try ctx.dir.makeDir(test_dir_path);
try testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir")); try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
try testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file")); try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
}
}.impl);
} }
test "rename" { test "rename" {
@ -720,35 +865,33 @@ test "openSelfExe" {
} }
test "makePath, put some files in it, deleteTree" { test "makePath, put some files in it, deleteTree" {
var tmp = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp.cleanup(); fn impl(ctx: *TestContext) !void {
const dir_path = try ctx.transformPath("os_test_tmp");
try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); try ctx.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
try tmp.dir.deleteTree("os_test_tmp");
if (tmp.dir.openDir("os_test_tmp", .{})) |dir| { try ctx.dir.deleteTree(dir_path);
_ = dir; try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
@panic("expected error");
} else |err| {
try testing.expect(err == error.FileNotFound);
} }
}.impl);
} }
test "makePath, put some files in it, deleteTreeMinStackSize" { test "makePath, put some files in it, deleteTreeMinStackSize" {
var tmp = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp.cleanup(); fn impl(ctx: *TestContext) !void {
const dir_path = try ctx.transformPath("os_test_tmp");
try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); try ctx.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); try ctx.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
try tmp.dir.deleteTreeMinStackSize("os_test_tmp");
if (tmp.dir.openDir("os_test_tmp", .{})) |dir| { try ctx.dir.deleteTreeMinStackSize(dir_path);
_ = dir; try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
@panic("expected error");
} else |err| {
try testing.expect(err == error.FileNotFound);
} }
}.impl);
} }
test "makePath in a directory that no longer exists" { test "makePath in a directory that no longer exists" {
@ -789,9 +932,9 @@ test "max file name component lengths" {
defer tmp.cleanup(); defer tmp.cleanup();
if (builtin.os.tag == .windows) { if (builtin.os.tag == .windows) {
// is the character with the largest codepoint that is encoded as a single u16 in UTF-16, // U+FFFF is the character with the largest code point that is encoded as a single
// so Windows allows for NAME_MAX of them // UTF-16 code unit, so Windows allows for NAME_MAX of them.
const maxed_windows_filename = ("".*) ** std.os.windows.NAME_MAX; const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;
try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename); try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);
} else if (builtin.os.tag == .wasi) { } else if (builtin.os.tag == .wasi) {
// On WASI, the maxed filename depends on the host OS, so in order for this test to // On WASI, the maxed filename depends on the host OS, so in order for this test to
@ -889,20 +1032,19 @@ test "pwritev, preadv" {
} }
test "access file" { test "access file" {
var tmp = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp.cleanup(); fn impl(ctx: *TestContext) !void {
const dir_path = try ctx.transformPath("os_test_tmp");
const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt");
try tmp.dir.makePath("os_test_tmp"); try ctx.dir.makePath(dir_path);
if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
_ = ok;
@panic("expected error"); try ctx.dir.writeFile(file_path, "");
} else |err| { try ctx.dir.access(file_path, .{});
try testing.expect(err == error.FileNotFound); try ctx.dir.deleteTree(dir_path);
} }
}.impl);
try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{});
try tmp.dir.deleteTree("os_test_tmp");
} }
test "sendfile" { test "sendfile" {
@ -996,26 +1138,27 @@ test "copyRangeAll" {
try testing.expect(mem.eql(u8, written_buf[0..amt], data)); try testing.expect(mem.eql(u8, written_buf[0..amt], data));
} }
test "fs.copyFile" { test "copyFile" {
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
const src_file = "tmp_test_copy_file.txt"; const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
const dest_file = "tmp_test_copy_file2.txt"; const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
const dest_file2 = "tmp_test_copy_file3.txt"; const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
var tmp = tmpDir(.{}); try ctx.dir.writeFile(src_file, data);
defer tmp.cleanup(); defer ctx.dir.deleteFile(src_file) catch {};
try tmp.dir.writeFile(src_file, data); try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
defer tmp.dir.deleteFile(src_file) catch {}; defer ctx.dir.deleteFile(dest_file) catch {};
try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{}); try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
defer tmp.dir.deleteFile(dest_file) catch {}; defer ctx.dir.deleteFile(dest_file2) catch {};
try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode }); try expectFileContents(ctx.dir, dest_file, data);
defer tmp.dir.deleteFile(dest_file2) catch {}; try expectFileContents(ctx.dir, dest_file2, data);
}
try expectFileContents(tmp.dir, dest_file, data); }.impl);
try expectFileContents(tmp.dir, dest_file2, data);
} }
fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
@ -1026,78 +1169,75 @@ fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
} }
test "AtomicFile" { test "AtomicFile" {
const test_out_file = "tmp_atomic_file_test_dest.txt"; try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
const test_content = const test_content =
\\ hello! \\ hello!
\\ this is a test file \\ this is a test file
; ;
var tmp = tmpDir(.{});
defer tmp.cleanup();
{ {
var af = try tmp.dir.atomicFile(test_out_file, .{}); var af = try ctx.dir.atomicFile(test_out_file, .{});
defer af.deinit(); defer af.deinit();
try af.file.writeAll(test_content); try af.file.writeAll(test_content);
try af.finish(); try af.finish();
} }
const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999); const content = try ctx.dir.readFileAlloc(testing.allocator, test_out_file, 9999);
defer testing.allocator.free(content); defer testing.allocator.free(content);
try testing.expect(mem.eql(u8, content, test_content)); try testing.expect(mem.eql(u8, content, test_content));
try tmp.dir.deleteFile(test_out_file); try ctx.dir.deleteFile(test_out_file);
} }
}.impl);
test "realpath" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
try testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf));
} }
test "open file with exclusive nonblocking lock twice" { test "open file with exclusive nonblocking lock twice" {
if (builtin.os.tag == .wasi) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest;
const filename = "file_nonblocking_lock_test.txt"; try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
var tmp = tmpDir(.{}); const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
defer tmp.cleanup();
const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
defer file1.close(); defer file1.close();
const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
try testing.expectError(error.WouldBlock, file2); try testing.expectError(error.WouldBlock, file2);
}
}.impl);
} }
test "open file with shared and exclusive nonblocking lock" { test "open file with shared and exclusive nonblocking lock" {
if (builtin.os.tag == .wasi) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest;
const filename = "file_nonblocking_lock_test.txt"; try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
var tmp = tmpDir(.{}); const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
defer tmp.cleanup();
const file1 = try tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
defer file1.close(); defer file1.close();
const file2 = tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true }); const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
try testing.expectError(error.WouldBlock, file2); try testing.expectError(error.WouldBlock, file2);
}
}.impl);
} }
test "open file with exclusive and shared nonblocking lock" { test "open file with exclusive and shared nonblocking lock" {
if (builtin.os.tag == .wasi) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest;
const filename = "file_nonblocking_lock_test.txt"; try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
var tmp = tmpDir(.{}); const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
defer tmp.cleanup();
const file1 = try tmp.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
defer file1.close(); defer file1.close();
const file2 = tmp.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true }); const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
try testing.expectError(error.WouldBlock, file2); try testing.expectError(error.WouldBlock, file2);
}
}.impl);
} }
test "open file with exclusive lock twice, make sure second lock waits" { test "open file with exclusive lock twice, make sure second lock waits" {
@ -1108,18 +1248,17 @@ test "open file with exclusive lock twice, make sure second lock waits" {
return error.SkipZigTest; return error.SkipZigTest;
} }
const filename = "file_lock_test.txt"; try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
const filename = try ctx.transformPath("file_lock_test.txt");
var tmp = tmpDir(.{}); const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
defer tmp.cleanup();
const file = try tmp.dir.createFile(filename, .{ .lock = .exclusive });
errdefer file.close(); errdefer file.close();
const S = struct { const S = struct {
fn checkFn(dir: *fs.Dir, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
started.set(); started.set();
const file1 = try dir.createFile(filename, .{ .lock = .exclusive }); const file1 = try dir.createFile(path, .{ .lock = .exclusive });
locked.set(); locked.set();
file1.close(); file1.close();
@ -1130,7 +1269,8 @@ test "open file with exclusive lock twice, make sure second lock waits" {
var locked = std.Thread.ResetEvent{}; var locked = std.Thread.ResetEvent{};
const t = try std.Thread.spawn(.{}, S.checkFn, .{ const t = try std.Thread.spawn(.{}, S.checkFn, .{
&tmp.dir, &ctx.dir,
filename,
&started, &started,
&locked, &locked,
}); });
@ -1144,6 +1284,8 @@ test "open file with exclusive lock twice, make sure second lock waits" {
// Release the file lock which should unlock the thread to lock it and set the locked event. // Release the file lock which should unlock the thread to lock it and set the locked event.
file.close(); file.close();
locked.wait(); locked.wait();
}
}.impl);
} }
test "open file with exclusive nonblocking lock twice (absolute paths)" { test "open file with exclusive nonblocking lock twice (absolute paths)" {
@ -1259,29 +1401,36 @@ test "walker without fully iterating" {
test ". and .. in fs.Dir functions" { test ". and .. in fs.Dir functions" {
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest; if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
var tmp = tmpDir(.{}); try testWithAllSupportedPathTypes(struct {
defer tmp.cleanup(); fn impl(ctx: *TestContext) !void {
const subdir_path = try ctx.transformPath("./subdir");
const file_path = try ctx.transformPath("./subdir/../file");
const copy_path = try ctx.transformPath("./subdir/../copy");
const rename_path = try ctx.transformPath("./subdir/../rename");
const update_path = try ctx.transformPath("./subdir/../update");
try tmp.dir.makeDir("./subdir"); try ctx.dir.makeDir(subdir_path);
try tmp.dir.access("./subdir", .{}); try ctx.dir.access(subdir_path, .{});
var created_subdir = try tmp.dir.openDir("./subdir", .{}); var created_subdir = try ctx.dir.openDir(subdir_path, .{});
created_subdir.close(); created_subdir.close();
const created_file = try tmp.dir.createFile("./subdir/../file", .{}); const created_file = try ctx.dir.createFile(file_path, .{});
created_file.close(); created_file.close();
try tmp.dir.access("./subdir/../file", .{}); try ctx.dir.access(file_path, .{});
try tmp.dir.copyFile("./subdir/../file", tmp.dir, "./subdir/../copy", .{}); try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
try tmp.dir.rename("./subdir/../copy", "./subdir/../rename"); try ctx.dir.rename(copy_path, rename_path);
const renamed_file = try tmp.dir.openFile("./subdir/../rename", .{}); const renamed_file = try ctx.dir.openFile(rename_path, .{});
renamed_file.close(); renamed_file.close();
try tmp.dir.deleteFile("./subdir/../rename"); try ctx.dir.deleteFile(rename_path);
try tmp.dir.writeFile("./subdir/../update", "something"); try ctx.dir.writeFile(update_path, "something");
const prev_status = try tmp.dir.updateFile("./subdir/../file", tmp.dir, "./subdir/../update", .{}); const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
try testing.expectEqual(fs.PrevStatus.stale, prev_status); try testing.expectEqual(fs.PrevStatus.stale, prev_status);
try tmp.dir.deleteDir("./subdir"); try ctx.dir.deleteDir(subdir_path);
}
}.impl);
} }
test ". and .. in absolute functions" { test ". and .. in absolute functions" {

View File

@ -5169,11 +5169,30 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
return getFdPath(h_file, out_buffer); return getFdPath(h_file, out_buffer);
} }
pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
return switch (os.tag) {
// zig fmt: off
.windows,
.macos, .ios, .watchos, .tvos,
.linux,
.solaris,
.freebsd,
=> true,
// zig fmt: on
.dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
.netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
else => false,
};
}
/// Return canonical path of handle `fd`. /// Return canonical path of handle `fd`.
/// This function is very host-specific and is not universally supported by all hosts. /// This function is very host-specific and is not universally supported by all hosts.
/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is /// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
/// unsupported on WASI. /// unsupported on WASI.
pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
@compileError("querying for canonical path of a handle is unsupported on this host");
}
switch (builtin.os.tag) { switch (builtin.os.tag) {
.windows => { .windows => {
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
@ -5276,9 +5295,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
} }
}, },
.dragonfly => { .dragonfly => {
if (comptime builtin.os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) == .lt) {
@compileError("querying for canonical path of a handle is unsupported on this host");
}
@memset(out_buffer[0..MAX_PATH_BYTES], 0); @memset(out_buffer[0..MAX_PATH_BYTES], 0);
switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) { switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
.SUCCESS => {}, .SUCCESS => {},
@ -5290,9 +5306,6 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
return out_buffer[0..len]; return out_buffer[0..len];
}, },
.netbsd => { .netbsd => {
if (comptime builtin.os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) == .lt) {
@compileError("querying for canonical path of a handle is unsupported on this host");
}
@memset(out_buffer[0..MAX_PATH_BYTES], 0); @memset(out_buffer[0..MAX_PATH_BYTES], 0);
switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) { switch (errno(system.fcntl(fd, F.GETPATH, out_buffer))) {
.SUCCESS => {}, .SUCCESS => {},
@ -5306,7 +5319,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES; const len = mem.indexOfScalar(u8, out_buffer[0..], @as(u8, 0)) orelse MAX_PATH_BYTES;
return out_buffer[0..len]; return out_buffer[0..len];
}, },
else => @compileError("querying for canonical path of a handle is unsupported on this host"), else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
} }
} }