zig

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

blob d51fe3db (83555B) - Raw


      1 const std = @import("../std.zig");
      2 const builtin = @import("builtin");
      3 const testing = std.testing;
      4 const fs = std.fs;
      5 const mem = std.mem;
      6 const wasi = std.os.wasi;
      7 const native_os = builtin.os.tag;
      8 const windows = std.os.windows;
      9 const posix = std.posix;
     10 
     11 const ArenaAllocator = std.heap.ArenaAllocator;
     12 const Dir = std.fs.Dir;
     13 const File = std.fs.File;
     14 const tmpDir = testing.tmpDir;
     15 const SymLinkFlags = std.fs.Dir.SymLinkFlags;
     16 
     17 const PathType = enum {
     18     relative,
     19     absolute,
     20     unc,
     21 
     22     pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
     23         return switch (self) {
     24             .relative => true,
     25             .absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
     26             .unc => target_os.tag == .windows,
     27         };
     28     }
     29 
     30     pub const TransformError = posix.RealPathError || error{OutOfMemory};
     31     pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
     32 
     33     pub fn getTransformFn(comptime path_type: PathType) TransformFn {
     34         switch (path_type) {
     35             .relative => return struct {
     36                 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
     37                     _ = allocator;
     38                     _ = dir;
     39                     return relative_path;
     40                 }
     41             }.transform,
     42             .absolute => return struct {
     43                 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
     44                     // The final path may not actually exist which would cause realpath to fail.
     45                     // So instead, we get the path of the dir and join it with the relative path.
     46                     var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
     47                     const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
     48                     return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
     49                 }
     50             }.transform,
     51             .unc => return struct {
     52                 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
     53                     // Any drive absolute path (C:\foo) can be converted into a UNC path by
     54                     // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
     55                     var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
     56                     const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
     57                     const windows_path_type = windows.getUnprefixedPathType(u8, dir_path);
     58                     switch (windows_path_type) {
     59                         .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
     60                         .drive_absolute => {
     61                             // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
     62                             const prepended = "\\\\127.0.0.1\\";
     63                             var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
     64                             path[prepended.len + 1] = '$';
     65                             return path;
     66                         },
     67                         else => unreachable,
     68                     }
     69                 }
     70             }.transform,
     71         }
     72     }
     73 };
     74 
     75 const TestContext = struct {
     76     path_type: PathType,
     77     path_sep: u8,
     78     arena: ArenaAllocator,
     79     tmp: testing.TmpDir,
     80     dir: std.fs.Dir,
     81     transform_fn: *const PathType.TransformFn,
     82 
     83     pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
     84         const tmp = tmpDir(.{ .iterate = true });
     85         return .{
     86             .path_type = path_type,
     87             .path_sep = path_sep,
     88             .arena = ArenaAllocator.init(allocator),
     89             .tmp = tmp,
     90             .dir = tmp.dir,
     91             .transform_fn = transform_fn,
     92         };
     93     }
     94 
     95     pub fn deinit(self: *TestContext) void {
     96         self.arena.deinit();
     97         self.tmp.cleanup();
     98     }
     99 
    100     /// Returns the `relative_path` transformed into the TestContext's `path_type`,
    101     /// with any supported path separators replaced by `path_sep`.
    102     /// The result is allocated by the TestContext's arena and will be free'd during
    103     /// `TestContext.deinit`.
    104     pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
    105         const allocator = self.arena.allocator();
    106         const transformed_path = try self.transform_fn(allocator, self.dir, relative_path);
    107         if (native_os == .windows) {
    108             const transformed_sep_path = try allocator.dupeZ(u8, transformed_path);
    109             std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) {
    110                 '/' => '\\',
    111                 '\\' => '/',
    112                 else => unreachable,
    113             }, self.path_sep);
    114             return transformed_sep_path;
    115         }
    116         return transformed_path;
    117     }
    118 
    119     /// Replaces any path separators with the canonical path separator for the platform
    120     /// (e.g. all path separators are converted to `\` on Windows).
    121     /// If path separators are replaced, then the result is allocated by the
    122     /// TestContext's arena and will be free'd during `TestContext.deinit`.
    123     pub fn toCanonicalPathSep(self: *TestContext, path: [:0]const u8) ![:0]const u8 {
    124         if (native_os == .windows) {
    125             const allocator = self.arena.allocator();
    126             const transformed_sep_path = try allocator.dupeZ(u8, path);
    127             std.mem.replaceScalar(u8, transformed_sep_path, '/', '\\');
    128             return transformed_sep_path;
    129         }
    130         return path;
    131     }
    132 };
    133 
    134 /// `test_func` must be a function that takes a `*TestContext` as a parameter and returns `!void`.
    135 /// `test_func` will be called once for each PathType that the current target supports,
    136 /// and will be passed a TestContext that can transform a relative path into the path type under test.
    137 /// The TestContext will also create a tmp directory for you (and will clean it up for you too).
    138 fn testWithAllSupportedPathTypes(test_func: anytype) !void {
    139     try testWithPathTypeIfSupported(.relative, '/', test_func);
    140     try testWithPathTypeIfSupported(.absolute, '/', test_func);
    141     try testWithPathTypeIfSupported(.unc, '/', test_func);
    142     try testWithPathTypeIfSupported(.relative, '\\', test_func);
    143     try testWithPathTypeIfSupported(.absolute, '\\', test_func);
    144     try testWithPathTypeIfSupported(.unc, '\\', test_func);
    145 }
    146 
    147 fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void {
    148     if (!(comptime path_type.isSupported(builtin.os))) return;
    149     if (!(comptime fs.path.isSep(path_sep))) return;
    150 
    151     var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn());
    152     defer ctx.deinit();
    153 
    154     try test_func(&ctx);
    155 }
    156 
    157 // For use in test setup.  If the symlink creation fails on Windows with
    158 // AccessDenied, then make the test failure silent (it is not a Zig failure).
    159 fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
    160     return dir.symLink(target, link, flags) catch |err| switch (err) {
    161         // Symlink requires admin privileges on windows, so this test can legitimately fail.
    162         error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,
    163         else => return err,
    164     };
    165 }
    166 
    167 // For use in test setup.  If the symlink creation fails on Windows with
    168 // AccessDenied, then make the test failure silent (it is not a Zig failure).
    169 fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
    170     return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) {
    171         error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,
    172         else => return err,
    173     };
    174 }
    175 
    176 test "Dir.readLink" {
    177     try testWithAllSupportedPathTypes(struct {
    178         fn impl(ctx: *TestContext) !void {
    179             // Create some targets
    180             const file_target_path = try ctx.transformPath("file.txt");
    181             try ctx.dir.writeFile(.{ .sub_path = file_target_path, .data = "nonsense" });
    182             const dir_target_path = try ctx.transformPath("subdir");
    183             try ctx.dir.makeDir(dir_target_path);
    184 
    185             // On Windows, symlink targets always use the canonical path separator
    186             const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path);
    187             const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path);
    188 
    189             // test 1: symlink to a file
    190             try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
    191             try testReadLink(ctx.dir, canonical_file_target_path, "symlink1");
    192 
    193             // test 2: symlink to a directory (can be different on Windows)
    194             try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
    195             try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2");
    196 
    197             // test 3: relative path symlink
    198             const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
    199             const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file);
    200             var subdir = try ctx.dir.makeOpenPath("subdir", .{});
    201             defer subdir.close();
    202             try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});
    203             try testReadLink(subdir, canonical_parent_file, "relative-link.txt");
    204         }
    205     }.impl);
    206 }
    207 
    208 fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
    209     var buffer: [fs.max_path_bytes]u8 = undefined;
    210     const actual = try dir.readLink(symlink_path, buffer[0..]);
    211     try testing.expectEqualStrings(target_path, actual);
    212 }
    213 
    214 fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
    215     var buffer: [fs.max_path_bytes]u8 = undefined;
    216     const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
    217     try testing.expectEqualStrings(target_path, given);
    218 }
    219 
    220 test "File.stat on a File that is a symlink returns Kind.sym_link" {
    221     // This test requires getting a file descriptor of a symlink which
    222     // is not possible on all targets
    223     switch (builtin.target.os.tag) {
    224         .windows, .linux => {},
    225         else => return error.SkipZigTest,
    226     }
    227 
    228     try testWithAllSupportedPathTypes(struct {
    229         fn impl(ctx: *TestContext) !void {
    230             const dir_target_path = try ctx.transformPath("subdir");
    231             try ctx.dir.makeDir(dir_target_path);
    232 
    233             try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
    234 
    235             var symlink = switch (builtin.target.os.tag) {
    236                 .windows => windows_symlink: {
    237                     const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink");
    238 
    239                     var result = Dir{
    240                         .fd = undefined,
    241                     };
    242 
    243                     const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2));
    244                     var nt_name = windows.UNICODE_STRING{
    245                         .Length = path_len_bytes,
    246                         .MaximumLength = path_len_bytes,
    247                         .Buffer = @constCast(&sub_path_w.data),
    248                     };
    249                     var attr = windows.OBJECT_ATTRIBUTES{
    250                         .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
    251                         .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd,
    252                         .Attributes = 0,
    253                         .ObjectName = &nt_name,
    254                         .SecurityDescriptor = null,
    255                         .SecurityQualityOfService = null,
    256                     };
    257                     var io: windows.IO_STATUS_BLOCK = undefined;
    258                     const rc = windows.ntdll.NtCreateFile(
    259                         &result.fd,
    260                         windows.STANDARD_RIGHTS_READ | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE | windows.FILE_TRAVERSE,
    261                         &attr,
    262                         &io,
    263                         null,
    264                         windows.FILE_ATTRIBUTE_NORMAL,
    265                         windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE,
    266                         windows.FILE_OPEN,
    267                         // FILE_OPEN_REPARSE_POINT is the important thing here
    268                         windows.FILE_OPEN_REPARSE_POINT | windows.FILE_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT | windows.FILE_OPEN_FOR_BACKUP_INTENT,
    269                         null,
    270                         0,
    271                     );
    272 
    273                     switch (rc) {
    274                         .SUCCESS => break :windows_symlink result,
    275                         else => return windows.unexpectedStatus(rc),
    276                     }
    277                 },
    278                 .linux => linux_symlink: {
    279                     const sub_path_c = try posix.toPosixPath("symlink");
    280                     // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink
    281                     // note that if O_DIRECTORY is set, then this will error with ENOTDIR
    282                     const flags: posix.O = .{
    283                         .NOFOLLOW = true,
    284                         .PATH = true,
    285                         .ACCMODE = .RDONLY,
    286                         .CLOEXEC = true,
    287                     };
    288                     const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0);
    289                     break :linux_symlink Dir{ .fd = fd };
    290                 },
    291                 else => unreachable,
    292             };
    293             defer symlink.close();
    294 
    295             const stat = try symlink.stat();
    296             try testing.expectEqual(File.Kind.sym_link, stat.kind);
    297         }
    298     }.impl);
    299 }
    300 
    301 test "openDir" {
    302     try testWithAllSupportedPathTypes(struct {
    303         fn impl(ctx: *TestContext) !void {
    304             const allocator = ctx.arena.allocator();
    305             const subdir_path = try ctx.transformPath("subdir");
    306             try ctx.dir.makeDir(subdir_path);
    307 
    308             for ([_][]const u8{ "", ".", ".." }) |sub_path| {
    309                 const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path });
    310                 var dir = try ctx.dir.openDir(dir_path, .{});
    311                 defer dir.close();
    312             }
    313         }
    314     }.impl);
    315 }
    316 
    317 test "accessAbsolute" {
    318     if (native_os == .wasi) return error.SkipZigTest;
    319 
    320     var tmp = tmpDir(.{});
    321     defer tmp.cleanup();
    322 
    323     const base_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
    324     defer testing.allocator.free(base_path);
    325 
    326     try fs.accessAbsolute(base_path, .{});
    327 }
    328 
    329 test "openDirAbsolute" {
    330     if (native_os == .wasi) return error.SkipZigTest;
    331 
    332     var tmp = tmpDir(.{});
    333     defer tmp.cleanup();
    334 
    335     const tmp_ino = (try tmp.dir.stat()).inode;
    336 
    337     try tmp.dir.makeDir("subdir");
    338     const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir");
    339     defer testing.allocator.free(sub_path);
    340 
    341     // Can open sub_path
    342     var tmp_sub = try fs.openDirAbsolute(sub_path, .{});
    343     defer tmp_sub.close();
    344 
    345     const sub_ino = (try tmp_sub.stat()).inode;
    346 
    347     {
    348         // Can open sub_path + ".."
    349         const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." });
    350         defer testing.allocator.free(dir_path);
    351 
    352         var dir = try fs.openDirAbsolute(dir_path, .{});
    353         defer dir.close();
    354 
    355         const ino = (try dir.stat()).inode;
    356         try testing.expectEqual(tmp_ino, ino);
    357     }
    358 
    359     {
    360         // Can open sub_path + "."
    361         const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." });
    362         defer testing.allocator.free(dir_path);
    363 
    364         var dir = try fs.openDirAbsolute(dir_path, .{});
    365         defer dir.close();
    366 
    367         const ino = (try dir.stat()).inode;
    368         try testing.expectEqual(sub_ino, ino);
    369     }
    370 
    371     {
    372         // Can open subdir + "..", with some extra "."
    373         const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." });
    374         defer testing.allocator.free(dir_path);
    375 
    376         var dir = try fs.openDirAbsolute(dir_path, .{});
    377         defer dir.close();
    378 
    379         const ino = (try dir.stat()).inode;
    380         try testing.expectEqual(tmp_ino, ino);
    381     }
    382 }
    383 
    384 test "openDir cwd parent '..'" {
    385     var dir = fs.cwd().openDir("..", .{}) catch |err| {
    386         if (native_os == .wasi and err == error.PermissionDenied) {
    387             return; // This is okay. WASI disallows escaping from the fs sandbox
    388         }
    389         return err;
    390     };
    391     defer dir.close();
    392 }
    393 
    394 test "openDir non-cwd parent '..'" {
    395     switch (native_os) {
    396         .wasi, .netbsd, .openbsd => return error.SkipZigTest,
    397         else => {},
    398     }
    399 
    400     var tmp = tmpDir(.{});
    401     defer tmp.cleanup();
    402 
    403     var subdir = try tmp.dir.makeOpenPath("subdir", .{});
    404     defer subdir.close();
    405 
    406     var dir = try subdir.openDir("..", .{});
    407     defer dir.close();
    408 
    409     const expected_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
    410     defer testing.allocator.free(expected_path);
    411 
    412     const actual_path = try dir.realpathAlloc(testing.allocator, ".");
    413     defer testing.allocator.free(actual_path);
    414 
    415     try testing.expectEqualStrings(expected_path, actual_path);
    416 }
    417 
    418 test "readLinkAbsolute" {
    419     if (native_os == .wasi) return error.SkipZigTest;
    420 
    421     var tmp = tmpDir(.{});
    422     defer tmp.cleanup();
    423 
    424     // Create some targets
    425     try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });
    426     try tmp.dir.makeDir("subdir");
    427 
    428     // Get base abs path
    429     var arena = ArenaAllocator.init(testing.allocator);
    430     defer arena.deinit();
    431     const allocator = arena.allocator();
    432 
    433     const base_path = try tmp.dir.realpathAlloc(allocator, ".");
    434 
    435     {
    436         const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" });
    437         const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" });
    438 
    439         // Create symbolic link by path
    440         try setupSymlinkAbsolute(target_path, symlink_path, .{});
    441         try testReadLinkAbsolute(target_path, symlink_path);
    442     }
    443     {
    444         const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" });
    445         const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" });
    446 
    447         // Create symbolic link to a directory by path
    448         try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
    449         try testReadLinkAbsolute(target_path, symlink_path);
    450     }
    451 }
    452 
    453 test "Dir.Iterator" {
    454     var tmp_dir = tmpDir(.{ .iterate = true });
    455     defer tmp_dir.cleanup();
    456 
    457     // First, create a couple of entries to iterate over.
    458     const file = try tmp_dir.dir.createFile("some_file", .{});
    459     file.close();
    460 
    461     try tmp_dir.dir.makeDir("some_dir");
    462 
    463     var arena = ArenaAllocator.init(testing.allocator);
    464     defer arena.deinit();
    465     const allocator = arena.allocator();
    466 
    467     var entries = std.array_list.Managed(Dir.Entry).init(allocator);
    468 
    469     // Create iterator.
    470     var iter = tmp_dir.dir.iterate();
    471     while (try iter.next()) |entry| {
    472         // We cannot just store `entry` as on Windows, we're re-using the name buffer
    473         // which means we'll actually share the `name` pointer between entries!
    474         const name = try allocator.dupe(u8, entry.name);
    475         try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
    476     }
    477 
    478     try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
    479     try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
    480     try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
    481 }
    482 
    483 test "Dir.Iterator many entries" {
    484     var tmp_dir = tmpDir(.{ .iterate = true });
    485     defer tmp_dir.cleanup();
    486 
    487     const num = 1024;
    488     var i: usize = 0;
    489     var buf: [4]u8 = undefined; // Enough to store "1024".
    490     while (i < num) : (i += 1) {
    491         const name = try std.fmt.bufPrint(&buf, "{}", .{i});
    492         const file = try tmp_dir.dir.createFile(name, .{});
    493         file.close();
    494     }
    495 
    496     var arena = ArenaAllocator.init(testing.allocator);
    497     defer arena.deinit();
    498     const allocator = arena.allocator();
    499 
    500     var entries = std.array_list.Managed(Dir.Entry).init(allocator);
    501 
    502     // Create iterator.
    503     var iter = tmp_dir.dir.iterate();
    504     while (try iter.next()) |entry| {
    505         // We cannot just store `entry` as on Windows, we're re-using the name buffer
    506         // which means we'll actually share the `name` pointer between entries!
    507         const name = try allocator.dupe(u8, entry.name);
    508         try entries.append(.{ .name = name, .kind = entry.kind });
    509     }
    510 
    511     i = 0;
    512     while (i < num) : (i += 1) {
    513         const name = try std.fmt.bufPrint(&buf, "{}", .{i});
    514         try testing.expect(contains(&entries, .{ .name = name, .kind = .file }));
    515     }
    516 }
    517 
    518 test "Dir.Iterator twice" {
    519     var tmp_dir = tmpDir(.{ .iterate = true });
    520     defer tmp_dir.cleanup();
    521 
    522     // First, create a couple of entries to iterate over.
    523     const file = try tmp_dir.dir.createFile("some_file", .{});
    524     file.close();
    525 
    526     try tmp_dir.dir.makeDir("some_dir");
    527 
    528     var arena = ArenaAllocator.init(testing.allocator);
    529     defer arena.deinit();
    530     const allocator = arena.allocator();
    531 
    532     var i: u8 = 0;
    533     while (i < 2) : (i += 1) {
    534         var entries = std.array_list.Managed(Dir.Entry).init(allocator);
    535 
    536         // Create iterator.
    537         var iter = tmp_dir.dir.iterate();
    538         while (try iter.next()) |entry| {
    539             // We cannot just store `entry` as on Windows, we're re-using the name buffer
    540             // which means we'll actually share the `name` pointer between entries!
    541             const name = try allocator.dupe(u8, entry.name);
    542             try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
    543         }
    544 
    545         try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
    546         try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
    547         try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
    548     }
    549 }
    550 
    551 test "Dir.Iterator reset" {
    552     var tmp_dir = tmpDir(.{ .iterate = true });
    553     defer tmp_dir.cleanup();
    554 
    555     // First, create a couple of entries to iterate over.
    556     const file = try tmp_dir.dir.createFile("some_file", .{});
    557     file.close();
    558 
    559     try tmp_dir.dir.makeDir("some_dir");
    560 
    561     var arena = ArenaAllocator.init(testing.allocator);
    562     defer arena.deinit();
    563     const allocator = arena.allocator();
    564 
    565     // Create iterator.
    566     var iter = tmp_dir.dir.iterate();
    567 
    568     var i: u8 = 0;
    569     while (i < 2) : (i += 1) {
    570         var entries = std.array_list.Managed(Dir.Entry).init(allocator);
    571 
    572         while (try iter.next()) |entry| {
    573             // We cannot just store `entry` as on Windows, we're re-using the name buffer
    574             // which means we'll actually share the `name` pointer between entries!
    575             const name = try allocator.dupe(u8, entry.name);
    576             try entries.append(.{ .name = name, .kind = entry.kind });
    577         }
    578 
    579         try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
    580         try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
    581         try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
    582 
    583         iter.reset();
    584     }
    585 }
    586 
    587 test "Dir.Iterator but dir is deleted during iteration" {
    588     var tmp = std.testing.tmpDir(.{});
    589     defer tmp.cleanup();
    590 
    591     // Create directory and setup an iterator for it
    592     var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true });
    593     defer subdir.close();
    594 
    595     var iterator = subdir.iterate();
    596 
    597     // Create something to iterate over within the subdir
    598     try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b");
    599 
    600     // Then, before iterating, delete the directory that we're iterating.
    601     // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc.
    602     // If we get an error while trying to delete, we can skip this test (this will happen on platforms
    603     // like Windows which will give FileBusy if the directory is currently open for iteration).
    604     tmp.dir.deleteTree("subdir") catch return error.SkipZigTest;
    605 
    606     // Now, when we try to iterate, the next call should return null immediately.
    607     const entry = try iterator.next();
    608     try std.testing.expect(entry == null);
    609 
    610     // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux`
    611     if (native_os == .linux) {
    612         try std.testing.expectError(error.DirNotFound, iterator.nextLinux());
    613     }
    614 }
    615 
    616 fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
    617     return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
    618 }
    619 
    620 fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bool {
    621     for (entries.items) |entry| {
    622         if (entryEql(entry, el)) return true;
    623     }
    624     return false;
    625 }
    626 
    627 test "Dir.realpath smoke test" {
    628     if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
    629 
    630     try testWithAllSupportedPathTypes(struct {
    631         fn impl(ctx: *TestContext) !void {
    632             const allocator = ctx.arena.allocator();
    633             const test_file_path = try ctx.transformPath("test_file");
    634             const test_dir_path = try ctx.transformPath("test_dir");
    635             var buf: [fs.max_path_bytes]u8 = undefined;
    636 
    637             // FileNotFound if the path doesn't exist
    638             try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path));
    639             try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
    640             try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path));
    641             try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
    642 
    643             // Now create the file and dir
    644             try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });
    645             try ctx.dir.makeDir(test_dir_path);
    646 
    647             const base_path = try ctx.transformPath(".");
    648             const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path);
    649             const expected_file_path = try fs.path.join(
    650                 allocator,
    651                 &.{ base_realpath, "test_file" },
    652             );
    653             const expected_dir_path = try fs.path.join(
    654                 allocator,
    655                 &.{ base_realpath, "test_dir" },
    656             );
    657 
    658             // First, test non-alloc version
    659             {
    660                 const file_path = try ctx.dir.realpath(test_file_path, &buf);
    661                 try testing.expectEqualStrings(expected_file_path, file_path);
    662 
    663                 const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
    664                 try testing.expectEqualStrings(expected_dir_path, dir_path);
    665             }
    666 
    667             // Next, test alloc version
    668             {
    669                 const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path);
    670                 try testing.expectEqualStrings(expected_file_path, file_path);
    671 
    672                 const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path);
    673                 try testing.expectEqualStrings(expected_dir_path, dir_path);
    674             }
    675         }
    676     }.impl);
    677 }
    678 
    679 test "readAllAlloc" {
    680     var tmp_dir = tmpDir(.{});
    681     defer tmp_dir.cleanup();
    682 
    683     var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
    684     defer file.close();
    685 
    686     const buf1 = try file.readToEndAlloc(testing.allocator, 1024);
    687     defer testing.allocator.free(buf1);
    688     try testing.expectEqual(@as(usize, 0), buf1.len);
    689 
    690     const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n";
    691     try file.writeAll(write_buf);
    692     try file.seekTo(0);
    693 
    694     // max_bytes > file_size
    695     const buf2 = try file.readToEndAlloc(testing.allocator, 1024);
    696     defer testing.allocator.free(buf2);
    697     try testing.expectEqual(write_buf.len, buf2.len);
    698     try testing.expectEqualStrings(write_buf, buf2);
    699     try file.seekTo(0);
    700 
    701     // max_bytes == file_size
    702     const buf3 = try file.readToEndAlloc(testing.allocator, write_buf.len);
    703     defer testing.allocator.free(buf3);
    704     try testing.expectEqual(write_buf.len, buf3.len);
    705     try testing.expectEqualStrings(write_buf, buf3);
    706     try file.seekTo(0);
    707 
    708     // max_bytes < file_size
    709     try testing.expectError(error.FileTooBig, file.readToEndAlloc(testing.allocator, write_buf.len - 1));
    710 }
    711 
    712 test "Dir.statFile" {
    713     try testWithAllSupportedPathTypes(struct {
    714         fn impl(ctx: *TestContext) !void {
    715             const test_file_name = try ctx.transformPath("test_file");
    716 
    717             try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name));
    718 
    719             try ctx.dir.writeFile(.{ .sub_path = test_file_name, .data = "" });
    720 
    721             const stat = try ctx.dir.statFile(test_file_name);
    722             try testing.expectEqual(File.Kind.file, stat.kind);
    723         }
    724     }.impl);
    725 }
    726 
    727 test "statFile on dangling symlink" {
    728     try testWithAllSupportedPathTypes(struct {
    729         fn impl(ctx: *TestContext) !void {
    730             const symlink_name = try ctx.transformPath("dangling-symlink");
    731             const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist";
    732 
    733             try setupSymlink(ctx.dir, symlink_target, symlink_name, .{});
    734 
    735             try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(symlink_name));
    736         }
    737     }.impl);
    738 }
    739 
    740 test "directory operations on files" {
    741     try testWithAllSupportedPathTypes(struct {
    742         fn impl(ctx: *TestContext) !void {
    743             const test_file_name = try ctx.transformPath("test_file");
    744 
    745             var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
    746             file.close();
    747 
    748             try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
    749             try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
    750             try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
    751 
    752             if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
    753                 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
    754                 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
    755             }
    756 
    757             // ensure the file still exists and is a file as a sanity check
    758             file = try ctx.dir.openFile(test_file_name, .{});
    759             const stat = try file.stat();
    760             try testing.expectEqual(File.Kind.file, stat.kind);
    761             file.close();
    762         }
    763     }.impl);
    764 }
    765 
    766 test "file operations on directories" {
    767     // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
    768     if (native_os == .freebsd) return error.SkipZigTest;
    769 
    770     try testWithAllSupportedPathTypes(struct {
    771         fn impl(ctx: *TestContext) !void {
    772             const test_dir_name = try ctx.transformPath("test_dir");
    773 
    774             try ctx.dir.makeDir(test_dir_name);
    775 
    776             try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
    777             try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
    778             switch (native_os) {
    779                 .dragonfly, .netbsd => {
    780                     // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732
    781                     const buf = try ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize));
    782                     testing.allocator.free(buf);
    783                 },
    784                 .wasi => {
    785                     // WASI return EBADF, which gets mapped to NotOpenForReading.
    786                     // See https://github.com/bytecodealliance/wasmtime/issues/1935
    787                     try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
    788                 },
    789                 else => {
    790                     try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)));
    791                 },
    792             }
    793 
    794             if (native_os == .wasi and builtin.link_libc) {
    795                 // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747
    796                 const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write });
    797                 handle.close();
    798             } else {
    799                 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
    800                 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
    801                 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
    802             }
    803 
    804             if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
    805                 try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
    806                 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
    807             }
    808 
    809             // ensure the directory still exists as a sanity check
    810             var dir = try ctx.dir.openDir(test_dir_name, .{});
    811             dir.close();
    812         }
    813     }.impl);
    814 }
    815 
    816 test "makeOpenPath parent dirs do not exist" {
    817     var tmp_dir = tmpDir(.{});
    818     defer tmp_dir.cleanup();
    819 
    820     var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{});
    821     dir.close();
    822 
    823     // double check that the full directory structure was created
    824     var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{});
    825     dir_verification.close();
    826 }
    827 
    828 test "deleteDir" {
    829     try testWithAllSupportedPathTypes(struct {
    830         fn impl(ctx: *TestContext) !void {
    831             const test_dir_path = try ctx.transformPath("test_dir");
    832             const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file");
    833 
    834             // deleting a non-existent directory
    835             try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
    836 
    837             // deleting a non-empty directory
    838             try ctx.dir.makeDir(test_dir_path);
    839             try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });
    840             try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
    841 
    842             // deleting an empty directory
    843             try ctx.dir.deleteFile(test_file_path);
    844             try ctx.dir.deleteDir(test_dir_path);
    845         }
    846     }.impl);
    847 }
    848 
    849 test "Dir.rename files" {
    850     try testWithAllSupportedPathTypes(struct {
    851         fn impl(ctx: *TestContext) !void {
    852             // Rename on Windows can hit intermittent AccessDenied errors
    853             // when certain conditions are true about the host system.
    854             // For now, skip this test when the path type is UNC to avoid them.
    855             // See https://github.com/ziglang/zig/issues/17134
    856             if (ctx.path_type == .unc) return;
    857 
    858             const missing_file_path = try ctx.transformPath("missing_file_name");
    859             const something_else_path = try ctx.transformPath("something_else");
    860 
    861             try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
    862 
    863             // Renaming files
    864             const test_file_name = try ctx.transformPath("test_file");
    865             const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
    866             var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
    867             file.close();
    868             try ctx.dir.rename(test_file_name, renamed_test_file_name);
    869 
    870             // Ensure the file was renamed
    871             try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
    872             file = try ctx.dir.openFile(renamed_test_file_name, .{});
    873             file.close();
    874 
    875             // Rename to self succeeds
    876             try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
    877 
    878             // Rename to existing file succeeds
    879             const existing_file_path = try ctx.transformPath("existing_file");
    880             var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
    881             existing_file.close();
    882             try ctx.dir.rename(renamed_test_file_name, existing_file_path);
    883 
    884             try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{}));
    885             file = try ctx.dir.openFile(existing_file_path, .{});
    886             file.close();
    887         }
    888     }.impl);
    889 }
    890 
    891 test "Dir.rename directories" {
    892     try testWithAllSupportedPathTypes(struct {
    893         fn impl(ctx: *TestContext) !void {
    894             // Rename on Windows can hit intermittent AccessDenied errors
    895             // when certain conditions are true about the host system.
    896             // For now, skip this test when the path type is UNC to avoid them.
    897             // See https://github.com/ziglang/zig/issues/17134
    898             if (ctx.path_type == .unc) return;
    899 
    900             const test_dir_path = try ctx.transformPath("test_dir");
    901             const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
    902 
    903             // Renaming directories
    904             try ctx.dir.makeDir(test_dir_path);
    905             try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
    906 
    907             // Ensure the directory was renamed
    908             try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
    909             var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
    910 
    911             // Put a file in the directory
    912             var file = try dir.createFile("test_file", .{ .read = true });
    913             file.close();
    914             dir.close();
    915 
    916             const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
    917             try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
    918 
    919             // Ensure the directory was renamed and the file still exists in it
    920             try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
    921             dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});
    922             file = try dir.openFile("test_file", .{});
    923             file.close();
    924             dir.close();
    925         }
    926     }.impl);
    927 }
    928 
    929 test "Dir.rename directory onto empty dir" {
    930     // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
    931     if (native_os == .windows) return error.SkipZigTest;
    932 
    933     try testWithAllSupportedPathTypes(struct {
    934         fn impl(ctx: *TestContext) !void {
    935             const test_dir_path = try ctx.transformPath("test_dir");
    936             const target_dir_path = try ctx.transformPath("target_dir_path");
    937 
    938             try ctx.dir.makeDir(test_dir_path);
    939             try ctx.dir.makeDir(target_dir_path);
    940             try ctx.dir.rename(test_dir_path, target_dir_path);
    941 
    942             // Ensure the directory was renamed
    943             try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
    944             var dir = try ctx.dir.openDir(target_dir_path, .{});
    945             dir.close();
    946         }
    947     }.impl);
    948 }
    949 
    950 test "Dir.rename directory onto non-empty dir" {
    951     // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
    952     if (native_os == .windows) return error.SkipZigTest;
    953 
    954     try testWithAllSupportedPathTypes(struct {
    955         fn impl(ctx: *TestContext) !void {
    956             const test_dir_path = try ctx.transformPath("test_dir");
    957             const target_dir_path = try ctx.transformPath("target_dir_path");
    958 
    959             try ctx.dir.makeDir(test_dir_path);
    960 
    961             var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
    962             var file = try target_dir.createFile("test_file", .{ .read = true });
    963             file.close();
    964             target_dir.close();
    965 
    966             // Rename should fail with PathAlreadyExists if target_dir is non-empty
    967             try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
    968 
    969             // Ensure the directory was not renamed
    970             var dir = try ctx.dir.openDir(test_dir_path, .{});
    971             dir.close();
    972         }
    973     }.impl);
    974 }
    975 
    976 test "Dir.rename file <-> dir" {
    977     // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
    978     if (native_os == .windows) return error.SkipZigTest;
    979 
    980     try testWithAllSupportedPathTypes(struct {
    981         fn impl(ctx: *TestContext) !void {
    982             const test_file_path = try ctx.transformPath("test_file");
    983             const test_dir_path = try ctx.transformPath("test_dir");
    984 
    985             var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
    986             file.close();
    987             try ctx.dir.makeDir(test_dir_path);
    988             try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
    989             try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
    990         }
    991     }.impl);
    992 }
    993 
    994 test "rename" {
    995     var tmp_dir1 = tmpDir(.{});
    996     defer tmp_dir1.cleanup();
    997 
    998     var tmp_dir2 = tmpDir(.{});
    999     defer tmp_dir2.cleanup();
   1000 
   1001     // Renaming files
   1002     const test_file_name = "test_file";
   1003     const renamed_test_file_name = "test_file_renamed";
   1004     var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });
   1005     file.close();
   1006     try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);
   1007 
   1008     // ensure the file was renamed
   1009     try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
   1010     file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
   1011     file.close();
   1012 }
   1013 
   1014 test "renameAbsolute" {
   1015     if (native_os == .wasi) return error.SkipZigTest;
   1016 
   1017     var tmp_dir = tmpDir(.{});
   1018     defer tmp_dir.cleanup();
   1019 
   1020     // Get base abs path
   1021     var arena = ArenaAllocator.init(testing.allocator);
   1022     defer arena.deinit();
   1023     const allocator = arena.allocator();
   1024 
   1025     const base_path = try tmp_dir.dir.realpathAlloc(allocator, ".");
   1026 
   1027     try testing.expectError(error.FileNotFound, fs.renameAbsolute(
   1028         try fs.path.join(allocator, &.{ base_path, "missing_file_name" }),
   1029         try fs.path.join(allocator, &.{ base_path, "something_else" }),
   1030     ));
   1031 
   1032     // Renaming files
   1033     const test_file_name = "test_file";
   1034     const renamed_test_file_name = "test_file_renamed";
   1035     var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
   1036     file.close();
   1037     try fs.renameAbsolute(
   1038         try fs.path.join(allocator, &.{ base_path, test_file_name }),
   1039         try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }),
   1040     );
   1041 
   1042     // ensure the file was renamed
   1043     try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
   1044     file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
   1045     const stat = try file.stat();
   1046     try testing.expectEqual(File.Kind.file, stat.kind);
   1047     file.close();
   1048 
   1049     // Renaming directories
   1050     const test_dir_name = "test_dir";
   1051     const renamed_test_dir_name = "test_dir_renamed";
   1052     try tmp_dir.dir.makeDir(test_dir_name);
   1053     try fs.renameAbsolute(
   1054         try fs.path.join(allocator, &.{ base_path, test_dir_name }),
   1055         try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }),
   1056     );
   1057 
   1058     // ensure the directory was renamed
   1059     try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
   1060     var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
   1061     dir.close();
   1062 }
   1063 
   1064 test "openSelfExe" {
   1065     if (native_os == .wasi) return error.SkipZigTest;
   1066 
   1067     const self_exe_file = try std.fs.openSelfExe(.{});
   1068     self_exe_file.close();
   1069 }
   1070 
   1071 test "selfExePath" {
   1072     if (native_os == .wasi) return error.SkipZigTest;
   1073 
   1074     var buf: [fs.max_path_bytes]u8 = undefined;
   1075     const buf_self_exe_path = try std.fs.selfExePath(&buf);
   1076     const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
   1077     defer testing.allocator.free(alloc_self_exe_path);
   1078     try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
   1079 }
   1080 
   1081 test "deleteTree does not follow symlinks" {
   1082     var tmp = tmpDir(.{});
   1083     defer tmp.cleanup();
   1084 
   1085     try tmp.dir.makePath("b");
   1086     {
   1087         var a = try tmp.dir.makeOpenPath("a", .{});
   1088         defer a.close();
   1089 
   1090         try setupSymlink(a, "../b", "b", .{ .is_directory = true });
   1091     }
   1092 
   1093     try tmp.dir.deleteTree("a");
   1094 
   1095     try testing.expectError(error.FileNotFound, tmp.dir.access("a", .{}));
   1096     try tmp.dir.access("b", .{});
   1097 }
   1098 
   1099 test "deleteTree on a symlink" {
   1100     var tmp = tmpDir(.{});
   1101     defer tmp.cleanup();
   1102 
   1103     // Symlink to a file
   1104     try tmp.dir.writeFile(.{ .sub_path = "file", .data = "" });
   1105     try setupSymlink(tmp.dir, "file", "filelink", .{});
   1106 
   1107     try tmp.dir.deleteTree("filelink");
   1108     try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{}));
   1109     try tmp.dir.access("file", .{});
   1110 
   1111     // Symlink to a directory
   1112     try tmp.dir.makePath("dir");
   1113     try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });
   1114 
   1115     try tmp.dir.deleteTree("dirlink");
   1116     try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));
   1117     try tmp.dir.access("dir", .{});
   1118 }
   1119 
   1120 test "makePath, put some files in it, deleteTree" {
   1121     try testWithAllSupportedPathTypes(struct {
   1122         fn impl(ctx: *TestContext) !void {
   1123             const allocator = ctx.arena.allocator();
   1124             const dir_path = try ctx.transformPath("os_test_tmp");
   1125 
   1126             try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
   1127             try ctx.dir.writeFile(.{
   1128                 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
   1129                 .data = "nonsense",
   1130             });
   1131             try ctx.dir.writeFile(.{
   1132                 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
   1133                 .data = "blah",
   1134             });
   1135 
   1136             try ctx.dir.deleteTree(dir_path);
   1137             try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
   1138         }
   1139     }.impl);
   1140 }
   1141 
   1142 test "makePath, put some files in it, deleteTreeMinStackSize" {
   1143     try testWithAllSupportedPathTypes(struct {
   1144         fn impl(ctx: *TestContext) !void {
   1145             const allocator = ctx.arena.allocator();
   1146             const dir_path = try ctx.transformPath("os_test_tmp");
   1147 
   1148             try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
   1149             try ctx.dir.writeFile(.{
   1150                 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
   1151                 .data = "nonsense",
   1152             });
   1153             try ctx.dir.writeFile(.{
   1154                 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
   1155                 .data = "blah",
   1156             });
   1157 
   1158             try ctx.dir.deleteTreeMinStackSize(dir_path);
   1159             try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
   1160         }
   1161     }.impl);
   1162 }
   1163 
   1164 test "makePath in a directory that no longer exists" {
   1165     if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir
   1166 
   1167     var tmp = tmpDir(.{});
   1168     defer tmp.cleanup();
   1169     try tmp.parent_dir.deleteTree(&tmp.sub_path);
   1170 
   1171     try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
   1172 }
   1173 
   1174 test "makePath but sub_path contains pre-existing file" {
   1175     var tmp = tmpDir(.{});
   1176     defer tmp.cleanup();
   1177 
   1178     try tmp.dir.makeDir("foo");
   1179     try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" });
   1180 
   1181     try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz"));
   1182 }
   1183 
   1184 fn expectDir(dir: Dir, path: []const u8) !void {
   1185     var d = try dir.openDir(path, .{});
   1186     d.close();
   1187 }
   1188 
   1189 test "makepath existing directories" {
   1190     var tmp = tmpDir(.{});
   1191     defer tmp.cleanup();
   1192 
   1193     try tmp.dir.makeDir("A");
   1194     var tmpA = try tmp.dir.openDir("A", .{});
   1195     defer tmpA.close();
   1196     try tmpA.makeDir("B");
   1197 
   1198     const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C";
   1199     try tmp.dir.makePath(testPath);
   1200 
   1201     try expectDir(tmp.dir, testPath);
   1202 }
   1203 
   1204 test "makepath through existing valid symlink" {
   1205     var tmp = tmpDir(.{});
   1206     defer tmp.cleanup();
   1207 
   1208     try tmp.dir.makeDir("realfolder");
   1209     try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
   1210 
   1211     try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder");
   1212 
   1213     try expectDir(tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder");
   1214 }
   1215 
   1216 test "makepath relative walks" {
   1217     var tmp = tmpDir(.{});
   1218     defer tmp.cleanup();
   1219 
   1220     const relPath = try fs.path.join(testing.allocator, &.{
   1221         "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C",
   1222     });
   1223     defer testing.allocator.free(relPath);
   1224 
   1225     try tmp.dir.makePath(relPath);
   1226 
   1227     // How .. is handled is different on Windows than non-Windows
   1228     switch (native_os) {
   1229         .windows => {
   1230             // On Windows, .. is resolved before passing the path to NtCreateFile,
   1231             // meaning everything except `first/C` drops out.
   1232             try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
   1233             try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{}));
   1234             try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{}));
   1235         },
   1236         else => {
   1237             try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A");
   1238             try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B");
   1239             try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
   1240             try expectDir(tmp.dir, "second");
   1241             try expectDir(tmp.dir, "third");
   1242         },
   1243     }
   1244 }
   1245 
   1246 test "makepath ignores '.'" {
   1247     var tmp = tmpDir(.{});
   1248     defer tmp.cleanup();
   1249 
   1250     // Path to create, with "." elements:
   1251     const dotPath = try fs.path.join(testing.allocator, &.{
   1252         "first", ".", "second", ".", "third",
   1253     });
   1254     defer testing.allocator.free(dotPath);
   1255 
   1256     // Path to expect to find:
   1257     const expectedPath = try fs.path.join(testing.allocator, &.{
   1258         "first", "second", "third",
   1259     });
   1260     defer testing.allocator.free(expectedPath);
   1261 
   1262     try tmp.dir.makePath(dotPath);
   1263 
   1264     try expectDir(tmp.dir, expectedPath);
   1265 }
   1266 
   1267 fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
   1268     // setup, create a dir and a nested file both with maxed filenames, and walk the dir
   1269     {
   1270         var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{});
   1271         defer maxed_dir.close();
   1272 
   1273         try maxed_dir.writeFile(.{ .sub_path = maxed_filename, .data = "" });
   1274 
   1275         var walker = try iterable_dir.walk(testing.allocator);
   1276         defer walker.deinit();
   1277 
   1278         var count: usize = 0;
   1279         while (try walker.next()) |entry| {
   1280             try testing.expectEqualStrings(maxed_filename, entry.basename);
   1281             count += 1;
   1282         }
   1283         try testing.expectEqual(@as(usize, 2), count);
   1284     }
   1285 
   1286     // ensure that we can delete the tree
   1287     try iterable_dir.deleteTree(maxed_filename);
   1288 }
   1289 
   1290 test "max file name component lengths" {
   1291     var tmp = tmpDir(.{ .iterate = true });
   1292     defer tmp.cleanup();
   1293 
   1294     if (native_os == .windows) {
   1295         // U+FFFF is the character with the largest code point that is encoded as a single
   1296         // UTF-16 code unit, so Windows allows for NAME_MAX of them.
   1297         const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX;
   1298         try testFilenameLimits(tmp.dir, &maxed_windows_filename);
   1299     } else if (native_os == .wasi) {
   1300         // On WASI, the maxed filename depends on the host OS, so in order for this test to
   1301         // work on any host, we need to use a length that will work for all platforms
   1302         // (i.e. the minimum max_name_bytes of all supported platforms).
   1303         const maxed_wasi_filename = [_]u8{'1'} ** 255;
   1304         try testFilenameLimits(tmp.dir, &maxed_wasi_filename);
   1305     } else {
   1306         const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes;
   1307         try testFilenameLimits(tmp.dir, &maxed_ascii_filename);
   1308     }
   1309 }
   1310 
   1311 test "writev, readv" {
   1312     var tmp = tmpDir(.{});
   1313     defer tmp.cleanup();
   1314 
   1315     const line1 = "line1\n";
   1316     const line2 = "line2\n";
   1317 
   1318     var buf1: [line1.len]u8 = undefined;
   1319     var buf2: [line2.len]u8 = undefined;
   1320     var write_vecs = [_]posix.iovec_const{
   1321         .{
   1322             .base = line1,
   1323             .len = line1.len,
   1324         },
   1325         .{
   1326             .base = line2,
   1327             .len = line2.len,
   1328         },
   1329     };
   1330     var read_vecs = [_]posix.iovec{
   1331         .{
   1332             .base = &buf2,
   1333             .len = buf2.len,
   1334         },
   1335         .{
   1336             .base = &buf1,
   1337             .len = buf1.len,
   1338         },
   1339     };
   1340 
   1341     var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
   1342     defer src_file.close();
   1343 
   1344     try src_file.writevAll(&write_vecs);
   1345     try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());
   1346     try src_file.seekTo(0);
   1347     const read = try src_file.readvAll(&read_vecs);
   1348     try testing.expectEqual(@as(usize, line1.len + line2.len), read);
   1349     try testing.expectEqualStrings(&buf1, "line2\n");
   1350     try testing.expectEqualStrings(&buf2, "line1\n");
   1351 }
   1352 
   1353 test "pwritev, preadv" {
   1354     var tmp = tmpDir(.{});
   1355     defer tmp.cleanup();
   1356 
   1357     const line1 = "line1\n";
   1358     const line2 = "line2\n";
   1359 
   1360     var buf1: [line1.len]u8 = undefined;
   1361     var buf2: [line2.len]u8 = undefined;
   1362     var write_vecs = [_]posix.iovec_const{
   1363         .{
   1364             .base = line1,
   1365             .len = line1.len,
   1366         },
   1367         .{
   1368             .base = line2,
   1369             .len = line2.len,
   1370         },
   1371     };
   1372     var read_vecs = [_]posix.iovec{
   1373         .{
   1374             .base = &buf2,
   1375             .len = buf2.len,
   1376         },
   1377         .{
   1378             .base = &buf1,
   1379             .len = buf1.len,
   1380         },
   1381     };
   1382 
   1383     var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
   1384     defer src_file.close();
   1385 
   1386     try src_file.pwritevAll(&write_vecs, 16);
   1387     try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());
   1388     const read = try src_file.preadvAll(&read_vecs, 16);
   1389     try testing.expectEqual(@as(usize, line1.len + line2.len), read);
   1390     try testing.expectEqualStrings(&buf1, "line2\n");
   1391     try testing.expectEqualStrings(&buf2, "line1\n");
   1392 }
   1393 
   1394 test "setEndPos" {
   1395     // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
   1396     if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
   1397     if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806
   1398 
   1399     var tmp = tmpDir(.{});
   1400     defer tmp.cleanup();
   1401 
   1402     const file_name = "afile.txt";
   1403     try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" });
   1404     const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write });
   1405     defer f.close();
   1406 
   1407     const initial_size = try f.getEndPos();
   1408     var buffer: [32]u8 = undefined;
   1409 
   1410     {
   1411         try f.setEndPos(initial_size);
   1412         try testing.expectEqual(initial_size, try f.getEndPos());
   1413         try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0));
   1414         try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
   1415     }
   1416 
   1417     {
   1418         const larger = initial_size + 4;
   1419         try f.setEndPos(larger);
   1420         try testing.expectEqual(larger, try f.getEndPos());
   1421         try testing.expectEqual(larger, try f.preadAll(&buffer, 0));
   1422         try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
   1423     }
   1424 
   1425     {
   1426         const smaller = initial_size - 5;
   1427         try f.setEndPos(smaller);
   1428         try testing.expectEqual(smaller, try f.getEndPos());
   1429         try testing.expectEqual(smaller, try f.preadAll(&buffer, 0));
   1430         try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
   1431     }
   1432 
   1433     try f.setEndPos(0);
   1434     try testing.expectEqual(0, try f.getEndPos());
   1435     try testing.expectEqual(0, try f.preadAll(&buffer, 0));
   1436 
   1437     // Invalid file length should error gracefully. Actual limit is host
   1438     // and file-system dependent, but 1PB should fail on filesystems like
   1439     // EXT4 and NTFS.  But XFS or Btrfs support up to 8EiB files.
   1440     f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) {
   1441         return err;
   1442     };
   1443 
   1444     f.setEndPos(std.math.maxInt(u63)) catch |err| if (err != error.FileTooBig) {
   1445         return err;
   1446     };
   1447 
   1448     try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63) + 1));
   1449     try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64)));
   1450 }
   1451 
   1452 test "access file" {
   1453     try testWithAllSupportedPathTypes(struct {
   1454         fn impl(ctx: *TestContext) !void {
   1455             const dir_path = try ctx.transformPath("os_test_tmp");
   1456             const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt");
   1457 
   1458             try ctx.dir.makePath(dir_path);
   1459             try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
   1460 
   1461             try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" });
   1462             try ctx.dir.access(file_path, .{});
   1463             try ctx.dir.deleteTree(dir_path);
   1464         }
   1465     }.impl);
   1466 }
   1467 
   1468 test "sendfile" {
   1469     var tmp = tmpDir(.{});
   1470     defer tmp.cleanup();
   1471 
   1472     try tmp.dir.makePath("os_test_tmp");
   1473 
   1474     var dir = try tmp.dir.openDir("os_test_tmp", .{});
   1475     defer dir.close();
   1476 
   1477     const line1 = "line1\n";
   1478     const line2 = "second line\n";
   1479     var vecs = [_]posix.iovec_const{
   1480         .{
   1481             .base = line1,
   1482             .len = line1.len,
   1483         },
   1484         .{
   1485             .base = line2,
   1486             .len = line2.len,
   1487         },
   1488     };
   1489 
   1490     var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
   1491     defer src_file.close();
   1492 
   1493     try src_file.writevAll(&vecs);
   1494 
   1495     var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
   1496     defer dest_file.close();
   1497 
   1498     const header1 = "header1\n";
   1499     const header2 = "second header\n";
   1500     const trailer1 = "trailer1\n";
   1501     const trailer2 = "second trailer\n";
   1502     var headers: [2][]const u8 = .{ header1, header2 };
   1503     var trailers: [2][]const u8 = .{ trailer1, trailer2 };
   1504 
   1505     var written_buf: [100]u8 = undefined;
   1506     var file_reader = src_file.reader(&.{});
   1507     var fallback_buffer: [50]u8 = undefined;
   1508     var file_writer = dest_file.writer(&fallback_buffer);
   1509     try file_writer.interface.writeVecAll(&headers);
   1510     try file_reader.seekTo(1);
   1511     try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));
   1512     try file_writer.interface.writeVecAll(&trailers);
   1513     try file_writer.interface.flush();
   1514     const amt = try dest_file.preadAll(&written_buf, 0);
   1515     try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
   1516 }
   1517 
   1518 test "copyRangeAll" {
   1519     var tmp = tmpDir(.{});
   1520     defer tmp.cleanup();
   1521 
   1522     try tmp.dir.makePath("os_test_tmp");
   1523 
   1524     var dir = try tmp.dir.openDir("os_test_tmp", .{});
   1525     defer dir.close();
   1526 
   1527     var src_file = try dir.createFile("file1.txt", .{ .read = true });
   1528     defer src_file.close();
   1529 
   1530     const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
   1531     try src_file.writeAll(data);
   1532 
   1533     var dest_file = try dir.createFile("file2.txt", .{ .read = true });
   1534     defer dest_file.close();
   1535 
   1536     var written_buf: [100]u8 = undefined;
   1537     _ = try src_file.copyRangeAll(0, dest_file, 0, data.len);
   1538 
   1539     const amt = try dest_file.preadAll(&written_buf, 0);
   1540     try testing.expectEqualStrings(data, written_buf[0..amt]);
   1541 }
   1542 
   1543 test "copyFile" {
   1544     try testWithAllSupportedPathTypes(struct {
   1545         fn impl(ctx: *TestContext) !void {
   1546             const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
   1547             const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
   1548             const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
   1549             const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
   1550 
   1551             try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data });
   1552             defer ctx.dir.deleteFile(src_file) catch {};
   1553 
   1554             try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
   1555             defer ctx.dir.deleteFile(dest_file) catch {};
   1556 
   1557             try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
   1558             defer ctx.dir.deleteFile(dest_file2) catch {};
   1559 
   1560             try expectFileContents(ctx.dir, dest_file, data);
   1561             try expectFileContents(ctx.dir, dest_file2, data);
   1562         }
   1563     }.impl);
   1564 }
   1565 
   1566 fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
   1567     const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000);
   1568     defer testing.allocator.free(contents);
   1569 
   1570     try testing.expectEqualSlices(u8, data, contents);
   1571 }
   1572 
   1573 test "AtomicFile" {
   1574     try testWithAllSupportedPathTypes(struct {
   1575         fn impl(ctx: *TestContext) !void {
   1576             const allocator = ctx.arena.allocator();
   1577             const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
   1578             const test_content =
   1579                 \\ hello!
   1580                 \\ this is a test file
   1581             ;
   1582 
   1583             {
   1584                 var buffer: [100]u8 = undefined;
   1585                 var af = try ctx.dir.atomicFile(test_out_file, .{ .write_buffer = &buffer });
   1586                 defer af.deinit();
   1587                 try af.file_writer.interface.writeAll(test_content);
   1588                 try af.finish();
   1589             }
   1590             const content = try ctx.dir.readFileAlloc(allocator, test_out_file, 9999);
   1591             try testing.expectEqualStrings(test_content, content);
   1592 
   1593             try ctx.dir.deleteFile(test_out_file);
   1594         }
   1595     }.impl);
   1596 }
   1597 
   1598 test "open file with exclusive nonblocking lock twice" {
   1599     if (native_os == .wasi) return error.SkipZigTest;
   1600 
   1601     try testWithAllSupportedPathTypes(struct {
   1602         fn impl(ctx: *TestContext) !void {
   1603             const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
   1604 
   1605             const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
   1606             defer file1.close();
   1607 
   1608             const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
   1609             try testing.expectError(error.WouldBlock, file2);
   1610         }
   1611     }.impl);
   1612 }
   1613 
   1614 test "open file with shared and exclusive nonblocking lock" {
   1615     if (native_os == .wasi) return error.SkipZigTest;
   1616 
   1617     try testWithAllSupportedPathTypes(struct {
   1618         fn impl(ctx: *TestContext) !void {
   1619             const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
   1620 
   1621             const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
   1622             defer file1.close();
   1623 
   1624             const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
   1625             try testing.expectError(error.WouldBlock, file2);
   1626         }
   1627     }.impl);
   1628 }
   1629 
   1630 test "open file with exclusive and shared nonblocking lock" {
   1631     if (native_os == .wasi) return error.SkipZigTest;
   1632 
   1633     try testWithAllSupportedPathTypes(struct {
   1634         fn impl(ctx: *TestContext) !void {
   1635             const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
   1636 
   1637             const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
   1638             defer file1.close();
   1639 
   1640             const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
   1641             try testing.expectError(error.WouldBlock, file2);
   1642         }
   1643     }.impl);
   1644 }
   1645 
   1646 test "open file with exclusive lock twice, make sure second lock waits" {
   1647     if (builtin.single_threaded) return error.SkipZigTest;
   1648 
   1649     try testWithAllSupportedPathTypes(struct {
   1650         fn impl(ctx: *TestContext) !void {
   1651             const filename = try ctx.transformPath("file_lock_test.txt");
   1652 
   1653             const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
   1654             errdefer file.close();
   1655 
   1656             const S = struct {
   1657                 fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
   1658                     started.set();
   1659                     const file1 = try dir.createFile(path, .{ .lock = .exclusive });
   1660 
   1661                     locked.set();
   1662                     file1.close();
   1663                 }
   1664             };
   1665 
   1666             var started = std.Thread.ResetEvent{};
   1667             var locked = std.Thread.ResetEvent{};
   1668 
   1669             const t = try std.Thread.spawn(.{}, S.checkFn, .{
   1670                 &ctx.dir,
   1671                 filename,
   1672                 &started,
   1673                 &locked,
   1674             });
   1675             defer t.join();
   1676 
   1677             // Wait for the spawned thread to start trying to acquire the exclusive file lock.
   1678             // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
   1679             started.wait();
   1680             try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
   1681 
   1682             // Release the file lock which should unlock the thread to lock it and set the locked event.
   1683             file.close();
   1684             locked.wait();
   1685         }
   1686     }.impl);
   1687 }
   1688 
   1689 test "open file with exclusive nonblocking lock twice (absolute paths)" {
   1690     if (native_os == .wasi) return error.SkipZigTest;
   1691 
   1692     var random_bytes: [12]u8 = undefined;
   1693     std.crypto.random.bytes(&random_bytes);
   1694 
   1695     var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
   1696     _ = fs.base64_encoder.encode(&random_b64, &random_bytes);
   1697 
   1698     const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";
   1699 
   1700     const gpa = testing.allocator;
   1701 
   1702     const cwd = try std.process.getCwdAlloc(gpa);
   1703     defer gpa.free(cwd);
   1704 
   1705     const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path });
   1706     defer gpa.free(filename);
   1707 
   1708     defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures
   1709     const file1 = try fs.createFileAbsolute(filename, .{
   1710         .lock = .exclusive,
   1711         .lock_nonblocking = true,
   1712     });
   1713 
   1714     const file2 = fs.createFileAbsolute(filename, .{
   1715         .lock = .exclusive,
   1716         .lock_nonblocking = true,
   1717     });
   1718     file1.close();
   1719     try testing.expectError(error.WouldBlock, file2);
   1720 }
   1721 
   1722 test "read from locked file" {
   1723     try testWithAllSupportedPathTypes(struct {
   1724         fn impl(ctx: *TestContext) !void {
   1725             const filename = try ctx.transformPath("read_lock_file_test.txt");
   1726 
   1727             {
   1728                 const f = try ctx.dir.createFile(filename, .{ .read = true });
   1729                 defer f.close();
   1730                 var buffer: [1]u8 = undefined;
   1731                 _ = try f.readAll(&buffer);
   1732             }
   1733             {
   1734                 const f = try ctx.dir.createFile(filename, .{
   1735                     .read = true,
   1736                     .lock = .exclusive,
   1737                 });
   1738                 defer f.close();
   1739                 const f2 = try ctx.dir.openFile(filename, .{});
   1740                 defer f2.close();
   1741                 var buffer: [1]u8 = undefined;
   1742                 if (builtin.os.tag == .windows) {
   1743                     try std.testing.expectError(error.LockViolation, f2.readAll(&buffer));
   1744                 } else {
   1745                     try std.testing.expectEqual(0, f2.readAll(&buffer));
   1746                 }
   1747             }
   1748         }
   1749     }.impl);
   1750 }
   1751 
   1752 test "walker" {
   1753     var tmp = tmpDir(.{ .iterate = true });
   1754     defer tmp.cleanup();
   1755 
   1756     // iteration order of walker is undefined, so need lookup maps to check against
   1757 
   1758     const expected_paths = std.StaticStringMap(void).initComptime(.{
   1759         .{"dir1"},
   1760         .{"dir2"},
   1761         .{"dir3"},
   1762         .{"dir4"},
   1763         .{"dir3" ++ fs.path.sep_str ++ "sub1"},
   1764         .{"dir3" ++ fs.path.sep_str ++ "sub2"},
   1765         .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
   1766     });
   1767 
   1768     const expected_basenames = std.StaticStringMap(void).initComptime(.{
   1769         .{"dir1"},
   1770         .{"dir2"},
   1771         .{"dir3"},
   1772         .{"dir4"},
   1773         .{"sub1"},
   1774         .{"sub2"},
   1775         .{"subsub1"},
   1776     });
   1777 
   1778     for (expected_paths.keys()) |key| {
   1779         try tmp.dir.makePath(key);
   1780     }
   1781 
   1782     var walker = try tmp.dir.walk(testing.allocator);
   1783     defer walker.deinit();
   1784 
   1785     var num_walked: usize = 0;
   1786     while (try walker.next()) |entry| {
   1787         testing.expect(expected_basenames.has(entry.basename)) catch |err| {
   1788             std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
   1789             return err;
   1790         };
   1791         testing.expect(expected_paths.has(entry.path)) catch |err| {
   1792             std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
   1793             return err;
   1794         };
   1795         // make sure that the entry.dir is the containing dir
   1796         var entry_dir = try entry.dir.openDir(entry.basename, .{});
   1797         defer entry_dir.close();
   1798         num_walked += 1;
   1799     }
   1800     try testing.expectEqual(expected_paths.kvs.len, num_walked);
   1801 }
   1802 
   1803 test "walker without fully iterating" {
   1804     var tmp = tmpDir(.{ .iterate = true });
   1805     defer tmp.cleanup();
   1806 
   1807     var walker = try tmp.dir.walk(testing.allocator);
   1808     defer walker.deinit();
   1809 
   1810     // Create 2 directories inside the tmp directory, but then only iterate once before breaking.
   1811     // This ensures that walker doesn't try to close the initial directory when not fully iterating.
   1812 
   1813     try tmp.dir.makePath("a");
   1814     try tmp.dir.makePath("b");
   1815 
   1816     var num_walked: usize = 0;
   1817     while (try walker.next()) |_| {
   1818         num_walked += 1;
   1819         break;
   1820     }
   1821     try testing.expectEqual(@as(usize, 1), num_walked);
   1822 }
   1823 
   1824 test "'.' and '..' in fs.Dir functions" {
   1825     if (native_os == .windows and builtin.cpu.arch == .aarch64) {
   1826         // https://github.com/ziglang/zig/issues/17134
   1827         return error.SkipZigTest;
   1828     }
   1829 
   1830     try testWithAllSupportedPathTypes(struct {
   1831         fn impl(ctx: *TestContext) !void {
   1832             const subdir_path = try ctx.transformPath("./subdir");
   1833             const file_path = try ctx.transformPath("./subdir/../file");
   1834             const copy_path = try ctx.transformPath("./subdir/../copy");
   1835             const rename_path = try ctx.transformPath("./subdir/../rename");
   1836             const update_path = try ctx.transformPath("./subdir/../update");
   1837 
   1838             try ctx.dir.makeDir(subdir_path);
   1839             try ctx.dir.access(subdir_path, .{});
   1840             var created_subdir = try ctx.dir.openDir(subdir_path, .{});
   1841             created_subdir.close();
   1842 
   1843             const created_file = try ctx.dir.createFile(file_path, .{});
   1844             created_file.close();
   1845             try ctx.dir.access(file_path, .{});
   1846 
   1847             try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
   1848             try ctx.dir.rename(copy_path, rename_path);
   1849             const renamed_file = try ctx.dir.openFile(rename_path, .{});
   1850             renamed_file.close();
   1851             try ctx.dir.deleteFile(rename_path);
   1852 
   1853             try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });
   1854             const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
   1855             try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
   1856 
   1857             try ctx.dir.deleteDir(subdir_path);
   1858         }
   1859     }.impl);
   1860 }
   1861 
   1862 test "'.' and '..' in absolute functions" {
   1863     if (native_os == .wasi) return error.SkipZigTest;
   1864 
   1865     var tmp = tmpDir(.{});
   1866     defer tmp.cleanup();
   1867 
   1868     var arena = ArenaAllocator.init(testing.allocator);
   1869     defer arena.deinit();
   1870     const allocator = arena.allocator();
   1871 
   1872     const base_path = try tmp.dir.realpathAlloc(allocator, ".");
   1873 
   1874     const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" });
   1875     try fs.makeDirAbsolute(subdir_path);
   1876     try fs.accessAbsolute(subdir_path, .{});
   1877     var created_subdir = try fs.openDirAbsolute(subdir_path, .{});
   1878     created_subdir.close();
   1879 
   1880     const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" });
   1881     const created_file = try fs.createFileAbsolute(created_file_path, .{});
   1882     created_file.close();
   1883     try fs.accessAbsolute(created_file_path, .{});
   1884 
   1885     const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" });
   1886     try fs.copyFileAbsolute(created_file_path, copied_file_path, .{});
   1887     const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" });
   1888     try fs.renameAbsolute(copied_file_path, renamed_file_path);
   1889     const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{});
   1890     renamed_file.close();
   1891     try fs.deleteFileAbsolute(renamed_file_path);
   1892 
   1893     const update_file_path = try fs.path.join(allocator, &.{ subdir_path, "../update" });
   1894     const update_file = try fs.createFileAbsolute(update_file_path, .{});
   1895     try update_file.writeAll("something");
   1896     update_file.close();
   1897     const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});
   1898     try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
   1899 
   1900     try fs.deleteDirAbsolute(subdir_path);
   1901 }
   1902 
   1903 test "chmod" {
   1904     if (native_os == .windows or native_os == .wasi)
   1905         return error.SkipZigTest;
   1906 
   1907     var tmp = tmpDir(.{});
   1908     defer tmp.cleanup();
   1909 
   1910     const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 });
   1911     defer file.close();
   1912     try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777);
   1913 
   1914     try file.chmod(0o644);
   1915     try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);
   1916 
   1917     try tmp.dir.makeDir("test_dir");
   1918     var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
   1919     defer dir.close();
   1920 
   1921     try dir.chmod(0o700);
   1922     try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);
   1923 }
   1924 
   1925 test "chown" {
   1926     if (native_os == .windows or native_os == .wasi)
   1927         return error.SkipZigTest;
   1928 
   1929     var tmp = tmpDir(.{});
   1930     defer tmp.cleanup();
   1931 
   1932     const file = try tmp.dir.createFile("test_file", .{});
   1933     defer file.close();
   1934     try file.chown(null, null);
   1935 
   1936     try tmp.dir.makeDir("test_dir");
   1937 
   1938     var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
   1939     defer dir.close();
   1940     try dir.chown(null, null);
   1941 }
   1942 
   1943 test "delete a setAsCwd directory on Windows" {
   1944     if (native_os != .windows) return error.SkipZigTest;
   1945 
   1946     var tmp = tmpDir(.{});
   1947     // Set tmp dir as current working directory.
   1948     try tmp.dir.setAsCwd();
   1949     tmp.dir.close();
   1950     try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path));
   1951     // Now set the parent dir as the current working dir for clean up.
   1952     try tmp.parent_dir.setAsCwd();
   1953     try tmp.parent_dir.deleteTree(&tmp.sub_path);
   1954     // Close the parent "tmp" so we don't leak the HANDLE.
   1955     tmp.parent_dir.close();
   1956 }
   1957 
   1958 test "invalid UTF-8/WTF-8 paths" {
   1959     const expected_err = switch (native_os) {
   1960         .wasi => error.InvalidUtf8,
   1961         .windows => error.InvalidWtf8,
   1962         else => return error.SkipZigTest,
   1963     };
   1964 
   1965     try testWithAllSupportedPathTypes(struct {
   1966         fn impl(ctx: *TestContext) !void {
   1967             // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
   1968             const invalid_path = try ctx.transformPath("\xFF");
   1969 
   1970             try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
   1971             try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
   1972 
   1973             try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
   1974             try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
   1975 
   1976             try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
   1977             try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
   1978 
   1979             try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
   1980             try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
   1981 
   1982             try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
   1983             try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
   1984 
   1985             try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
   1986             try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
   1987 
   1988             try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
   1989             try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
   1990 
   1991             try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
   1992             try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
   1993 
   1994             try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
   1995             try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
   1996             if (native_os == .wasi) {
   1997                 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
   1998             }
   1999 
   2000             try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
   2001             try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
   2002             if (native_os == .wasi) {
   2003                 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
   2004             }
   2005 
   2006             try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
   2007             try testing.expectError(expected_err, ctx.dir.readFileAlloc(testing.allocator, invalid_path, 0));
   2008 
   2009             try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
   2010             try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
   2011 
   2012             try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" }));
   2013 
   2014             try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
   2015             try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
   2016 
   2017             try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
   2018             try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
   2019 
   2020             try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
   2021 
   2022             if (native_os != .wasi) {
   2023                 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
   2024                 try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
   2025                 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
   2026             }
   2027 
   2028             try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
   2029             try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
   2030 
   2031             if (native_os != .wasi and ctx.path_type != .relative) {
   2032                 try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
   2033                 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
   2034                 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
   2035                 try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
   2036                 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
   2037                 try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
   2038                 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
   2039                 try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
   2040                 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
   2041                 try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
   2042                 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
   2043                 try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
   2044                 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
   2045                 try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
   2046                 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
   2047                 try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
   2048                 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
   2049                 try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
   2050                 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
   2051                 var readlink_buf: [fs.max_path_bytes]u8 = undefined;
   2052                 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
   2053                 try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
   2054                 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
   2055                 try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
   2056                 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
   2057             }
   2058         }
   2059     }.impl);
   2060 }
   2061 
   2062 test "read file non vectored" {
   2063     var tmp_dir = testing.tmpDir(.{});
   2064     defer tmp_dir.cleanup();
   2065 
   2066     const contents = "hello, world!\n";
   2067 
   2068     const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
   2069     defer file.close();
   2070     {
   2071         var file_writer: std.fs.File.Writer = .init(file, &.{});
   2072         try file_writer.interface.writeAll(contents);
   2073         try file_writer.interface.flush();
   2074     }
   2075 
   2076     var file_reader: std.fs.File.Reader = .init(file, &.{});
   2077 
   2078     var write_buffer: [100]u8 = undefined;
   2079     var w: std.Io.Writer = .fixed(&write_buffer);
   2080 
   2081     var i: usize = 0;
   2082     while (true) {
   2083         i += file_reader.interface.stream(&w, .limited(3)) catch |err| switch (err) {
   2084             error.EndOfStream => break,
   2085             else => |e| return e,
   2086         };
   2087     }
   2088     try testing.expectEqualStrings(contents, w.buffered());
   2089     try testing.expectEqual(contents.len, i);
   2090 }
   2091 
   2092 test "seek keeping partial buffer" {
   2093     var tmp_dir = testing.tmpDir(.{});
   2094     defer tmp_dir.cleanup();
   2095 
   2096     const contents = "0123456789";
   2097 
   2098     const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
   2099     defer file.close();
   2100     {
   2101         var file_writer: std.fs.File.Writer = .init(file, &.{});
   2102         try file_writer.interface.writeAll(contents);
   2103         try file_writer.interface.flush();
   2104     }
   2105 
   2106     var read_buffer: [3]u8 = undefined;
   2107     var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
   2108 
   2109     try testing.expectEqual(0, file_reader.logicalPos());
   2110 
   2111     var buf: [4]u8 = undefined;
   2112     try file_reader.interface.readSliceAll(&buf);
   2113 
   2114     if (file_reader.interface.bufferedLen() != 3) {
   2115         // Pass the test if the OS doesn't give us vectored reads.
   2116         return;
   2117     }
   2118 
   2119     try testing.expectEqual(4, file_reader.logicalPos());
   2120     try testing.expectEqual(7, file_reader.pos);
   2121     try file_reader.seekTo(6);
   2122     try testing.expectEqual(6, file_reader.logicalPos());
   2123     try testing.expectEqual(7, file_reader.pos);
   2124 
   2125     try testing.expectEqualStrings("0123", &buf);
   2126 
   2127     const n = try file_reader.interface.readSliceShort(&buf);
   2128     try testing.expectEqual(4, n);
   2129 
   2130     try testing.expectEqualStrings("6789", &buf);
   2131 }