aboutsummaryrefslogtreecommitdiff
path: root/lib/std/posix/test.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/posix/test.zig')
-rw-r--r--lib/std/posix/test.zig1317
1 files changed, 1317 insertions, 0 deletions
diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig
new file mode 100644
index 0000000000..1020bef4b7
--- /dev/null
+++ b/lib/std/posix/test.zig
@@ -0,0 +1,1317 @@
+const std = @import("../std.zig");
+const posix = std.posix;
+const testing = std.testing;
+const expect = testing.expect;
+const expectEqual = testing.expectEqual;
+const expectError = testing.expectError;
+const io = std.io;
+const fs = std.fs;
+const mem = std.mem;
+const elf = std.elf;
+const File = std.fs.File;
+const Thread = std.Thread;
+const linux = std.os.linux;
+
+const a = std.testing.allocator;
+
+const builtin = @import("builtin");
+const AtomicRmwOp = std.builtin.AtomicRmwOp;
+const AtomicOrder = std.builtin.AtomicOrder;
+const native_os = builtin.target.os.tag;
+const tmpDir = std.testing.tmpDir;
+const Dir = std.fs.Dir;
+const ArenaAllocator = std.heap.ArenaAllocator;
+
+test "chdir smoke test" {
+ if (native_os == .wasi) return error.SkipZigTest;
+
+ if (true) {
+ // https://github.com/ziglang/zig/issues/14968
+ return error.SkipZigTest;
+ }
+
+ // Get current working directory path
+ var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const old_cwd = try posix.getcwd(old_cwd_buf[0..]);
+
+ {
+ // Firstly, changing to itself should have no effect
+ try posix.chdir(old_cwd);
+ var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
+ try expect(mem.eql(u8, old_cwd, new_cwd));
+ }
+
+ // Next, change current working directory to one level above
+ if (native_os != .wasi) { // WASI does not support navigating outside of Preopens
+ const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
+ try posix.chdir(parent);
+
+ // Restore cwd because process may have other tests that do not tolerate chdir.
+ defer posix.chdir(old_cwd) catch unreachable;
+
+ var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
+ try expect(mem.eql(u8, parent, new_cwd));
+ }
+
+ // Next, change current working directory to a temp directory one level below
+ {
+ // Create a tmp directory
+ var tmp_dir_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const tmp_dir_path = path: {
+ var allocator = std.heap.FixedBufferAllocator.init(&tmp_dir_buf);
+ break :path try fs.path.resolve(allocator.allocator(), &[_][]const u8{ old_cwd, "zig-test-tmp" });
+ };
+ var tmp_dir = try fs.cwd().makeOpenPath("zig-test-tmp", .{});
+
+ // Change current working directory to tmp directory
+ try posix.chdir("zig-test-tmp");
+
+ var new_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const new_cwd = try posix.getcwd(new_cwd_buf[0..]);
+
+ // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
+ var resolved_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const resolved_cwd = path: {
+ var allocator = std.heap.FixedBufferAllocator.init(&resolved_cwd_buf);
+ break :path try fs.path.resolve(allocator.allocator(), &[_][]const u8{new_cwd});
+ };
+ try expect(mem.eql(u8, tmp_dir_path, resolved_cwd));
+
+ // Restore cwd because process may have other tests that do not tolerate chdir.
+ tmp_dir.close();
+ posix.chdir(old_cwd) catch unreachable;
+ try fs.cwd().deleteDir("zig-test-tmp");
+ }
+}
+
+test "open smoke test" {
+ if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .windows) return error.SkipZigTest;
+
+ // TODO verify file attributes using `fstat`
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: posix.fd_t = undefined;
+ const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
+ posix.close(fd);
+
+ // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode));
+
+ // Try opening without `EXCL` flag.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode);
+ posix.close(fd);
+
+ // Try opening as a directory which should fail.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode));
+
+ // Create some directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try posix.mkdir(file_path, mode);
+
+ // Open dir using `open`
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
+ posix.close(fd);
+
+ // Try opening as file which should fail.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
+}
+
+test "openat smoke test" {
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .windows) return error.SkipZigTest;
+
+ // TODO verify file attributes using `fstatat`
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var fd: posix.fd_t = undefined;
+ const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
+
+ // Create some file using `openat`.
+ fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
+ .ACCMODE = .RDWR,
+ .CREAT = true,
+ .EXCL = true,
+ }), mode);
+ posix.close(fd);
+
+ // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
+ try expectError(error.PathAlreadyExists, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
+ .ACCMODE = .RDWR,
+ .CREAT = true,
+ .EXCL = true,
+ }), mode));
+
+ // Try opening without `EXCL` flag.
+ fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
+ .ACCMODE = .RDWR,
+ .CREAT = true,
+ }), mode);
+ posix.close(fd);
+
+ // Try opening as a directory which should fail.
+ try expectError(error.NotDir, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
+ .ACCMODE = .RDWR,
+ .DIRECTORY = true,
+ }), mode));
+
+ // Create some directory
+ try posix.mkdirat(tmp.dir.fd, "some_dir", mode);
+
+ // Open dir using `open`
+ fd = try posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
+ .ACCMODE = .RDONLY,
+ .DIRECTORY = true,
+ }), mode);
+ posix.close(fd);
+
+ // Try opening as file which should fail.
+ try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
+ .ACCMODE = .RDWR,
+ }), mode));
+}
+
+test "symlink with relative paths" {
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+
+ if (true) {
+ // https://github.com/ziglang/zig/issues/14968
+ return error.SkipZigTest;
+ }
+ const cwd = fs.cwd();
+ cwd.deleteFile("file.txt") catch {};
+ cwd.deleteFile("symlinked") catch {};
+
+ // First, try relative paths in cwd
+ try cwd.writeFile("file.txt", "nonsense");
+
+ if (native_os == .windows) {
+ std.os.windows.CreateSymbolicLink(
+ cwd.fd,
+ &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' },
+ &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' },
+ false,
+ ) catch |err| switch (err) {
+ // Symlink requires admin privileges on windows, so this test can legitimately fail.
+ error.AccessDenied => {
+ try cwd.deleteFile("file.txt");
+ try cwd.deleteFile("symlinked");
+ return error.SkipZigTest;
+ },
+ else => return err,
+ };
+ } else {
+ try posix.symlink("file.txt", "symlinked");
+ }
+
+ var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const given = try posix.readlink("symlinked", buffer[0..]);
+ try expect(mem.eql(u8, "file.txt", given));
+
+ try cwd.deleteFile("file.txt");
+ try cwd.deleteFile("symlinked");
+}
+
+test "readlink on Windows" {
+ if (native_os != .windows) return error.SkipZigTest;
+
+ try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
+ try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
+ try testReadlink("C:\\Users", "C:\\Documents and Settings");
+}
+
+fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
+ var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const given = try posix.readlink(symlink_path, buffer[0..]);
+ try expect(mem.eql(u8, target_path, given));
+}
+
+test "link with relative paths" {
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+
+ switch (native_os) {
+ .wasi, .linux, .solaris, .illumos => {},
+ else => return error.SkipZigTest,
+ }
+ if (true) {
+ // https://github.com/ziglang/zig/issues/14968
+ return error.SkipZigTest;
+ }
+ var cwd = fs.cwd();
+
+ cwd.deleteFile("example.txt") catch {};
+ cwd.deleteFile("new.txt") catch {};
+
+ try cwd.writeFile("example.txt", "example");
+ try posix.link("example.txt", "new.txt", 0);
+
+ const efd = try cwd.openFile("example.txt", .{});
+ defer efd.close();
+
+ const nfd = try cwd.openFile("new.txt", .{});
+ defer nfd.close();
+
+ {
+ const estat = try posix.fstat(efd.handle);
+ const nstat = try posix.fstat(nfd.handle);
+
+ try testing.expectEqual(estat.ino, nstat.ino);
+ try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
+ }
+
+ try posix.unlink("new.txt");
+
+ {
+ const estat = try posix.fstat(efd.handle);
+ try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
+ }
+
+ try cwd.deleteFile("example.txt");
+}
+
+test "linkat with different directories" {
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+
+ switch (native_os) {
+ .wasi, .linux, .solaris, .illumos => {},
+ else => return error.SkipZigTest,
+ }
+ if (true) {
+ // https://github.com/ziglang/zig/issues/14968
+ return error.SkipZigTest;
+ }
+ var cwd = fs.cwd();
+ var tmp = tmpDir(.{});
+
+ cwd.deleteFile("example.txt") catch {};
+ tmp.dir.deleteFile("new.txt") catch {};
+
+ try cwd.writeFile("example.txt", "example");
+ try posix.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0);
+
+ const efd = try cwd.openFile("example.txt", .{});
+ defer efd.close();
+
+ const nfd = try tmp.dir.openFile("new.txt", .{});
+
+ {
+ defer nfd.close();
+ const estat = try posix.fstat(efd.handle);
+ const nstat = try posix.fstat(nfd.handle);
+
+ try testing.expectEqual(estat.ino, nstat.ino);
+ try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
+ }
+
+ try posix.unlinkat(tmp.dir.fd, "new.txt", 0);
+
+ {
+ const estat = try posix.fstat(efd.handle);
+ try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
+ }
+
+ try cwd.deleteFile("example.txt");
+}
+
+test "fstatat" {
+ // enable when `fstat` and `fstatat` are implemented on Windows
+ if (native_os == .windows) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // create dummy file
+ const contents = "nonsense";
+ try tmp.dir.writeFile("file.txt", contents);
+
+ // fetch file's info on the opened fd directly
+ const file = try tmp.dir.openFile("file.txt", .{});
+ const stat = try posix.fstat(file.handle);
+ defer file.close();
+
+ // now repeat but using `fstatat` instead
+ const flags = if (native_os == .wasi) 0x0 else posix.AT.SYMLINK_NOFOLLOW;
+ const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags);
+ try expectEqual(stat, statat);
+}
+
+test "readlinkat" {
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // create file
+ try tmp.dir.writeFile("file.txt", "nonsense");
+
+ // create a symbolic link
+ if (native_os == .windows) {
+ std.os.windows.CreateSymbolicLink(
+ tmp.dir.fd,
+ &[_]u16{ 'l', 'i', 'n', 'k' },
+ &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' },
+ false,
+ ) catch |err| switch (err) {
+ // Symlink requires admin privileges on windows, so this test can legitimately fail.
+ error.AccessDenied => return error.SkipZigTest,
+ else => return err,
+ };
+ } else {
+ try posix.symlinkat("file.txt", tmp.dir.fd, "link");
+ }
+
+ // read the link
+ var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]);
+ try expect(mem.eql(u8, "file.txt", read_link));
+}
+
+fn testThreadIdFn(thread_id: *Thread.Id) void {
+ thread_id.* = Thread.getCurrentId();
+}
+
+test "Thread.getCurrentId" {
+ if (builtin.single_threaded) return error.SkipZigTest;
+
+ var thread_current_id: Thread.Id = undefined;
+ const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
+ thread.join();
+ try expect(Thread.getCurrentId() != thread_current_id);
+}
+
+test "spawn threads" {
+ if (builtin.single_threaded) return error.SkipZigTest;
+
+ var shared_ctx: i32 = 1;
+
+ const thread1 = try Thread.spawn(.{}, start1, .{});
+ const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx});
+ const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx});
+ const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx});
+
+ thread1.join();
+ thread2.join();
+ thread3.join();
+ thread4.join();
+
+ try expect(shared_ctx == 4);
+}
+
+fn start1() u8 {
+ return 0;
+}
+
+fn start2(ctx: *i32) u8 {
+ _ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.seq_cst);
+ return 0;
+}
+
+test "cpu count" {
+ if (native_os == .wasi) return error.SkipZigTest;
+
+ const cpu_count = try Thread.getCpuCount();
+ try expect(cpu_count >= 1);
+}
+
+test "thread local storage" {
+ if (builtin.single_threaded) return error.SkipZigTest;
+
+ const thread1 = try Thread.spawn(.{}, testTls, .{});
+ const thread2 = try Thread.spawn(.{}, testTls, .{});
+ try testTls();
+ thread1.join();
+ thread2.join();
+}
+
+threadlocal var x: i32 = 1234;
+fn testTls() !void {
+ if (x != 1234) return error.TlsBadStartValue;
+ x += 1;
+ if (x != 1235) return error.TlsBadEndValue;
+}
+
+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 "getcwd" {
+ // at least call it so it gets compiled
+ var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ _ = posix.getcwd(&buf) catch undefined;
+}
+
+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 testing.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 dl_phdr_info = if (@hasDecl(posix.system, "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.dlpi_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.dlpi_phnum) : (i += 1) {
+ const phdr = info.dlpi_phdr[i];
+
+ if (phdr.p_type != elf.PT_LOAD) continue;
+
+ const reloc_addr = info.dlpi_addr + phdr.p_vaddr;
+ // Find the ELF header
+ const elf_header = @as(*elf.Ehdr, @ptrFromInt(reloc_addr - phdr.p_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.dlpi_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 posix.pipe();
+ 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 testing.expectEqualSlices(u8, buf[0..5], "hello");
+ posix.close(fds[1]);
+ posix.close(fds[0]);
+}
+
+test "argsAlloc" {
+ const args = try std.process.argsAlloc(std.testing.allocator);
+ std.process.argsFree(std.testing.allocator, args);
+}
+
+test "memfd_create" {
+ // 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 fd = posix.memfd_create("test", 0) catch |err| switch (err) {
+ // Related: https://github.com/ziglang/zig/issues/4019
+ error.SystemOutdated => return error.SkipZigTest,
+ else => |e| return e,
+ };
+ defer posix.close(fd);
+ try expect((try posix.write(fd, "test")) == 4);
+ try posix.lseek_SET(fd, 0);
+
+ var buf: [10]u8 = undefined;
+ const bytes_read = try posix.read(fd, &buf);
+ try expect(bytes_read == 4);
+ try expect(mem.eql(u8, buf[0..4], "test"));
+}
+
+test "mmap" {
+ if (native_os == .windows or native_os == .wasi)
+ return error.SkipZigTest;
+
+ 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 testing.expectEqual(@as(usize, 1234), data.len);
+
+ // By definition the data returned by mmap is zero-filled
+ try testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234));
+
+ // Make sure the memory is writeable as requested
+ @memset(data, 0x55);
+ try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
+ }
+
+ const test_out_file = "os_tmp_test";
+ // Must be a multiple of 4096 so that the test works with mmap2
+ const alloc_size = 8 * 4096;
+
+ // Create a file used for testing mmap() calls with a file descriptor
+ {
+ const file = try tmp.dir.createFile(test_out_file, .{});
+ defer file.close();
+
+ const stream = file.writer();
+
+ var i: u32 = 0;
+ while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
+ try stream.writeInt(u32, i, .little);
+ }
+ }
+
+ // Map the whole file
+ {
+ const file = try tmp.dir.openFile(test_out_file, .{});
+ defer file.close();
+
+ const data = try posix.mmap(
+ null,
+ alloc_size,
+ posix.PROT.READ,
+ .{ .TYPE = .PRIVATE },
+ file.handle,
+ 0,
+ );
+ defer posix.munmap(data);
+
+ var mem_stream = io.fixedBufferStream(data);
+ const stream = mem_stream.reader();
+
+ var i: u32 = 0;
+ while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
+ try testing.expectEqual(i, try stream.readInt(u32, .little));
+ }
+ }
+
+ // Map the upper half of the file
+ {
+ const file = try tmp.dir.openFile(test_out_file, .{});
+ defer file.close();
+
+ const data = try posix.mmap(
+ null,
+ alloc_size / 2,
+ posix.PROT.READ,
+ .{ .TYPE = .PRIVATE },
+ file.handle,
+ alloc_size / 2,
+ );
+ defer posix.munmap(data);
+
+ var mem_stream = io.fixedBufferStream(data);
+ const stream = mem_stream.reader();
+
+ var i: u32 = alloc_size / 2 / @sizeOf(u32);
+ while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
+ try testing.expectEqual(i, try stream.readInt(u32, .little));
+ }
+ }
+
+ try tmp.dir.deleteFile(test_out_file);
+}
+
+test "getenv" {
+ if (native_os == .wasi and !builtin.link_libc) {
+ // std.posix.getenv is not supported on WASI due to the need of allocation
+ return error.SkipZigTest;
+ }
+
+ if (native_os == .windows) {
+ try expect(std.process.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
+ } else {
+ try expect(posix.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
+ }
+}
+
+test "fcntl" {
+ if (native_os == .windows or native_os == .wasi)
+ return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ const test_out_file = "os_tmp_test";
+
+ const file = try tmp.dir.createFile(test_out_file, .{});
+ defer {
+ file.close();
+ tmp.dir.deleteFile(test_out_file) catch {};
+ }
+
+ // 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, .solaris, .illumos => {},
+ else => return error.SkipZigTest,
+ }
+ _ = &posix.signalfd;
+}
+
+test "sync" {
+ if (native_os != .linux)
+ return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ const test_out_file = "os_tmp_test";
+ const file = try tmp.dir.createFile(test_out_file, .{});
+ defer {
+ file.close();
+ tmp.dir.deleteFile(test_out_file) catch {};
+ }
+
+ posix.sync();
+ try posix.syncfs(file.handle);
+}
+
+test "fsync" {
+ switch (native_os) {
+ .linux, .windows, .solaris, .illumos => {},
+ else => return error.SkipZigTest,
+ }
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ const test_out_file = "os_tmp_test";
+ const file = try tmp.dir.createFile(test_out_file, .{});
+ defer {
+ file.close();
+ tmp.dir.deleteFile(test_out_file) catch {};
+ }
+
+ try posix.fsync(file.handle);
+ try posix.fdatasync(file.handle);
+}
+
+test "getrlimit and setrlimit" {
+ if (!@hasDecl(posix.system, "rlimit")) {
+ return error.SkipZigTest;
+ }
+
+ inline for (std.meta.fields(posix.rlimit_resource)) |field| {
+ const resource = @as(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 (comptime 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 "shutdown socket" {
+ if (native_os == .wasi)
+ return error.SkipZigTest;
+ if (native_os == .windows) {
+ _ = try std.os.windows.WSAStartup(2, 2);
+ }
+ defer {
+ if (native_os == .windows) {
+ std.os.windows.WSACleanup() catch unreachable;
+ }
+ }
+ const sock = try posix.socket(posix.AF.INET, posix.SOCK.STREAM, 0);
+ posix.shutdown(sock, .both) catch |err| switch (err) {
+ error.SocketNotConnected => {},
+ else => |e| return e,
+ };
+ std.net.Stream.close(.{ .handle = sock });
+}
+
+test "sigaction" {
+ if (native_os == .wasi or native_os == .windows)
+ return error.SkipZigTest;
+
+ // https://github.com/ziglang/zig/issues/7427
+ if (native_os == .linux and builtin.target.cpu.arch == .x86)
+ return error.SkipZigTest;
+
+ // https://github.com/ziglang/zig/issues/15381
+ if (native_os == .macos and builtin.target.cpu.arch == .x86_64) {
+ return error.SkipZigTest;
+ }
+
+ const S = struct {
+ var handler_called_count: u32 = 0;
+
+ fn handler(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) void {
+ _ = ctx_ptr;
+ // Check that we received the correct signal.
+ switch (native_os) {
+ .netbsd => {
+ if (sig == posix.SIG.USR1 and sig == info.info.signo)
+ handler_called_count += 1;
+ },
+ else => {
+ if (sig == posix.SIG.USR1 and sig == info.signo)
+ handler_called_count += 1;
+ },
+ }
+ }
+ };
+
+ var sa: posix.Sigaction = .{
+ .handler = .{ .sigaction = &S.handler },
+ .mask = posix.empty_sigset,
+ .flags = posix.SA.SIGINFO | posix.SA.RESETHAND,
+ };
+ var old_sa: posix.Sigaction = undefined;
+
+ // Install the new signal handler.
+ try posix.sigaction(posix.SIG.USR1, &sa, null);
+
+ // Check that we can read it back correctly.
+ try posix.sigaction(posix.SIG.USR1, null, &old_sa);
+ try testing.expectEqual(&S.handler, old_sa.handler.sigaction.?);
+ try testing.expect((old_sa.flags & posix.SA.SIGINFO) != 0);
+
+ // Invoke the handler.
+ try posix.raise(posix.SIG.USR1);
+ try testing.expect(S.handler_called_count == 1);
+
+ // Check if passing RESETHAND correctly reset the handler to SIG_DFL
+ try posix.sigaction(posix.SIG.USR1, null, &old_sa);
+ try testing.expectEqual(posix.SIG.DFL, old_sa.handler.handler);
+
+ // Reinstall the signal w/o RESETHAND and re-raise
+ sa.flags = posix.SA.SIGINFO;
+ try posix.sigaction(posix.SIG.USR1, &sa, null);
+ try posix.raise(posix.SIG.USR1);
+ try testing.expect(S.handler_called_count == 2);
+
+ // Now set the signal to ignored
+ sa.handler = .{ .handler = posix.SIG.IGN };
+ sa.flags = 0;
+ try posix.sigaction(posix.SIG.USR1, &sa, null);
+
+ // Re-raise to ensure handler is actually ignored
+ try posix.raise(posix.SIG.USR1);
+ try testing.expect(S.handler_called_count == 2);
+
+ // Ensure that ignored state is returned when querying
+ try posix.sigaction(posix.SIG.USR1, null, &old_sa);
+ try testing.expectEqual(posix.SIG.IGN, old_sa.handler.handler.?);
+}
+
+test "dup & dup2" {
+ switch (native_os) {
+ .linux, .solaris, .illumos => {},
+ else => return error.SkipZigTest,
+ }
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ {
+ var file = try tmp.dir.createFile("os_dup_test", .{});
+ defer file.close();
+
+ var duped = std.fs.File{ .handle = try posix.dup(file.handle) };
+ defer duped.close();
+ try duped.writeAll("dup");
+
+ // Tests aren't run in parallel so using the next fd shouldn't be an issue.
+ const new_fd = duped.handle + 1;
+ try posix.dup2(file.handle, new_fd);
+ var dup2ed = std.fs.File{ .handle = new_fd };
+ defer dup2ed.close();
+ try dup2ed.writeAll("dup2");
+ }
+
+ var file = try tmp.dir.openFile("os_dup_test", .{});
+ defer file.close();
+
+ var buf: [7]u8 = undefined;
+ try testing.expectEqualStrings("dupdup2", buf[0..try file.readAll(&buf)]);
+}
+
+test "writev longer than IOV_MAX" {
+ if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var file = try tmp.dir.createFile("pwritev", .{});
+ defer file.close();
+
+ const iovecs = [_]posix.iovec_const{.{ .iov_base = "a", .iov_len = 1 }} ** (posix.IOV_MAX + 1);
+ const amt = try file.writev(&iovecs);
+ try testing.expectEqual(@as(usize, posix.IOV_MAX), amt);
+}
+
+test "POSIX file locking with fcntl" {
+ if (native_os == .windows or native_os == .wasi) {
+ // Not POSIX.
+ return error.SkipZigTest;
+ }
+
+ if (true) {
+ // https://github.com/ziglang/zig/issues/11074
+ return error.SkipZigTest;
+ }
+
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Create a temporary lock file
+ var file = try tmp.dir.createFile("lock", .{ .read = true });
+ defer file.close();
+ try file.setEndPos(2);
+ const fd = file.handle;
+
+ // Place an exclusive lock on the first byte, and a shared lock on the second byte:
+ var struct_flock = std.mem.zeroInit(posix.Flock, .{ .type = posix.F.WRLCK });
+ _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
+ struct_flock.start = 1;
+ struct_flock.type = posix.F.RDLCK;
+ _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
+
+ // Check the locks in a child process:
+ const pid = try posix.fork();
+ if (pid == 0) {
+ // child expects be denied the exclusive lock:
+ struct_flock.start = 0;
+ struct_flock.type = posix.F.WRLCK;
+ try expectError(error.Locked, posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)));
+ // child expects to get the shared lock:
+ struct_flock.start = 1;
+ struct_flock.type = posix.F.RDLCK;
+ _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
+ // child waits for the exclusive lock in order to test deadlock:
+ struct_flock.start = 0;
+ struct_flock.type = posix.F.WRLCK;
+ _ = try posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock));
+ // child exits without continuing:
+ posix.exit(0);
+ } else {
+ // parent waits for child to get shared lock:
+ std.time.sleep(1 * std.time.ns_per_ms);
+ // parent expects deadlock when attempting to upgrade the shared lock to exclusive:
+ struct_flock.start = 1;
+ struct_flock.type = posix.F.WRLCK;
+ try expectError(error.DeadLock, posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock)));
+ // parent releases exclusive lock:
+ struct_flock.start = 0;
+ struct_flock.type = posix.F.UNLCK;
+ _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
+ // parent releases shared lock:
+ struct_flock.start = 1;
+ struct_flock.type = posix.F.UNLCK;
+ _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
+ // parent waits for child:
+ const result = posix.waitpid(pid, 0);
+ try expect(result.status == 0 * 256);
+ }
+}
+
+test "rename smoke test" {
+ if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .windows) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: posix.fd_t = undefined;
+ const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
+ posix.close(fd);
+
+ // Rename the file
+ var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
+ try posix.rename(file_path, new_file_path);
+
+ // Try opening renamed file
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);
+ posix.close(fd);
+
+ // Try opening original file - should fail with error.FileNotFound
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
+
+ // Create some directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try posix.mkdir(file_path, mode);
+
+ // Rename the directory
+ new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
+ try posix.rename(file_path, new_file_path);
+
+ // Try opening renamed directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
+ posix.close(fd);
+
+ // Try opening original directory - should fail with error.FileNotFound
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));
+}
+
+test "access smoke test" {
+ if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .windows) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: posix.fd_t = undefined;
+ const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
+ posix.close(fd);
+
+ // Try to access() the file
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ if (native_os == .windows) {
+ try posix.access(file_path, posix.F_OK);
+ } else {
+ try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK);
+ }
+
+ // Try to access() a non-existent file - should fail with error.FileNotFound
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
+ try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK));
+
+ // Create some directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try posix.mkdir(file_path, mode);
+
+ // Try to access() the directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try posix.access(file_path, posix.F_OK);
+}
+
+test "timerfd" {
+ if (native_os != .linux) return error.SkipZigTest;
+
+ const tfd = try posix.timerfd_create(linux.CLOCK.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 = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_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 = .{ .tv_sec = 0, .tv_nsec = 0 }, .it_value = .{ .tv_sec = 0, .tv_nsec = 0 } };
+ try expectEqual(expect_disarmed_timer, git);
+}
+
+test "isatty" {
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var file = try tmp.dir.createFile("foo", .{});
+ defer file.close();
+
+ try expectEqual(posix.isatty(file.handle), false);
+}
+
+test "read with empty buffer" {
+ if (native_os == .wasi) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ // Get base abs path
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ var file = try fs.cwd().createFile(file_path, .{ .read = true });
+ defer file.close();
+
+ const bytes = try allocator.alloc(u8, 0);
+
+ _ = try posix.read(file.handle, bytes);
+}
+
+test "pread with empty buffer" {
+ if (native_os == .wasi) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ // Get base abs path
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ var file = try fs.cwd().createFile(file_path, .{ .read = true });
+ defer file.close();
+
+ const bytes = try allocator.alloc(u8, 0);
+
+ _ = try posix.pread(file.handle, bytes, 0);
+}
+
+test "write with empty buffer" {
+ if (native_os == .wasi) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ // Get base abs path
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ var file = try fs.cwd().createFile(file_path, .{});
+ defer file.close();
+
+ const bytes = try allocator.alloc(u8, 0);
+
+ _ = try posix.write(file.handle, bytes);
+}
+
+test "pwrite with empty buffer" {
+ if (native_os == .wasi) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ // Get base abs path
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ var file = try fs.cwd().createFile(file_path, .{});
+ defer file.close();
+
+ const bytes = try allocator.alloc(u8, 0);
+
+ _ = try posix.pwrite(file.handle, bytes, 0);
+}
+
+fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
+ const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW);
+ try expectEqual(mode, st.mode & 0b111_111_111);
+}
+
+test "fchmodat smoke test" {
+ if (!std.fs.has_executable_bit) return error.SkipZigTest;
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.fd, "regfile", 0o666, 0));
+ const fd = try posix.openat(
+ tmp.dir.fd,
+ "regfile",
+ .{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true },
+ 0o644,
+ );
+ posix.close(fd);
+ try posix.symlinkat("regfile", tmp.dir.fd, "symlink");
+ const sym_mode = blk: {
+ const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW);
+ break :blk st.mode & 0b111_111_111;
+ };
+
+ try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
+ try expectMode(tmp.dir.fd, "regfile", 0o640);
+ try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW);
+ try expectMode(tmp.dir.fd, "regfile", 0o600);
+
+ try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0);
+ try expectMode(tmp.dir.fd, "regfile", 0o640);
+ try expectMode(tmp.dir.fd, "symlink", sym_mode);
+
+ var test_link = true;
+ posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) {
+ error.OperationNotSupported => test_link = false,
+ else => |e| return e,
+ };
+ if (test_link)
+ try expectMode(tmp.dir.fd, "symlink", 0o600);
+ try expectMode(tmp.dir.fd, "regfile", 0o640);
+}
+
+const CommonOpenFlags = packed struct {
+ ACCMODE: posix.ACCMODE = .RDONLY,
+ CREAT: bool = false,
+ EXCL: bool = false,
+ LARGEFILE: bool = false,
+ DIRECTORY: bool = false,
+ CLOEXEC: bool = false,
+ NONBLOCK: bool = false,
+
+ pub fn lower(cof: CommonOpenFlags) posix.O {
+ if (native_os == .wasi) return .{
+ .read = cof.ACCMODE != .WRONLY,
+ .write = cof.ACCMODE != .RDONLY,
+ .CREAT = cof.CREAT,
+ .EXCL = cof.EXCL,
+ .DIRECTORY = cof.DIRECTORY,
+ .NONBLOCK = cof.NONBLOCK,
+ };
+ var result: posix.O = .{
+ .ACCMODE = cof.ACCMODE,
+ .CREAT = cof.CREAT,
+ .EXCL = cof.EXCL,
+ .DIRECTORY = cof.DIRECTORY,
+ .NONBLOCK = cof.NONBLOCK,
+ .CLOEXEC = cof.CLOEXEC,
+ };
+ if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;
+ return result;
+ }
+};