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};