master
  1const std = @import("../std.zig");
  2const posix = std.posix;
  3const testing = std.testing;
  4const expect = testing.expect;
  5const expectEqual = testing.expectEqual;
  6const expectError = testing.expectError;
  7const fs = std.fs;
  8const mem = std.mem;
  9const elf = std.elf;
 10const linux = std.os.linux;
 11
 12const a = std.testing.allocator;
 13
 14const builtin = @import("builtin");
 15const AtomicRmwOp = std.builtin.AtomicRmwOp;
 16const AtomicOrder = std.builtin.AtomicOrder;
 17const native_os = builtin.target.os.tag;
 18const tmpDir = std.testing.tmpDir;
 19
 20// NOTE: several additional tests are in test/standalone/posix/.  Any tests that mutate
 21// process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there.
 22
 23// https://github.com/ziglang/zig/issues/20288
 24test "WTF-8 to WTF-16 conversion buffer overflows" {
 25    if (native_os != .windows) return error.SkipZigTest;
 26
 27    const input_wtf8 = "\u{10FFFF}" ** 16385;
 28    try expectError(error.NameTooLong, posix.chdir(input_wtf8));
 29    try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));
 30}
 31
 32test "check WASI CWD" {
 33    if (native_os == .wasi) {
 34        if (std.options.wasiCwd() != 3) {
 35            @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)");
 36        }
 37
 38        if (!builtin.link_libc) {
 39            // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls
 40            try expectEqual(3, posix.AT.FDCWD);
 41        }
 42    }
 43}
 44
 45test "open smoke test" {
 46    if (native_os == .wasi) return error.SkipZigTest;
 47    if (native_os == .windows) return error.SkipZigTest;
 48
 49    // TODO verify file attributes using `fstat`
 50
 51    var tmp = tmpDir(.{});
 52    defer tmp.cleanup();
 53
 54    const base_path = try tmp.dir.realpathAlloc(a, ".");
 55    defer a.free(base_path);
 56
 57    const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
 58
 59    {
 60        // Create some file using `open`.
 61        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
 62        defer a.free(file_path);
 63        const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
 64        posix.close(fd);
 65    }
 66
 67    {
 68        // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
 69        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
 70        defer a.free(file_path);
 71        try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode));
 72    }
 73
 74    {
 75        // Try opening without `EXCL` flag.
 76        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
 77        defer a.free(file_path);
 78        const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode);
 79        posix.close(fd);
 80    }
 81
 82    {
 83        // Try opening as a directory which should fail.
 84        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
 85        defer a.free(file_path);
 86        try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode));
 87    }
 88
 89    {
 90        // Create some directory
 91        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
 92        defer a.free(file_path);
 93        try posix.mkdir(file_path, mode);
 94    }
 95
 96    {
 97        // Open dir using `open`
 98        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
 99        defer a.free(file_path);
100        const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
101        posix.close(fd);
102    }
103
104    {
105        // Try opening as file which should fail.
106        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
107        defer a.free(file_path);
108        try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
109    }
110}
111
112test "readlink on Windows" {
113    if (native_os != .windows) return error.SkipZigTest;
114
115    try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
116    try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
117    try testReadlink("C:\\Users", "C:\\Documents and Settings");
118}
119
120fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
121    var buffer: [fs.max_path_bytes]u8 = undefined;
122    const given = try posix.readlink(symlink_path, buffer[0..]);
123    try expect(mem.eql(u8, target_path, given));
124}
125
126test "linkat with different directories" {
127    if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
128    if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
129
130    switch (native_os) {
131        .wasi, .linux, .illumos => {},
132        else => return error.SkipZigTest,
133    }
134
135    var tmp = tmpDir(.{});
136    defer tmp.cleanup();
137
138    const target_name = "link-target";
139    const link_name = "newlink";
140
141    const subdir = try tmp.dir.makeOpenPath("subdir", .{});
142
143    defer tmp.dir.deleteFile(target_name) catch {};
144    try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
145
146    // Test 1: link from file in subdir back up to target in parent directory
147    try posix.linkat(tmp.dir.fd, target_name, subdir.fd, link_name, 0);
148
149    const efd = try tmp.dir.openFile(target_name, .{});
150    defer efd.close();
151
152    const nfd = try subdir.openFile(link_name, .{});
153    defer nfd.close();
154
155    {
156        const estat = try posix.fstat(efd.handle);
157        const nstat = try posix.fstat(nfd.handle);
158        try testing.expectEqual(estat.ino, nstat.ino);
159        try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
160    }
161
162    // Test 2: remove link
163    try posix.unlinkat(subdir.fd, link_name, 0);
164
165    {
166        const estat = try posix.fstat(efd.handle);
167        try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
168    }
169}
170
171test "readlinkat" {
172    var tmp = tmpDir(.{});
173    defer tmp.cleanup();
174
175    // create file
176    try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });
177
178    // create a symbolic link
179    if (native_os == .windows) {
180        std.os.windows.CreateSymbolicLink(
181            tmp.dir.fd,
182            &[_]u16{ 'l', 'i', 'n', 'k' },
183            &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' },
184            false,
185        ) catch |err| switch (err) {
186            // Symlink requires admin privileges on windows, so this test can legitimately fail.
187            error.AccessDenied => return error.SkipZigTest,
188            else => return err,
189        };
190    } else {
191        try posix.symlinkat("file.txt", tmp.dir.fd, "link");
192    }
193
194    // read the link
195    var buffer: [fs.max_path_bytes]u8 = undefined;
196    const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]);
197    try expect(mem.eql(u8, "file.txt", read_link));
198}
199
200test "getrandom" {
201    var buf_a: [50]u8 = undefined;
202    var buf_b: [50]u8 = undefined;
203    try posix.getrandom(&buf_a);
204    try posix.getrandom(&buf_b);
205    // If this test fails the chance is significantly higher that there is a bug than
206    // that two sets of 50 bytes were equal.
207    try expect(!mem.eql(u8, &buf_a, &buf_b));
208}
209
210test "getuid" {
211    if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
212    _ = posix.getuid();
213    _ = posix.geteuid();
214}
215
216test "getgid" {
217    if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
218    _ = posix.getgid();
219    _ = posix.getegid();
220}
221
222test "sigaltstack" {
223    if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
224
225    var st: posix.stack_t = undefined;
226    try posix.sigaltstack(null, &st);
227    // Setting a stack size less than MINSIGSTKSZ returns ENOMEM
228    st.flags = 0;
229    st.size = 1;
230    try testing.expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));
231}
232
233// If the type is not available use void to avoid erroring out when `iter_fn` is
234// analyzed
235const have_dl_phdr_info = posix.system.dl_phdr_info != void;
236const dl_phdr_info = if (have_dl_phdr_info) posix.dl_phdr_info else anyopaque;
237
238const IterFnError = error{
239    MissingPtLoadSegment,
240    MissingLoad,
241    BadElfMagic,
242    FailedConsistencyCheck,
243};
244
245fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void {
246    _ = size;
247    // Count how many libraries are loaded
248    counter.* += @as(usize, 1);
249
250    // The image should contain at least a PT_LOAD segment
251    if (info.phnum < 1) return error.MissingPtLoadSegment;
252
253    // Quick & dirty validation of the phdr pointers, make sure we're not
254    // pointing to some random gibberish
255    var i: usize = 0;
256    var found_load = false;
257    while (i < info.phnum) : (i += 1) {
258        const phdr = info.phdr[i];
259
260        if (phdr.type != .LOAD) continue;
261
262        const reloc_addr = info.addr + phdr.vaddr;
263        // Find the ELF header
264        const elf_header = @as(*elf.Ehdr, @ptrFromInt(reloc_addr - phdr.offset));
265        // Validate the magic
266        if (!mem.eql(u8, elf_header.e_ident[0..4], elf.MAGIC)) return error.BadElfMagic;
267        // Consistency check
268        if (elf_header.e_phnum != info.phnum) return error.FailedConsistencyCheck;
269
270        found_load = true;
271        break;
272    }
273
274    if (!found_load) return error.MissingLoad;
275}
276
277test "dl_iterate_phdr" {
278    if (builtin.object_format != .elf) return error.SkipZigTest;
279
280    var counter: usize = 0;
281    try posix.dl_iterate_phdr(&counter, IterFnError, iter_fn);
282    try expect(counter != 0);
283}
284
285test "gethostname" {
286    if (native_os == .windows or native_os == .wasi)
287        return error.SkipZigTest;
288
289    var buf: [posix.HOST_NAME_MAX]u8 = undefined;
290    const hostname = try posix.gethostname(&buf);
291    try expect(hostname.len != 0);
292}
293
294test "pipe" {
295    if (native_os == .windows or native_os == .wasi)
296        return error.SkipZigTest;
297
298    const fds = try posix.pipe();
299    try expect((try posix.write(fds[1], "hello")) == 5);
300    var buf: [16]u8 = undefined;
301    try expect((try posix.read(fds[0], buf[0..])) == 5);
302    try testing.expectEqualSlices(u8, buf[0..5], "hello");
303    posix.close(fds[1]);
304    posix.close(fds[0]);
305}
306
307test "argsAlloc" {
308    const args = try std.process.argsAlloc(std.testing.allocator);
309    std.process.argsFree(std.testing.allocator, args);
310}
311
312test "memfd_create" {
313    // memfd_create is only supported by linux and freebsd.
314    switch (native_os) {
315        .linux => {},
316        .freebsd => {
317            if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
318                return error.SkipZigTest;
319        },
320        else => return error.SkipZigTest,
321    }
322
323    const fd = try posix.memfd_create("test", 0);
324    defer posix.close(fd);
325    try expect((try posix.write(fd, "test")) == 4);
326    try posix.lseek_SET(fd, 0);
327
328    var buf: [10]u8 = undefined;
329    const bytes_read = try posix.read(fd, &buf);
330    try expect(bytes_read == 4);
331    try expect(mem.eql(u8, buf[0..4], "test"));
332}
333
334test "mmap" {
335    if (native_os == .windows or native_os == .wasi)
336        return error.SkipZigTest;
337
338    var tmp = tmpDir(.{});
339    defer tmp.cleanup();
340
341    // Simple mmap() call with non page-aligned size
342    {
343        const data = try posix.mmap(
344            null,
345            1234,
346            posix.PROT.READ | posix.PROT.WRITE,
347            .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
348            -1,
349            0,
350        );
351        defer posix.munmap(data);
352
353        try testing.expectEqual(@as(usize, 1234), data.len);
354
355        // By definition the data returned by mmap is zero-filled
356        try testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234));
357
358        // Make sure the memory is writeable as requested
359        @memset(data, 0x55);
360        try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
361    }
362
363    const test_out_file = "os_tmp_test";
364    // Must be a multiple of the page size so that the test works with mmap2
365    const alloc_size = 8 * std.heap.pageSize();
366
367    // Create a file used for testing mmap() calls with a file descriptor
368    {
369        const file = try tmp.dir.createFile(test_out_file, .{});
370        defer file.close();
371
372        var stream = file.writer(&.{});
373
374        var i: usize = 0;
375        while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
376            try stream.interface.writeInt(u32, @intCast(i), .little);
377        }
378    }
379
380    // Map the whole file
381    {
382        const file = try tmp.dir.openFile(test_out_file, .{});
383        defer file.close();
384
385        const data = try posix.mmap(
386            null,
387            alloc_size,
388            posix.PROT.READ,
389            .{ .TYPE = .PRIVATE },
390            file.handle,
391            0,
392        );
393        defer posix.munmap(data);
394
395        var stream: std.Io.Reader = .fixed(data);
396
397        var i: usize = 0;
398        while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
399            try testing.expectEqual(i, try stream.takeInt(u32, .little));
400        }
401    }
402
403    if (builtin.cpu.arch == .hexagon) return error.SkipZigTest;
404
405    // Map the upper half of the file
406    {
407        const file = try tmp.dir.openFile(test_out_file, .{});
408        defer file.close();
409
410        const data = try posix.mmap(
411            null,
412            alloc_size / 2,
413            posix.PROT.READ,
414            .{ .TYPE = .PRIVATE },
415            file.handle,
416            alloc_size / 2,
417        );
418        defer posix.munmap(data);
419
420        var stream: std.Io.Reader = .fixed(data);
421
422        var i: usize = alloc_size / 2 / @sizeOf(u32);
423        while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
424            try testing.expectEqual(i, try stream.takeInt(u32, .little));
425        }
426    }
427}
428
429test "fcntl" {
430    if (native_os == .windows or native_os == .wasi)
431        return error.SkipZigTest;
432
433    var tmp = tmpDir(.{});
434    defer tmp.cleanup();
435
436    const test_out_file = "os_tmp_test";
437
438    const file = try tmp.dir.createFile(test_out_file, .{});
439    defer file.close();
440
441    // Note: The test assumes createFile opens the file with CLOEXEC
442    {
443        const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
444        try expect((flags & posix.FD_CLOEXEC) != 0);
445    }
446    {
447        _ = try posix.fcntl(file.handle, posix.F.SETFD, 0);
448        const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
449        try expect((flags & posix.FD_CLOEXEC) == 0);
450    }
451    {
452        _ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC);
453        const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0);
454        try expect((flags & posix.FD_CLOEXEC) != 0);
455    }
456}
457
458test "signalfd" {
459    switch (native_os) {
460        .linux, .illumos => {},
461        else => return error.SkipZigTest,
462    }
463    _ = &posix.signalfd;
464}
465
466test "sync" {
467    if (native_os != .linux)
468        return error.SkipZigTest;
469
470    // Unfortunately, we cannot safely call `sync` or `syncfs`, because if file IO is happening
471    // than the system can commit the results to disk, such calls could block indefinitely.
472
473    _ = &posix.sync;
474    _ = &posix.syncfs;
475}
476
477test "fsync" {
478    switch (native_os) {
479        .linux, .windows, .illumos => {},
480        else => return error.SkipZigTest,
481    }
482
483    var tmp = tmpDir(.{});
484    defer tmp.cleanup();
485
486    const test_out_file = "os_tmp_test";
487    const file = try tmp.dir.createFile(test_out_file, .{});
488    defer file.close();
489
490    try posix.fsync(file.handle);
491    try posix.fdatasync(file.handle);
492}
493
494test "getrlimit and setrlimit" {
495    if (posix.system.rlimit_resource == void) return error.SkipZigTest;
496
497    inline for (@typeInfo(posix.rlimit_resource).@"enum".fields) |field| {
498        const resource: posix.rlimit_resource = @enumFromInt(field.value);
499        const limit = try posix.getrlimit(resource);
500
501        // XNU kernel does not support RLIMIT_STACK if a custom stack is active,
502        // which looks to always be the case. EINVAL is returned.
503        // See https://github.com/apple-oss-distributions/xnu/blob/5e3eaea39dcf651e66cb99ba7d70e32cc4a99587/bsd/kern/kern_resource.c#L1173
504        if (native_os.isDarwin() and resource == .STACK) {
505            continue;
506        }
507
508        // On 32 bit MIPS musl includes a fix which changes limits greater than -1UL/2 to RLIM_INFINITY.
509        // See http://git.musl-libc.org/cgit/musl/commit/src/misc/getrlimit.c?id=8258014fd1e34e942a549c88c7e022a00445c352
510        //
511        // This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB.
512        // In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM.
513        if (builtin.cpu.arch.isMIPS() and builtin.link_libc) {
514            if (limit.cur != linux.RLIM.INFINITY) {
515                try posix.setrlimit(resource, limit);
516            }
517        } else {
518            try posix.setrlimit(resource, limit);
519        }
520    }
521}
522
523test "sigrtmin/max" {
524    if (native_os == .wasi or native_os == .windows or native_os.isDarwin()) {
525        return error.SkipZigTest;
526    }
527
528    try std.testing.expect(posix.sigrtmin() >= 32);
529    try std.testing.expect(posix.sigrtmin() >= posix.system.sigrtmin());
530    try std.testing.expect(posix.sigrtmin() < posix.system.sigrtmax());
531}
532
533test "sigset empty/full" {
534    if (native_os == .wasi or native_os == .windows)
535        return error.SkipZigTest;
536
537    var set: posix.sigset_t = posix.sigemptyset();
538    for (1..posix.NSIG) |i| {
539        const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
540        try expectEqual(false, posix.sigismember(&set, sig));
541    }
542
543    // The C library can reserve some (unnamed) signals, so can't check the full
544    // NSIG set is defined, but just test a couple:
545    set = posix.sigfillset();
546    try expectEqual(true, posix.sigismember(&set, .CHLD));
547    try expectEqual(true, posix.sigismember(&set, .INT));
548}
549
550// Some signals (i.e., 32 - 34 on glibc/musl) are not allowed to be added to a
551// sigset by the C library, so avoid testing them.
552fn reserved_signo(i: usize) bool {
553    if (native_os.isDarwin()) return false;
554    if (!builtin.link_libc) return false;
555    const max = if (native_os == .netbsd) 32 else 31;
556    return i > max and i < posix.sigrtmin();
557}
558
559test "sigset add/del" {
560    if (native_os == .wasi or native_os == .windows)
561        return error.SkipZigTest;
562
563    var sigset: posix.sigset_t = posix.sigemptyset();
564
565    // See that none are set, then set each one, see that they're all set, then
566    // remove them all, and then see that none are set.
567    for (1..posix.NSIG) |i| {
568        const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
569        try expectEqual(false, posix.sigismember(&sigset, sig));
570    }
571    for (1..posix.NSIG) |i| {
572        if (!reserved_signo(i)) {
573            const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
574            posix.sigaddset(&sigset, sig);
575        }
576    }
577    for (1..posix.NSIG) |i| {
578        if (!reserved_signo(i)) {
579            const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
580            try expectEqual(true, posix.sigismember(&sigset, sig));
581        }
582    }
583    for (1..posix.NSIG) |i| {
584        if (!reserved_signo(i)) {
585            const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
586            posix.sigdelset(&sigset, sig);
587        }
588    }
589    for (1..posix.NSIG) |i| {
590        const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
591        try expectEqual(false, posix.sigismember(&sigset, sig));
592    }
593}
594
595test "dup & dup2" {
596    switch (native_os) {
597        .linux, .illumos => {},
598        else => return error.SkipZigTest,
599    }
600
601    var tmp = tmpDir(.{});
602    defer tmp.cleanup();
603
604    {
605        var file = try tmp.dir.createFile("os_dup_test", .{});
606        defer file.close();
607
608        var duped = std.fs.File{ .handle = try posix.dup(file.handle) };
609        defer duped.close();
610        try duped.writeAll("dup");
611
612        // Tests aren't run in parallel so using the next fd shouldn't be an issue.
613        const new_fd = duped.handle + 1;
614        try posix.dup2(file.handle, new_fd);
615        var dup2ed = std.fs.File{ .handle = new_fd };
616        defer dup2ed.close();
617        try dup2ed.writeAll("dup2");
618    }
619
620    var buffer: [8]u8 = undefined;
621    try testing.expectEqualStrings("dupdup2", try tmp.dir.readFile("os_dup_test", &buffer));
622}
623
624test "getpid" {
625    if (native_os == .wasi) return error.SkipZigTest;
626    if (native_os == .windows) return error.SkipZigTest;
627
628    try expect(posix.getpid() != 0);
629}
630
631test "getppid" {
632    if (native_os == .wasi) return error.SkipZigTest;
633    if (native_os == .windows) return error.SkipZigTest;
634    if (native_os == .plan9 and !builtin.link_libc) return error.SkipZigTest;
635
636    try expect(posix.getppid() >= 0);
637}
638
639test "writev longer than IOV_MAX" {
640    if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
641
642    var tmp = tmpDir(.{});
643    defer tmp.cleanup();
644
645    var file = try tmp.dir.createFile("pwritev", .{});
646    defer file.close();
647
648    const iovecs = [_]posix.iovec_const{.{ .base = "a", .len = 1 }} ** (posix.IOV_MAX + 1);
649    const amt = try file.writev(&iovecs);
650    try testing.expectEqual(@as(usize, posix.IOV_MAX), amt);
651}
652
653test "POSIX file locking with fcntl" {
654    if (native_os == .windows or native_os == .wasi) {
655        // Not POSIX.
656        return error.SkipZigTest;
657    }
658
659    if (true) {
660        // https://github.com/ziglang/zig/issues/11074
661        return error.SkipZigTest;
662    }
663
664    var tmp = tmpDir(.{});
665    defer tmp.cleanup();
666
667    // Create a temporary lock file
668    var file = try tmp.dir.createFile("lock", .{ .read = true });
669    defer file.close();
670    try file.setEndPos(2);
671    const fd = file.handle;
672
673    // Place an exclusive lock on the first byte, and a shared lock on the second byte:
674    var struct_flock = std.mem.zeroInit(posix.Flock, .{ .type = posix.F.WRLCK });
675    _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
676    struct_flock.start = 1;
677    struct_flock.type = posix.F.RDLCK;
678    _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
679
680    // Check the locks in a child process:
681    const pid = try posix.fork();
682    if (pid == 0) {
683        // child expects be denied the exclusive lock:
684        struct_flock.start = 0;
685        struct_flock.type = posix.F.WRLCK;
686        try expectError(error.Locked, posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)));
687        // child expects to get the shared lock:
688        struct_flock.start = 1;
689        struct_flock.type = posix.F.RDLCK;
690        _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
691        // child waits for the exclusive lock in order to test deadlock:
692        struct_flock.start = 0;
693        struct_flock.type = posix.F.WRLCK;
694        _ = try posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock));
695        // child exits without continuing:
696        posix.exit(0);
697    } else {
698        // parent waits for child to get shared lock:
699        std.Thread.sleep(1 * std.time.ns_per_ms);
700        // parent expects deadlock when attempting to upgrade the shared lock to exclusive:
701        struct_flock.start = 1;
702        struct_flock.type = posix.F.WRLCK;
703        try expectError(error.DeadLock, posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock)));
704        // parent releases exclusive lock:
705        struct_flock.start = 0;
706        struct_flock.type = posix.F.UNLCK;
707        _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
708        // parent releases shared lock:
709        struct_flock.start = 1;
710        struct_flock.type = posix.F.UNLCK;
711        _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
712        // parent waits for child:
713        const result = posix.waitpid(pid, 0);
714        try expect(result.status == 0 * 256);
715    }
716}
717
718test "rename smoke test" {
719    if (native_os == .wasi) return error.SkipZigTest;
720    if (native_os == .windows) return error.SkipZigTest;
721
722    var tmp = tmpDir(.{});
723    defer tmp.cleanup();
724
725    const base_path = try tmp.dir.realpathAlloc(a, ".");
726    defer a.free(base_path);
727
728    const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
729
730    {
731        // Create some file using `open`.
732        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
733        defer a.free(file_path);
734        const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
735        posix.close(fd);
736
737        // Rename the file
738        const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });
739        defer a.free(new_file_path);
740        try posix.rename(file_path, new_file_path);
741    }
742
743    {
744        // Try opening renamed file
745        const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });
746        defer a.free(file_path);
747        const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);
748        posix.close(fd);
749    }
750
751    {
752        // Try opening original file - should fail with error.FileNotFound
753        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
754        defer a.free(file_path);
755        try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
756    }
757
758    {
759        // Create some directory
760        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
761        defer a.free(file_path);
762        try posix.mkdir(file_path, mode);
763
764        // Rename the directory
765        const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" });
766        defer a.free(new_file_path);
767        try posix.rename(file_path, new_file_path);
768    }
769
770    {
771        // Try opening renamed directory
772        const file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" });
773        defer a.free(file_path);
774        const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
775        posix.close(fd);
776    }
777
778    {
779        // Try opening original directory - should fail with error.FileNotFound
780        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
781        defer a.free(file_path);
782        try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));
783    }
784}
785
786test "access smoke test" {
787    if (native_os == .wasi) return error.SkipZigTest;
788    if (native_os == .windows) return error.SkipZigTest;
789
790    var tmp = tmpDir(.{});
791    defer tmp.cleanup();
792
793    const base_path = try tmp.dir.realpathAlloc(a, ".");
794    defer a.free(base_path);
795
796    const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
797    {
798        // Create some file using `open`.
799        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
800        defer a.free(file_path);
801        const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
802        posix.close(fd);
803    }
804
805    {
806        // Try to access() the file
807        const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
808        defer a.free(file_path);
809        if (native_os == .windows) {
810            try posix.access(file_path, posix.F_OK);
811        } else {
812            try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK);
813        }
814    }
815
816    {
817        // Try to access() a non-existent file - should fail with error.FileNotFound
818        const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });
819        defer a.free(file_path);
820        try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK));
821    }
822
823    {
824        // Create some directory
825        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
826        defer a.free(file_path);
827        try posix.mkdir(file_path, mode);
828    }
829
830    {
831        // Try to access() the directory
832        const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
833        defer a.free(file_path);
834
835        try posix.access(file_path, posix.F_OK);
836    }
837}
838
839test "timerfd" {
840    if (native_os != .linux) return error.SkipZigTest;
841
842    const tfd = try posix.timerfd_create(.MONOTONIC, .{ .CLOEXEC = true });
843    defer posix.close(tfd);
844
845    // Fire event 10_000_000ns = 10ms after the posix.timerfd_settime call.
846    var sit: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 10 * (1000 * 1000) } };
847    try posix.timerfd_settime(tfd, .{}, &sit, null);
848
849    var fds: [1]posix.pollfd = .{.{ .fd = tfd, .events = linux.POLL.IN, .revents = 0 }};
850    try expectEqual(@as(usize, 1), try posix.poll(&fds, -1)); // -1 => infinite waiting
851
852    const git = try posix.timerfd_gettime(tfd);
853    const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } };
854    try expectEqual(expect_disarmed_timer, git);
855}
856
857test "isatty" {
858    var tmp = tmpDir(.{});
859    defer tmp.cleanup();
860
861    var file = try tmp.dir.createFile("foo", .{});
862    defer file.close();
863
864    try expectEqual(posix.isatty(file.handle), false);
865}
866
867test "pread with empty buffer" {
868    var tmp = tmpDir(.{});
869    defer tmp.cleanup();
870
871    var file = try tmp.dir.createFile("pread_empty", .{ .read = true });
872    defer file.close();
873
874    const bytes = try a.alloc(u8, 0);
875    defer a.free(bytes);
876
877    const rc = try posix.pread(file.handle, bytes, 0);
878    try expectEqual(rc, 0);
879}
880
881test "write with empty buffer" {
882    var tmp = tmpDir(.{});
883    defer tmp.cleanup();
884
885    var file = try tmp.dir.createFile("write_empty", .{});
886    defer file.close();
887
888    const bytes = try a.alloc(u8, 0);
889    defer a.free(bytes);
890
891    const rc = try posix.write(file.handle, bytes);
892    try expectEqual(rc, 0);
893}
894
895test "pwrite with empty buffer" {
896    var tmp = tmpDir(.{});
897    defer tmp.cleanup();
898
899    var file = try tmp.dir.createFile("pwrite_empty", .{});
900    defer file.close();
901
902    const bytes = try a.alloc(u8, 0);
903    defer a.free(bytes);
904
905    const rc = try posix.pwrite(file.handle, bytes, 0);
906    try expectEqual(rc, 0);
907}
908
909fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
910    const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW);
911    try expectEqual(mode, st.mode & 0b111_111_111);
912}
913
914test "fchmodat smoke test" {
915    if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23808
916
917    if (!std.fs.has_executable_bit) return error.SkipZigTest;
918
919    var tmp = tmpDir(.{});
920    defer tmp.cleanup();
921
922    try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.fd, "regfile", 0o666, 0));
923    const fd = try posix.openat(
924        tmp.dir.fd,
925        "regfile",
926        .{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true },
927        0o644,
928    );
929    posix.close(fd);
930
931    if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
932
933    try posix.symlinkat("regfile", tmp.dir.fd, "symlink");
934    const sym_mode = blk: {
935        const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW);
936        break :blk st.mode & 0b111_111_111;
937    };
938
939    try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
940    try expectMode(tmp.dir.fd, "regfile", 0o640);
941    try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW);
942    try expectMode(tmp.dir.fd, "regfile", 0o600);
943
944    try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0);
945    try expectMode(tmp.dir.fd, "regfile", 0o640);
946    try expectMode(tmp.dir.fd, "symlink", sym_mode);
947
948    var test_link = true;
949    posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) {
950        error.OperationNotSupported => test_link = false,
951        else => |e| return e,
952    };
953    if (test_link)
954        try expectMode(tmp.dir.fd, "symlink", 0o600);
955    try expectMode(tmp.dir.fd, "regfile", 0o640);
956}
957
958const CommonOpenFlags = packed struct {
959    ACCMODE: posix.ACCMODE = .RDONLY,
960    CREAT: bool = false,
961    EXCL: bool = false,
962    LARGEFILE: bool = false,
963    DIRECTORY: bool = false,
964    CLOEXEC: bool = false,
965    NONBLOCK: bool = false,
966
967    pub fn lower(cof: CommonOpenFlags) posix.O {
968        var result: posix.O = if (native_os == .wasi) .{
969            .read = cof.ACCMODE != .WRONLY,
970            .write = cof.ACCMODE != .RDONLY,
971        } else .{
972            .ACCMODE = cof.ACCMODE,
973        };
974        result.CREAT = cof.CREAT;
975        result.EXCL = cof.EXCL;
976        result.DIRECTORY = cof.DIRECTORY;
977        result.NONBLOCK = cof.NONBLOCK;
978        if (@hasField(posix.O, "CLOEXEC")) result.CLOEXEC = cof.CLOEXEC;
979        if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;
980        return result;
981    }
982};