master
1const builtin = @import("builtin");
2
3const std = @import("../../std.zig");
4const assert = std.debug.assert;
5const linux = std.os.linux;
6const mem = std.mem;
7const elf = std.elf;
8const expect = std.testing.expect;
9const expectEqual = std.testing.expectEqual;
10const fs = std.fs;
11
12test "fallocate" {
13 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23809
14
15 var tmp = std.testing.tmpDir(.{});
16 defer tmp.cleanup();
17
18 const path = "test_fallocate";
19 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
20 defer file.close();
21
22 try expect((try file.stat()).size == 0);
23
24 const len: i64 = 65536;
25 switch (linux.errno(linux.fallocate(file.handle, 0, 0, len))) {
26 .SUCCESS => {},
27 .NOSYS => return error.SkipZigTest,
28 .OPNOTSUPP => return error.SkipZigTest,
29 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
30 }
31
32 try expect((try file.stat()).size == len);
33}
34
35test "getpid" {
36 try expect(linux.getpid() != 0);
37}
38
39test "getppid" {
40 try expect(linux.getppid() != 0);
41}
42
43test "timer" {
44 const epoll_fd = linux.epoll_create();
45 var err: linux.E = linux.errno(epoll_fd);
46 try expect(err == .SUCCESS);
47
48 const timer_fd = linux.timerfd_create(linux.TIMERFD_CLOCK.MONOTONIC, .{});
49 try expect(linux.errno(timer_fd) == .SUCCESS);
50
51 const time_interval = linux.timespec{
52 .sec = 0,
53 .nsec = 2000000,
54 };
55
56 const new_time = linux.itimerspec{
57 .it_interval = time_interval,
58 .it_value = time_interval,
59 };
60
61 err = linux.errno(linux.timerfd_settime(@as(i32, @intCast(timer_fd)), .{}, &new_time, null));
62 try expect(err == .SUCCESS);
63
64 var event = linux.epoll_event{
65 .events = linux.EPOLL.IN | linux.EPOLL.OUT | linux.EPOLL.ET,
66 .data = linux.epoll_data{ .ptr = 0 },
67 };
68
69 err = linux.errno(linux.epoll_ctl(@as(i32, @intCast(epoll_fd)), linux.EPOLL.CTL_ADD, @as(i32, @intCast(timer_fd)), &event));
70 try expect(err == .SUCCESS);
71
72 const events_one: linux.epoll_event = undefined;
73 var events = [_]linux.epoll_event{events_one} ** 8;
74
75 err = linux.errno(linux.epoll_wait(@as(i32, @intCast(epoll_fd)), &events, 8, -1));
76 try expect(err == .SUCCESS);
77}
78
79test "statx" {
80 var tmp = std.testing.tmpDir(.{});
81 defer tmp.cleanup();
82
83 const tmp_file_name = "just_a_temporary_file.txt";
84 var file = try tmp.dir.createFile(tmp_file_name, .{});
85 defer file.close();
86
87 var statx_buf: linux.Statx = undefined;
88 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) {
89 .SUCCESS => {},
90 else => unreachable,
91 }
92
93 if (builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) return error.SkipZigTest; // No fstatat, so the rest of the test is meaningless.
94
95 var stat_buf: linux.Stat = undefined;
96 switch (linux.errno(linux.fstatat(file.handle, "", &stat_buf, linux.AT.EMPTY_PATH))) {
97 .SUCCESS => {},
98 else => unreachable,
99 }
100
101 try expect(stat_buf.mode == statx_buf.mode);
102 try expect(@as(u32, @bitCast(stat_buf.uid)) == statx_buf.uid);
103 try expect(@as(u32, @bitCast(stat_buf.gid)) == statx_buf.gid);
104 try expect(@as(u64, @bitCast(@as(i64, stat_buf.size))) == statx_buf.size);
105 try expect(@as(u64, @bitCast(@as(i64, stat_buf.blksize))) == statx_buf.blksize);
106 try expect(@as(u64, @bitCast(@as(i64, stat_buf.blocks))) == statx_buf.blocks);
107}
108
109test "user and group ids" {
110 if (builtin.link_libc) return error.SkipZigTest;
111 try expectEqual(linux.getauxval(elf.AT_UID), linux.getuid());
112 try expectEqual(linux.getauxval(elf.AT_GID), linux.getgid());
113 try expectEqual(linux.getauxval(elf.AT_EUID), linux.geteuid());
114 try expectEqual(linux.getauxval(elf.AT_EGID), linux.getegid());
115}
116
117test "fadvise" {
118 var tmp = std.testing.tmpDir(.{});
119 defer tmp.cleanup();
120
121 const tmp_file_name = "temp_posix_fadvise.txt";
122 var file = try tmp.dir.createFile(tmp_file_name, .{});
123 defer file.close();
124
125 var buf: [2048]u8 = undefined;
126 try file.writeAll(&buf);
127
128 const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
129 try expectEqual(@as(usize, 0), ret);
130}
131
132test "sigset_t" {
133 const SIG = linux.SIG;
134 assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8));
135
136 var sigset = linux.sigemptyset();
137
138 // See that none are set, then set each one, see that they're all set, then
139 // remove them all, and then see that none are set.
140 for (1..linux.NSIG) |i| {
141 const sig = std.meta.intToEnum(SIG, i) catch continue;
142 try expectEqual(false, linux.sigismember(&sigset, sig));
143 }
144 for (1..linux.NSIG) |i| {
145 const sig = std.meta.intToEnum(SIG, i) catch continue;
146 linux.sigaddset(&sigset, sig);
147 }
148 for (1..linux.NSIG) |i| {
149 const sig = std.meta.intToEnum(SIG, i) catch continue;
150 try expectEqual(true, linux.sigismember(&sigset, sig));
151 }
152 for (1..linux.NSIG) |i| {
153 const sig = std.meta.intToEnum(SIG, i) catch continue;
154 linux.sigdelset(&sigset, sig);
155 }
156 for (1..linux.NSIG) |i| {
157 const sig = std.meta.intToEnum(SIG, i) catch continue;
158 try expectEqual(false, linux.sigismember(&sigset, sig));
159 }
160}
161
162test "sigfillset" {
163 // unlike the C library, all the signals are set in the kernel-level fillset
164 const sigset = linux.sigfillset();
165 for (1..linux.NSIG) |i| {
166 const sig = std.meta.intToEnum(linux.SIG, i) catch continue;
167 try expectEqual(true, linux.sigismember(&sigset, sig));
168 }
169}
170
171test "sigemptyset" {
172 const sigset = linux.sigemptyset();
173 for (1..linux.NSIG) |i| {
174 const sig = std.meta.intToEnum(linux.SIG, i) catch continue;
175 try expectEqual(false, linux.sigismember(&sigset, sig));
176 }
177}
178
179test "sysinfo" {
180 var info: linux.Sysinfo = undefined;
181 const result: usize = linux.sysinfo(&info);
182 try expect(std.os.linux.errno(result) == .SUCCESS);
183
184 try expect(info.mem_unit > 0);
185 try expect(info.mem_unit <= std.heap.page_size_max);
186}
187
188comptime {
189 assert(128 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = true, .realtime = false })));
190 assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
191
192 // Check futex_param4 union is packed correctly
193 const param_union = linux.futex_param4{
194 .val2 = 0xaabbcc,
195 };
196 assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
197}
198
199test "futex v1" {
200 var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
201 var rc: usize = 0;
202
203 // No-op wait, lock value is not expected value
204 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, .{ .timeout = null }, null, 0);
205 try expectEqual(.AGAIN, linux.errno(rc));
206
207 rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, null);
208 try expectEqual(.AGAIN, linux.errno(rc));
209
210 // Short-fuse wait, timeout kicks in
211 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, .{ .timeout = &.{ .sec = 0, .nsec = 2 } }, null, 0);
212 try expectEqual(.TIMEDOUT, linux.errno(rc));
213
214 rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, &.{ .sec = 0, .nsec = 2 });
215 try expectEqual(.TIMEDOUT, linux.errno(rc));
216
217 // Wakeup (no waiters)
218 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2, .{ .timeout = null }, null, 0);
219 try expectEqual(0, rc);
220
221 rc = linux.futex_3arg(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2);
222 try expectEqual(0, rc);
223
224 // CMP_REQUEUE - val3 mismatch
225 rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, 2, .{ .val2 = 0 }, null, 99);
226 try expectEqual(.AGAIN, linux.errno(rc));
227
228 // CMP_REQUEUE - requeue (but no waiters, so ... not much)
229 {
230 const val3 = 1;
231 const wake_nr = 3;
232 const requeue_max = std.math.maxInt(u31);
233 var target_lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
234 rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, wake_nr, .{ .val2 = requeue_max }, &target_lock.raw, val3);
235 try expectEqual(0, rc);
236 }
237
238 // WAKE_OP - just to see if we can construct the arguments ...
239 {
240 var lock2: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
241 const wake1_nr = 2;
242 const wake2_nr = 3;
243 const wake_op = linux.FUTEX_WAKE_OP{
244 .cmd = .ANDN,
245 .arg_shift = true,
246 .cmp = .LT,
247 .oparg = 4,
248 .cmdarg = 5,
249 };
250
251 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_OP, .private = true }, wake1_nr, .{ .val2 = wake2_nr }, &lock2.raw, @bitCast(wake_op));
252 try expectEqual(0, rc);
253 }
254
255 // WAIT_BITSET
256 {
257 // val1 return early
258 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff);
259 try expectEqual(.AGAIN, linux.errno(rc));
260
261 // timeout wait
262 const timeout: linux.timespec = .{ .sec = 0, .nsec = 2 };
263 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 1, .{ .timeout = &timeout }, null, 0xfff);
264 try expectEqual(.TIMEDOUT, linux.errno(rc));
265 }
266
267 // WAKE_BITSET
268 {
269 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff000);
270 try expectEqual(0, rc);
271
272 // bitmask must have at least 1 bit set:
273 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0);
274 try expectEqual(.INVAL, linux.errno(rc));
275 }
276}
277
278comptime {
279 assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false })));
280 assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true })));
281}
282
283test "futex2_waitv" {
284 const locks = [_]std.atomic.Value(u32){
285 std.atomic.Value(u32).init(1),
286 std.atomic.Value(u32).init(1),
287 std.atomic.Value(u32).init(1),
288 };
289
290 const futexes = [_]linux.futex2_waitone{
291 .{
292 .val = 1,
293 .uaddr = @intFromPtr(&locks[0].raw),
294 .flags = .{ .size = .U32, .private = true },
295 },
296 .{
297 .val = 1,
298 .uaddr = @intFromPtr(&locks[1].raw),
299 .flags = .{ .size = .U32, .private = true },
300 },
301 .{
302 .val = 1,
303 .uaddr = @intFromPtr(&locks[2].raw),
304 .flags = .{ .size = .U32, .private = true },
305 },
306 };
307
308 const timeout = linux.kernel_timespec{ .sec = 0, .nsec = 2 }; // absolute timeout, so this is 1970...
309 const rc = linux.futex2_waitv(&futexes, futexes.len, .{}, &timeout, .MONOTONIC);
310 switch (linux.errno(rc)) {
311 .NOSYS => return error.SkipZigTest, // futex2_waitv added in kernel v5.16
312 else => |err| try expectEqual(.TIMEDOUT, err),
313 }
314}
315
316// Futex v2 API is only supported on recent kernels (v6.7), so skip tests if the syscalls
317// return ENOSYS.
318fn futex2_skip_if_unsupported() !void {
319 const lock: u32 = 0;
320 const rc = linux.futex2_wake(&lock, 0, 1, .{ .size = .U32, .private = true });
321 if (linux.errno(rc) == .NOSYS) {
322 return error.SkipZigTest;
323 }
324}
325
326test "futex2_wait" {
327 var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
328 var rc: usize = 0;
329 const mask = 0x1;
330
331 try futex2_skip_if_unsupported();
332
333 // The API for 8,16,64 bit futexes is defined, but as of kernel v6.14
334 // (at least) they're not implemented.
335 if (false) {
336 rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U8, .private = true }, null, .MONOTONIC);
337 try expectEqual(.INVAL, linux.errno(rc));
338
339 rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U16, .private = true }, null, .MONOTONIC);
340 try expectEqual(.INVAL, linux.errno(rc));
341
342 rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U64, .private = true }, null, .MONOTONIC);
343 try expectEqual(.INVAL, linux.errno(rc));
344 }
345
346 const flags = linux.FUTEX2_FLAGS{ .size = .U32, .private = true };
347 // no-wait, lock state mismatch
348 rc = linux.futex2_wait(&lock.raw, 2, mask, flags, null, .MONOTONIC);
349 try expectEqual(.AGAIN, linux.errno(rc));
350
351 // hit timeout on wait
352 rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .MONOTONIC);
353 try expectEqual(.TIMEDOUT, linux.errno(rc));
354
355 // timeout is absolute
356 {
357 var curr: linux.timespec = undefined;
358 rc = linux.clock_gettime(.MONOTONIC, &curr); // gettime() uses platform timespec
359 try expectEqual(0, rc);
360
361 // ... but futex2_wait always uses 64-bit timespec
362 var timeout: linux.kernel_timespec = .{
363 .sec = curr.sec,
364 .nsec = curr.nsec + 2,
365 };
366 rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &timeout, .MONOTONIC);
367 try expectEqual(.TIMEDOUT, linux.errno(rc));
368 }
369
370 rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .REALTIME);
371 try expectEqual(.TIMEDOUT, linux.errno(rc));
372}
373
374test "futex2_wake" {
375 var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
376
377 try futex2_skip_if_unsupported();
378
379 const rc = linux.futex2_wake(&lock.raw, 0xFF, 1, .{ .size = .U32, .private = true });
380 try expectEqual(0, rc);
381}
382
383test "futex2_requeue" {
384 try futex2_skip_if_unsupported();
385
386 const locks = [_]std.atomic.Value(u32){
387 std.atomic.Value(u32).init(1),
388 std.atomic.Value(u32).init(1),
389 };
390
391 const futexes = [_]linux.futex2_waitone{
392 .{
393 .val = 1,
394 .uaddr = @intFromPtr(&locks[0].raw),
395 .flags = .{ .size = .U32, .private = true },
396 },
397 .{
398 .val = 1,
399 .uaddr = @intFromPtr(&locks[1].raw),
400 .flags = .{ .size = .U32, .private = true },
401 },
402 };
403
404 const rc = linux.futex2_requeue(&futexes, .{}, 2, 2);
405 try expectEqual(0, rc);
406}
407
408test "copy_file_range error" {
409 const fds = try std.posix.pipe();
410 defer std.posix.close(fds[0]);
411 defer std.posix.close(fds[1]);
412
413 try std.testing.expectError(error.InvalidArguments, linux.wrapped.copy_file_range(fds[0], null, fds[1], null, 1, 0));
414}
415
416test {
417 _ = linux.IoUring;
418}