const builtin = @import("builtin"); const native_os = builtin.target.os.tag; const AtomicRmwOp = std.builtin.AtomicRmwOp; const AtomicOrder = std.builtin.AtomicOrder; const std = @import("../std.zig"); const Io = std.Io; const Dir = std.Io.Dir; const posix = std.posix; const mem = std.mem; const elf = std.elf; const linux = std.os.linux; const AT = std.posix.AT; const testing = std.testing; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualSlices = std.testing.expectEqualSlices; const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; const tmpDir = std.testing.tmpDir; test "check WASI CWD" { if (native_os == .wasi) { const cwd: Dir = .cwd(); if (cwd.handle != 3) { @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)"); } if (!builtin.link_libc) { // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls try expectEqual(3, posix.AT.FDCWD); } } } test "getrandom" { var buf_a: [50]u8 = undefined; var buf_b: [50]u8 = undefined; try posix.getrandom(&buf_a); try posix.getrandom(&buf_b); // If this test fails the chance is significantly higher that there is a bug than // that two sets of 50 bytes were equal. try expect(!mem.eql(u8, &buf_a, &buf_b)); } test "getuid" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; _ = posix.getuid(); _ = posix.geteuid(); } test "getgid" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; _ = posix.getgid(); _ = posix.getegid(); } test "sigaltstack" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; var st: posix.stack_t = undefined; try posix.sigaltstack(null, &st); // Setting a stack size less than MINSIGSTKSZ returns ENOMEM st.flags = 0; st.size = 1; try expectError(error.SizeTooSmall, posix.sigaltstack(&st, null)); } // If the type is not available use void to avoid erroring out when `iter_fn` is // analyzed const have_dl_phdr_info = posix.system.dl_phdr_info != void; const dl_phdr_info = if (have_dl_phdr_info) posix.dl_phdr_info else anyopaque; const IterFnError = error{ MissingPtLoadSegment, MissingLoad, BadElfMagic, FailedConsistencyCheck, }; fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void { _ = size; // Count how many libraries are loaded counter.* += @as(usize, 1); // The image should contain at least a PT_LOAD segment if (info.phnum < 1) return error.MissingPtLoadSegment; // Quick & dirty validation of the phdr pointers, make sure we're not // pointing to some random gibberish var i: usize = 0; var found_load = false; while (i < info.phnum) : (i += 1) { const phdr = info.phdr[i]; if (phdr.type != .LOAD) continue; const reloc_addr = info.addr + phdr.vaddr; // Find the ELF header const elf_header = @as(*elf.Ehdr, @ptrFromInt(reloc_addr - phdr.offset)); // Validate the magic if (!mem.eql(u8, elf_header.e_ident[0..4], elf.MAGIC)) return error.BadElfMagic; // Consistency check if (elf_header.e_phnum != info.phnum) return error.FailedConsistencyCheck; found_load = true; break; } if (!found_load) return error.MissingLoad; } test "dl_iterate_phdr" { if (builtin.object_format != .elf) return error.SkipZigTest; var counter: usize = 0; try posix.dl_iterate_phdr(&counter, IterFnError, iter_fn); try expect(counter != 0); } test "gethostname" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; var buf: [posix.HOST_NAME_MAX]u8 = undefined; const hostname = try posix.gethostname(&buf); try expect(hostname.len != 0); } test "pipe" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; const fds = try std.Io.Threaded.pipe2(.{}); try expect((try posix.write(fds[1], "hello")) == 5); var buf: [16]u8 = undefined; try expect((try posix.read(fds[0], buf[0..])) == 5); try expectEqualSlices(u8, buf[0..5], "hello"); posix.close(fds[1]); posix.close(fds[0]); } test "memfd_create" { const io = testing.io; // memfd_create is only supported by linux and freebsd. switch (native_os) { .linux => {}, .freebsd => { if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt) return error.SkipZigTest; }, else => return error.SkipZigTest, } const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) }; defer file.close(io); try file.writePositionalAll(io, "test", 0); var buf: [10]u8 = undefined; const bytes_read = try file.readPositionalAll(io, &buf, 0); try expect(bytes_read == 4); try expectEqualStrings("test", buf[0..4]); } test "mmap" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; const io = testing.io; var tmp = tmpDir(.{}); defer tmp.cleanup(); // Simple mmap() call with non page-aligned size { const data = try posix.mmap( null, 1234, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); defer posix.munmap(data); try expectEqual(@as(usize, 1234), data.len); // By definition the data returned by mmap is zero-filled try expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234)); // Make sure the memory is writeable as requested @memset(data, 0x55); try expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234)); } const test_out_file = "os_tmp_test"; // Must be a multiple of the page size so that the test works with mmap2 const alloc_size = 8 * std.heap.pageSize(); // Create a file used for testing mmap() calls with a file descriptor { const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); var stream = file.writer(io, &.{}); var i: usize = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { try stream.interface.writeInt(u32, @intCast(i), .little); } } // Map the whole file { const file = try tmp.dir.openFile(io, test_out_file, .{}); defer file.close(io); const data = try posix.mmap( null, alloc_size, posix.PROT.READ, .{ .TYPE = .PRIVATE }, file.handle, 0, ); defer posix.munmap(data); var stream: std.Io.Reader = .fixed(data); var i: usize = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { try expectEqual(i, try stream.takeInt(u32, .little)); } } if (builtin.cpu.arch == .hexagon) return error.SkipZigTest; // Map the upper half of the file { const file = try tmp.dir.openFile(io, test_out_file, .{}); defer file.close(io); const data = try posix.mmap( null, alloc_size / 2, posix.PROT.READ, .{ .TYPE = .PRIVATE }, file.handle, alloc_size / 2, ); defer posix.munmap(data); var stream: std.Io.Reader = .fixed(data); var i: usize = alloc_size / 2 / @sizeOf(u32); while (i < alloc_size / @sizeOf(u32)) : (i += 1) { try expectEqual(i, try stream.takeInt(u32, .little)); } } } test "fcntl" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; const io = testing.io; var tmp = tmpDir(.{}); defer tmp.cleanup(); const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); // Note: The test assumes createFile opens the file with CLOEXEC { const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); try expect((flags & posix.FD_CLOEXEC) != 0); } { _ = try posix.fcntl(file.handle, posix.F.SETFD, 0); const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); try expect((flags & posix.FD_CLOEXEC) == 0); } { _ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC); const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); try expect((flags & posix.FD_CLOEXEC) != 0); } } test "signalfd" { switch (native_os) { .linux, .illumos => {}, else => return error.SkipZigTest, } _ = &posix.signalfd; } test "sync" { if (native_os != .linux) return error.SkipZigTest; // Unfortunately, we cannot safely call `sync` or `syncfs`, because if file IO is happening // than the system can commit the results to disk, such calls could block indefinitely. _ = &posix.sync; _ = &posix.syncfs; } test "fsync" { switch (native_os) { .linux, .illumos => {}, else => return error.SkipZigTest, } const io = testing.io; var tmp = tmpDir(.{}); defer tmp.cleanup(); const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(io, test_out_file, .{}); defer file.close(io); try file.sync(io); try posix.fdatasync(file.handle); } test "getrlimit and setrlimit" { if (posix.system.rlimit_resource == void) return error.SkipZigTest; inline for (@typeInfo(posix.rlimit_resource).@"enum".fields) |field| { const resource: posix.rlimit_resource = @enumFromInt(field.value); const limit = try posix.getrlimit(resource); // XNU kernel does not support RLIMIT_STACK if a custom stack is active, // which looks to always be the case. EINVAL is returned. // See https://github.com/apple-oss-distributions/xnu/blob/5e3eaea39dcf651e66cb99ba7d70e32cc4a99587/bsd/kern/kern_resource.c#L1173 if (native_os.isDarwin() and resource == .STACK) { continue; } // On 32 bit MIPS musl includes a fix which changes limits greater than -1UL/2 to RLIM_INFINITY. // See http://git.musl-libc.org/cgit/musl/commit/src/misc/getrlimit.c?id=8258014fd1e34e942a549c88c7e022a00445c352 // // This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB. // In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM. if (builtin.cpu.arch.isMIPS() and builtin.link_libc) { if (limit.cur != linux.RLIM.INFINITY) { try posix.setrlimit(resource, limit); } } else { try posix.setrlimit(resource, limit); } } } test "sigrtmin/max" { if (native_os.isDarwin() or switch (native_os) { .wasi, .windows, .openbsd, .dragonfly => true, else => false, }) return error.SkipZigTest; try expect(posix.sigrtmin() >= 32); try expect(posix.sigrtmin() >= posix.system.sigrtmin()); try expect(posix.sigrtmin() < posix.system.sigrtmax()); } test "sigset empty/full" { if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; var set: posix.sigset_t = posix.sigemptyset(); for (1..posix.NSIG) |i| { const sig = std.enums.fromInt(posix.SIG, i) orelse continue; try expectEqual(false, posix.sigismember(&set, sig)); } // The C library can reserve some (unnamed) signals, so can't check the full // NSIG set is defined, but just test a couple: set = posix.sigfillset(); try expectEqual(true, posix.sigismember(&set, .CHLD)); try expectEqual(true, posix.sigismember(&set, .INT)); } // Some signals (i.e., 32 - 34 on glibc/musl) are not allowed to be added to a // sigset by the C library, so avoid testing them. fn reserved_signo(i: usize) bool { if (native_os.isDarwin()) return false; if (!builtin.link_libc) return false; const max = if (native_os == .netbsd) 32 else 31; if (i > max) return true; if (native_os == .openbsd or native_os == .dragonfly) return false; // no RT signals return i < posix.sigrtmin(); } test "sigset add/del" { if (native_os == .wasi or native_os == .windows) return error.SkipZigTest; var sigset: posix.sigset_t = posix.sigemptyset(); // See that none are set, then set each one, see that they're all set, then // remove them all, and then see that none are set. for (1..posix.NSIG) |i| { const sig = std.enums.fromInt(posix.SIG, i) orelse continue; try expectEqual(false, posix.sigismember(&sigset, sig)); } for (1..posix.NSIG) |i| { if (!reserved_signo(i)) { const sig = std.enums.fromInt(posix.SIG, i) orelse continue; posix.sigaddset(&sigset, sig); } } for (1..posix.NSIG) |i| { if (!reserved_signo(i)) { const sig = std.enums.fromInt(posix.SIG, i) orelse continue; try expectEqual(true, posix.sigismember(&sigset, sig)); } } for (1..posix.NSIG) |i| { if (!reserved_signo(i)) { const sig = std.enums.fromInt(posix.SIG, i) orelse continue; posix.sigdelset(&sigset, sig); } } for (1..posix.NSIG) |i| { const sig = std.enums.fromInt(posix.SIG, i) orelse continue; try expectEqual(false, posix.sigismember(&sigset, sig)); } } test "getpid" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; try expect(posix.system.getpid() != 0); } test "getppid" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; if (native_os == .plan9 and !builtin.link_libc) return error.SkipZigTest; try expect(posix.getppid() >= 0); } test "rename smoke test" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; if (native_os == .openbsd) return error.SkipZigTest; const io = testing.io; const gpa = testing.allocator; var tmp = tmpDir(.{}); defer tmp.cleanup(); const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); defer gpa.free(base_path); const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; { // Create some file using `open`. const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); defer gpa.free(file_path); const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); posix.close(fd); // Rename the file const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); defer gpa.free(new_file_path); try Io.Dir.renameAbsolute(file_path, new_file_path, io); } { // Try opening renamed file const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" }); defer gpa.free(file_path); const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); posix.close(fd); } { // Try opening original file - should fail with error.FileNotFound const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" }); defer gpa.free(file_path); try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); } { // Create some directory const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); defer gpa.free(file_path); try Io.Dir.createDirAbsolute(io, file_path, .fromMode(mode)); // Rename the directory const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); defer gpa.free(new_file_path); try Io.Dir.renameAbsolute(file_path, new_file_path, io); } { // Try opening renamed directory const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); defer gpa.free(file_path); const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); posix.close(fd); } { // Try opening original directory - should fail with error.FileNotFound const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); defer gpa.free(file_path); try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); } } test "timerfd" { if (native_os != .linux) return error.SkipZigTest; const tfd = try posix.timerfd_create(.MONOTONIC, .{ .CLOEXEC = true }); defer posix.close(tfd); // Fire event 10_000_000ns = 10ms after the posix.timerfd_settime call. var sit: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 10 * (1000 * 1000) } }; try posix.timerfd_settime(tfd, .{}, &sit, null); var fds: [1]posix.pollfd = .{.{ .fd = tfd, .events = linux.POLL.IN, .revents = 0 }}; try expectEqual(@as(usize, 1), try posix.poll(&fds, -1)); // -1 => infinite waiting const git = try posix.timerfd_gettime(tfd); const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } }; try expectEqual(expect_disarmed_timer, git); }