1const builtin = @import("builtin");
2const native_os = builtin.target.os.tag;
3const AtomicRmwOp = std.builtin.AtomicRmwOp;
4const AtomicOrder = std.builtin.AtomicOrder;
5
6const std = @import("../std.zig");
7const Io = std.Io;
8const Dir = std.Io.Dir;
9const posix = std.posix;
10const mem = std.mem;
11const elf = std.elf;
12const linux = std.os.linux;
13const AT = std.posix.AT;
14
15const testing = std.testing;
16const expect = std.testing.expect;
17const expectEqual = std.testing.expectEqual;
18const expectEqualSlices = std.testing.expectEqualSlices;
19const expectEqualStrings = std.testing.expectEqualStrings;
20const expectError = std.testing.expectError;
21const tmpDir = std.testing.tmpDir;
22
23const fstest = @import("../fs/test.zig");
24
25test "check WASI CWD" {
26 if (native_os == .wasi) {
27 const cwd: Dir = .cwd();
28 if (cwd.handle != 3) {
29 @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)");
30 }
31 if (!builtin.link_libc) {
32 // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls
33 try expectEqual(3, posix.AT.FDCWD);
34 }
35 }
36}
37
38test "getuid" {
39 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
40 _ = posix.system.getuid();
41 _ = posix.system.geteuid();
42}
43
44test "getgid" {
45 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
46 _ = posix.system.getgid();
47 _ = posix.system.getegid();
48}
49
50test "sigaltstack" {
51 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
52
53 var st: posix.stack_t = undefined;
54 try posix.sigaltstack(null, &st);
55 // Setting a stack size less than MINSIGSTKSZ returns ENOMEM
56 st.flags = 0;
57 st.size = 1;
58 try expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));
59}
60
61// If the type is not available use void to avoid erroring out when `iter_fn` is
62// analyzed
63const have_dl_phdr_info = posix.system.dl_phdr_info != void;
64const dl_phdr_info = if (have_dl_phdr_info) posix.dl_phdr_info else anyopaque;
65
66const IterFnError = error{
67 MissingLoadSegment,
68 MissingEhdrLoadSegment,
69 BadElfMagic,
70 PhnumMismatch,
71};
72
73fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void {
74 _ = size;
75 // Count how many libraries are loaded
76 counter.* += 1;
77
78 // The image should contain at least one loadable segment
79 if (info.phnum < 1) return error.MissingLoadSegment;
80
81 // For some quick and dirty validation, find the phdr which contains the ELF
82 // header, and check it makes sense.
83 for (info.phdr[0..info.phnum]) |phdr| {
84 if (phdr.type != .LOAD) continue;
85 if (phdr.offset != 0) continue;
86 // This segment holds the ELF header at the start
87 const ehdr: *elf.Ehdr = @ptrFromInt(info.addr + phdr.vaddr);
88 if (!mem.eql(u8, ehdr.e_ident[0..4], elf.MAGIC)) return error.BadElfMagic;
89 if (ehdr.e_phnum != info.phnum) return error.PhnumMismatch;
90 break;
91 } else {
92 return error.MissingEhdrLoadSegment;
93 }
94}
95
96test "dl_iterate_phdr" {
97 if (builtin.object_format != .elf) return error.SkipZigTest;
98
99 var counter: usize = 0;
100 try posix.dl_iterate_phdr(&counter, IterFnError, iter_fn);
101 try expect(counter != 0);
102}
103
104test "gethostname" {
105 if (native_os == .windows or native_os == .wasi)
106 return error.SkipZigTest;
107
108 var buf: [posix.HOST_NAME_MAX]u8 = undefined;
109 const hostname = try posix.gethostname(&buf);
110 try expect(hostname.len != 0);
111}
112
113test "pipe" {
114 if (native_os == .windows or native_os == .wasi)
115 return error.SkipZigTest;
116
117 const io = testing.io;
118
119 const fds = try std.Io.Threaded.pipe2(.{});
120 const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } };
121 const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } };
122 try in.writeStreamingAll(io, "hello");
123 var buf: [16]u8 = undefined;
124 try expect((try out.readStreaming(io, &.{&buf})) == 5);
125
126 try expectEqualSlices(u8, buf[0..5], "hello");
127 out.close(io);
128 in.close(io);
129}
130
131test "memfd_create" {
132 const io = testing.io;
133
134 // memfd_create is only supported by linux and freebsd.
135 switch (native_os) {
136 .linux => {},
137 .freebsd => {
138 if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
139 return error.SkipZigTest;
140 },
141 else => return error.SkipZigTest,
142 }
143
144 const file: Io.File = .{
145 .handle = try posix.memfd_create("test", 0),
146 .flags = .{ .nonblocking = false },
147 };
148 defer file.close(io);
149 try file.writePositionalAll(io, "test", 0);
150
151 var buf: [10]u8 = undefined;
152 const bytes_read = try file.readPositionalAll(io, &buf, 0);
153 try expect(bytes_read == 4);
154 try expectEqualStrings("test", buf[0..4]);
155}
156
157test "mmap" {
158 if (native_os == .windows or native_os == .wasi)
159 return error.SkipZigTest;
160
161 const io = testing.io;
162
163 var tmp = tmpDir(.{});
164 defer tmp.cleanup();
165
166 // Simple mmap() call with non page-aligned size
167 {
168 const data = try posix.mmap(
169 null,
170 1234,
171 .{ .READ = true, .WRITE = true },
172 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
173 -1,
174 0,
175 );
176 defer posix.munmap(data);
177
178 try expectEqual(@as(usize, 1234), data.len);
179
180 // By definition the data returned by mmap is zero-filled
181 try expect(mem.eql(u8, data, &@as([1234]u8, @splat(0x00))));
182
183 // Make sure the memory is writeable as requested
184 @memset(data, 0x55);
185 try expect(mem.eql(u8, data, &@as([1234]u8, @splat(0x55))));
186 }
187
188 const test_out_file = "os_tmp_test";
189 // Must be a multiple of the page size so that the test works with mmap2
190 const alloc_size = 8 * std.heap.pageSize();
191
192 // Create a file used for testing mmap() calls with a file descriptor
193 {
194 const file = try tmp.dir.createFile(io, test_out_file, .{});
195 defer file.close(io);
196
197 var stream = file.writer(io, &.{});
198
199 var i: usize = 0;
200 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
201 try stream.interface.writeInt(u32, @intCast(i), .little);
202 }
203 }
204
205 // Map the whole file
206 {
207 const file = try tmp.dir.openFile(io, test_out_file, .{});
208 defer file.close(io);
209
210 const data = try posix.mmap(
211 null,
212 alloc_size,
213 .{ .READ = true },
214 .{ .TYPE = .PRIVATE },
215 file.handle,
216 0,
217 );
218 defer posix.munmap(data);
219
220 var stream: std.Io.Reader = .fixed(data);
221
222 var i: usize = 0;
223 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
224 try expectEqual(i, try stream.takeInt(u32, .little));
225 }
226 }
227
228 if (builtin.cpu.arch == .hexagon) return error.SkipZigTest;
229
230 // Map the upper half of the file
231 {
232 const file = try tmp.dir.openFile(io, test_out_file, .{});
233 defer file.close(io);
234
235 const data = try posix.mmap(
236 null,
237 alloc_size / 2,
238 .{ .READ = true },
239 .{ .TYPE = .PRIVATE },
240 file.handle,
241 alloc_size / 2,
242 );
243 defer posix.munmap(data);
244
245 var stream: std.Io.Reader = .fixed(data);
246
247 var i: usize = alloc_size / 2 / @sizeOf(u32);
248 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
249 try expectEqual(i, try stream.takeInt(u32, .little));
250 }
251 }
252}
253
254test "fcntl" {
255 if (native_os == .windows or native_os == .wasi)
256 return error.SkipZigTest;
257
258 const io = testing.io;
259
260 var tmp = tmpDir(.{});
261 defer tmp.cleanup();
262
263 const test_out_file = "os_tmp_test";
264
265 const file = try tmp.dir.createFile(io, test_out_file, .{});
266 defer file.close(io);
267
268 // Note: The test assumes createFile opens the file with CLOEXEC
269 {
270 const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
271 try expect((flags & posix.FD_CLOEXEC) != 0);
272 }
273 {
274 _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, 0));
275 const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
276 try expect((flags & posix.FD_CLOEXEC) == 0);
277 }
278 {
279 _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC));
280 const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0));
281 try expect((flags & posix.FD_CLOEXEC) != 0);
282 }
283}
284
285test "signalfd" {
286 switch (native_os) {
287 .linux, .illumos => {},
288 else => return error.SkipZigTest,
289 }
290 _ = &posix.signalfd;
291}
292
293test "sync" {
294 if (native_os != .linux)
295 return error.SkipZigTest;
296
297 // Unfortunately, we cannot safely call `sync` or `syncfs`, because if file IO is happening
298 // than the system can commit the results to disk, such calls could block indefinitely.
299
300 _ = &posix.sync;
301 _ = &posix.syncfs;
302}
303
304test "fsync" {
305 switch (native_os) {
306 .linux, .illumos => {},
307 else => return error.SkipZigTest,
308 }
309
310 const io = testing.io;
311
312 var tmp = tmpDir(.{});
313 defer tmp.cleanup();
314
315 const test_out_file = "os_tmp_test";
316 const file = try tmp.dir.createFile(io, test_out_file, .{});
317 defer file.close(io);
318
319 try file.sync(io);
320 try posix.fdatasync(file.handle);
321}
322
323test "getrlimit and setrlimit" {
324 if (posix.system.rlimit_resource == void) return error.SkipZigTest;
325
326 inline for (@typeInfo(posix.rlimit_resource).@"enum".field_values) |field_value| {
327 const resource: posix.rlimit_resource = @fromBackingInt(@intCast(field_value));
328 const limit = try posix.getrlimit(resource);
329
330 // XNU kernel does not support RLIMIT_STACK if a custom stack is active,
331 // which looks to always be the case. EINVAL is returned.
332 // See https://github.com/apple-oss-distributions/xnu/blob/5e3eaea39dcf651e66cb99ba7d70e32cc4a99587/bsd/kern/kern_resource.c#L1173
333 if (native_os.isDarwin() and resource == .STACK) {
334 continue;
335 }
336
337 // On 32 bit MIPS musl includes a fix which changes limits greater than -1UL/2 to RLIM_INFINITY.
338 // See http://git.musl-libc.org/cgit/musl/commit/src/misc/getrlimit.c?id=8258014fd1e34e942a549c88c7e022a00445c352
339 //
340 // This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB.
341 // In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM.
342 if (builtin.cpu.arch.isMIPS() and builtin.link_libc) {
343 if (limit.cur != linux.RLIM.INFINITY) {
344 try posix.setrlimit(resource, limit);
345 }
346 } else {
347 try posix.setrlimit(resource, limit);
348 }
349 }
350}
351
352test "sigrtmin/max" {
353 if (native_os.isDarwin() or switch (native_os) {
354 .wasi, .windows, .openbsd, .dragonfly => true,
355 else => false,
356 }) return error.SkipZigTest;
357
358 try expect(posix.sigrtmin() >= 32);
359 try expect(posix.sigrtmin() >= posix.system.sigrtmin());
360 try expect(posix.sigrtmin() < posix.system.sigrtmax());
361}
362
363test "sigset empty/full" {
364 if (native_os == .wasi or native_os == .windows)
365 return error.SkipZigTest;
366
367 var set: posix.sigset_t = posix.sigemptyset();
368 for (1..posix.NSIG) |i| {
369 const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
370 try expectEqual(false, posix.sigismember(&set, sig));
371 }
372
373 // The C library can reserve some (unnamed) signals, so can't check the full
374 // NSIG set is defined, but just test a couple:
375 set = posix.sigfillset();
376 try expectEqual(true, posix.sigismember(&set, .CHLD));
377 try expectEqual(true, posix.sigismember(&set, .INT));
378}
379
380// Some signals (i.e., 32 - 34 on glibc/musl) are not allowed to be added to a
381// sigset by the C library, so avoid testing them.
382fn reserved_signo(i: usize) bool {
383 if (native_os.isDarwin()) return false;
384 if (!builtin.link_libc) return false;
385 const max = if (native_os == .netbsd) 32 else 31;
386 if (i > max) return true;
387 if (native_os == .openbsd or native_os == .dragonfly) return false; // no RT signals
388 return i < posix.sigrtmin();
389}
390
391test "sigset add/del" {
392 if (native_os == .wasi or native_os == .windows)
393 return error.SkipZigTest;
394
395 var sigset: posix.sigset_t = posix.sigemptyset();
396
397 // See that none are set, then set each one, see that they're all set, then
398 // remove them all, and then see that none are set.
399 for (1..posix.NSIG) |i| {
400 const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
401 try expectEqual(false, posix.sigismember(&sigset, sig));
402 }
403 for (1..posix.NSIG) |i| {
404 if (!reserved_signo(i)) {
405 const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
406 posix.sigaddset(&sigset, sig);
407 }
408 }
409 for (1..posix.NSIG) |i| {
410 if (!reserved_signo(i)) {
411 const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
412 try expectEqual(true, posix.sigismember(&sigset, sig));
413 }
414 }
415 for (1..posix.NSIG) |i| {
416 if (!reserved_signo(i)) {
417 const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
418 posix.sigdelset(&sigset, sig);
419 }
420 }
421 for (1..posix.NSIG) |i| {
422 const sig = std.enums.fromInt(posix.SIG, i) orelse continue;
423 try expectEqual(false, posix.sigismember(&sigset, sig));
424 }
425}
426
427test "getpid" {
428 if (native_os == .wasi) return error.SkipZigTest;
429 if (native_os == .windows) return error.SkipZigTest;
430
431 try expect(posix.system.getpid() != 0);
432}
433
434test "getppid" {
435 if (native_os == .wasi) return error.SkipZigTest;
436 if (native_os == .windows) return error.SkipZigTest;
437 if (native_os == .plan9 and !builtin.link_libc) return error.SkipZigTest;
438
439 try expect(posix.getppid() >= 0);
440}
441
442test "rename smoke test" {
443 if (native_os == .windows) return error.SkipZigTest;
444 if (!fstest.isRealPathSupported()) return error.SkipZigTest;
445
446 const io = testing.io;
447 const gpa = testing.allocator;
448
449 var tmp = tmpDir(.{});
450 defer tmp.cleanup();
451
452 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
453 defer gpa.free(base_path);
454
455 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
456
457 {
458 // Create some file using `open`.
459 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
460 defer gpa.free(file_path);
461 const file = try Io.Dir.cwd().createFile(io, file_path, .{
462 .read = true,
463 .exclusive = true,
464 .permissions = .fromMode(mode),
465 });
466 file.close(io);
467
468 // Rename the file
469 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
470 defer gpa.free(new_file_path);
471 try Io.Dir.renameAbsolute(file_path, new_file_path, io);
472 }
473
474 {
475 // Try opening renamed file
476 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
477 defer gpa.free(file_path);
478 const file = try Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write });
479 file.close(io);
480 }
481
482 {
483 // Try opening original file - should fail with error.FileNotFound
484 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
485 defer gpa.free(file_path);
486 try expectError(error.FileNotFound, Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write }));
487 }
488
489 {
490 // Create some directory
491 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
492 defer gpa.free(file_path);
493 try Io.Dir.createDirAbsolute(io, file_path, .fromMode(mode));
494
495 // Rename the directory
496 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
497 defer gpa.free(new_file_path);
498 try Io.Dir.renameAbsolute(file_path, new_file_path, io);
499 }
500
501 {
502 // Try opening renamed directory
503 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
504 defer gpa.free(file_path);
505 const dir = try Io.Dir.cwd().openDir(io, file_path, .{});
506 dir.close(io);
507 }
508
509 {
510 // Try opening original directory - should fail with error.FileNotFound
511 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
512 defer gpa.free(file_path);
513 try expectError(error.FileNotFound, Io.Dir.cwd().openDir(io, file_path, .{}));
514 }
515}