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