lib/sys/src/fd.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const capabilities = @import("capabilities.zig");
4
5 const linux = std.os.linux;
6 const posix = std.posix;
7 const native_os = builtin.os.tag;
8 const system = posix.system;
9
10 pub const required_capabilities = switch (native_os) {
11 .linux => capabilities.noLibc(&.{.descriptors}),
12 else => capabilities.host(&.{.descriptors}),
13 };
14
15 pub const Descriptor = posix.fd_t;
16 pub const Mode = posix.mode_t;
17 pub const OpenFlags = posix.O;
18 pub const OpenError = posix.OpenError || error{UnsupportedPlatform};
19 pub const PipeFlags = posix.O;
20 pub const cwd_descriptor = posix.AT.FDCWD;
21
22 pub const PipeOptions = struct {
23 close_on_exec: bool = true,
24 nonblocking: bool = false,
25 };
26
27 pub const ReadError = error{
28 UnsupportedPlatform,
29 WouldBlock,
30 InputOutput,
31 BrokenPipe,
32 ConnectionResetByPeer,
33 ReadFailed,
34 };
35
36 pub const WriteError = error{
37 UnsupportedPlatform,
38 WouldBlock,
39 BrokenPipe,
40 WriteFailed,
41 };
42
43 pub const DuplicateError = error{
44 UnsupportedPlatform,
45 ProcessFdQuotaExceeded,
46 SystemFdQuotaExceeded,
47 DuplicateFailed,
48 };
49
50 pub const PipeError = error{
51 UnsupportedPlatform,
52 InvalidFlags,
53 SystemFdQuotaExceeded,
54 ProcessFdQuotaExceeded,
55 PipeFailed,
56 };
57
58 pub const FlagError = error{
59 UnsupportedPlatform,
60 InvalidFlags,
61 FlagUpdateFailed,
62 };
63
64 pub const ChangeDirectoryError = error{
65 UnsupportedPlatform,
66 InvalidDescriptor,
67 NotDir,
68 ChangeDirectoryFailed,
69 };
70
71 pub const DirectoryEmptyError = error{
72 UnsupportedPlatform,
73 InvalidDescriptor,
74 NotDir,
75 InvalidDirectoryEntry,
76 DirectoryReadFailed,
77 DirectoryReadCapacity,
78 };
79
80 pub const LockMode = enum {
81 shared,
82 exclusive,
83 };
84
85 pub const LockError = error{
86 UnsupportedPlatform,
87 InvalidDescriptor,
88 LockFailed,
89 };
90
91 pub const Identity = struct {
92 device_major: u32,
93 device_minor: u32,
94 inode: u64,
95 regular: bool,
96 };
97
98 pub const FileKind = enum {
99 regular,
100 directory,
101 symbolic_link,
102 other,
103 };
104
105 pub const Status = struct {
106 identity: Identity,
107 kind: FileKind,
108 allocated_bytes: u64,
109 modified_ns: i128,
110 links: u64,
111 };
112
113 pub const FilesystemSpace = struct {
114 total_bytes: u64,
115 free_bytes: u64,
116 available_bytes: u64,
117 };
118
119 const darwin_mount_path_bytes_max: usize = 1024;
120 const directory_read_attempts_max: u8 = 4;
121
122 pub const DarwinMountIdentity = struct {
123 filesystem_id: [2]i32,
124 mounted_on_length: u16,
125 mounted_on: [darwin_mount_path_bytes_max]u8,
126
127 pub fn path(identity_value: *const DarwinMountIdentity) []const u8 {
128 std.debug.assert(identity_value.mounted_on_length > 0);
129 std.debug.assert(identity_value.mounted_on_length < darwin_mount_path_bytes_max);
130 return identity_value.mounted_on[0..identity_value.mounted_on_length];
131 }
132 };
133
134 pub const MountIdentity = union(enum) {
135 linux: u64,
136 darwin: DarwinMountIdentity,
137
138 pub fn eql(a: MountIdentity, b: MountIdentity) bool {
139 if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false;
140 return switch (a) {
141 .linux => |mount_id| mount_id == b.linux,
142 .darwin => |identity_value| std.mem.eql(
143 i32,
144 &identity_value.filesystem_id,
145 &b.darwin.filesystem_id,
146 ) and std.mem.eql(u8, identity_value.path(), b.darwin.path()),
147 };
148 }
149 };
150
151 const DeviceParts = struct {
152 major: u32,
153 minor: u32,
154 };
155
156 pub const IdentityError = error{
157 UnsupportedPlatform,
158 InvalidDescriptor,
159 IdentityUnavailable,
160 };
161
162 pub const StatusError = error{
163 UnsupportedPlatform,
164 InvalidDescriptor,
165 FileNotFound,
166 NotDir,
167 SymLinkLoop,
168 NameTooLong,
169 BadPathName,
170 StatusUnavailable,
171 Overflow,
172 };
173
174 pub const FilesystemSpaceError = error{
175 UnsupportedPlatform,
176 InvalidDescriptor,
177 SpaceUnavailable,
178 Overflow,
179 };
180
181 pub const MountIdentityError = error{
182 UnsupportedPlatform,
183 InvalidDescriptor,
184 MountIdentityUnavailable,
185 };
186
187 pub const SocketPairError = error{
188 UnsupportedPlatform,
189 AddressFamilyUnsupported,
190 ProtocolUnsupportedBySystem,
191 ProcessFdQuotaExceeded,
192 SystemFdQuotaExceeded,
193 SystemResources,
194 ProtocolUnsupportedByAddressFamily,
195 SocketModeUnsupported,
196 SocketPairFailed,
197 };
198
199 pub const SocketPairOptions = struct {
200 close_on_exec: bool = true,
201 };
202
203 pub const DescriptorPolicy = enum {
204 unsupported,
205 linux_syscall,
206 posix_host,
207 };
208
209 const is_darwin = switch (native_os) {
210 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true,
211 else => false,
212 };
213 const socket_flags_unsupported = is_darwin or native_os == .haiku;
214
215 const LinuxFilesystemStatus = extern struct {
216 filesystem_type: i64,
217 block_size: i64,
218 blocks: i64,
219 blocks_free: i64,
220 blocks_available: i64,
221 files: i64,
222 files_free: i64,
223 filesystem_id: [2]i32,
224 name_length: i64,
225 fragment_size: i64,
226 flags: i64,
227 spare: [4]i64,
228 };
229
230 const DarwinFilesystemStatus = extern struct {
231 block_size: u32,
232 io_size: i32,
233 blocks: u64,
234 blocks_free: u64,
235 blocks_available: u64,
236 files: u64,
237 files_free: u64,
238 filesystem_id: [2]i32,
239 owner: u32,
240 filesystem_type: u32,
241 flags: u32,
242 filesystem_subtype: u32,
243 type_name: [16]u8,
244 mounted_on: [1024]u8,
245 mounted_from: [1024]u8,
246 flags_extended: u32,
247 reserved: [7]u32,
248 };
249
250 const DarwinFilesystemSystem = struct {
251 extern "c" fn fstatfs(
252 descriptor: Descriptor,
253 observed: *DarwinFilesystemStatus,
254 ) c_int;
255
256 extern "c" fn @"fstatfs$INODE64"(
257 descriptor: Descriptor,
258 observed: *DarwinFilesystemStatus,
259 ) c_int;
260 };
261
262 comptime {
263 if (native_os == .linux and supportedFilesystemSpaceArchitecture()) {
264 std.debug.assert(@sizeOf(LinuxFilesystemStatus) == 120);
265 std.debug.assert(@offsetOf(LinuxFilesystemStatus, "block_size") == 8);
266 std.debug.assert(@offsetOf(LinuxFilesystemStatus, "blocks") == 16);
267 std.debug.assert(@offsetOf(LinuxFilesystemStatus, "blocks_free") == 24);
268 std.debug.assert(@offsetOf(LinuxFilesystemStatus, "blocks_available") == 32);
269 std.debug.assert(@offsetOf(LinuxFilesystemStatus, "fragment_size") == 72);
270 }
271
272 if (is_darwin and supportedFilesystemSpaceArchitecture()) {
273 std.debug.assert(@sizeOf(DarwinFilesystemStatus) == 2168);
274 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "block_size") == 0);
275 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "blocks") == 8);
276 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "blocks_free") == 16);
277 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "blocks_available") == 24);
278 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "filesystem_id") == 48);
279 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "mounted_on") == 88);
280 std.debug.assert(@offsetOf(DarwinFilesystemStatus, "flags_extended") == 2136);
281 }
282 }
283
284 pub fn descriptorPolicy() DescriptorPolicy {
285 return switch (native_os) {
286 .windows, .wasi, .freestanding => .unsupported,
287 .linux => .linux_syscall,
288 else => .posix_host,
289 };
290 }
291
292 test "descriptor capability follows platform ABI" {
293 const expected: capabilities.CapabilityTier = if (native_os == .linux) .no_libc else .host;
294 try std.testing.expectEqual(expected, required_capabilities.tier);
295 }
296
297 test "Darwin device identity preserves major and minor fields" {
298 const parts = darwinDeviceParts(0x1234_5678);
299 try std.testing.expectEqual(@as(u32, 0x12), parts.major);
300 try std.testing.expectEqual(@as(u32, 0x34_5678), parts.minor);
301 }
302
303 pub fn close(descriptor: Descriptor) void {
304 switch (descriptorPolicy()) {
305 .unsupported => {},
306 .linux_syscall => closeLinux(descriptor),
307 .posix_host => closePosix(descriptor),
308 }
309 }
310
311 pub fn standardInput() Descriptor {
312 return posix.STDIN_FILENO;
313 }
314
315 pub fn standardOutput() Descriptor {
316 return posix.STDOUT_FILENO;
317 }
318
319 pub fn standardError() Descriptor {
320 return posix.STDERR_FILENO;
321 }
322
323 pub fn openAt(
324 directory: Descriptor,
325 path: []const u8,
326 flags: OpenFlags,
327 mode: Mode,
328 ) OpenError!Descriptor {
329 if (comptime descriptorPolicy() == .unsupported) return error.UnsupportedPlatform;
330 return posix.openat(directory, path, flags, mode);
331 }
332
333 pub fn openAtZ(
334 directory: Descriptor,
335 path: [*:0]const u8,
336 flags: OpenFlags,
337 mode: Mode,
338 ) OpenError!Descriptor {
339 if (comptime descriptorPolicy() == .unsupported) return error.UnsupportedPlatform;
340 return posix.openatZ(directory, path, flags, mode);
341 }
342
343 pub fn changeWorkingDirectory(descriptor: Descriptor) ChangeDirectoryError!void {
344 return switch (descriptorPolicy()) {
345 .unsupported => error.UnsupportedPlatform,
346 .linux_syscall => changeWorkingDirectoryLinux(descriptor),
347 .posix_host => changeWorkingDirectoryPosix(descriptor),
348 };
349 }
350
351 pub fn setCloseOnExec(descriptor: Descriptor) FlagError!void {
352 return switch (descriptorPolicy()) {
353 .unsupported => error.UnsupportedPlatform,
354 .linux_syscall => setCloseOnExecLinux(descriptor),
355 .posix_host => setCloseOnExecPosix(descriptor),
356 };
357 }
358
359 pub fn setInheritOnExec(descriptor: Descriptor) FlagError!void {
360 return switch (descriptorPolicy()) {
361 .unsupported => error.UnsupportedPlatform,
362 .linux_syscall => setInheritOnExecLinux(descriptor),
363 .posix_host => setInheritOnExecPosix(descriptor),
364 };
365 }
366
367 pub fn lock(
368 descriptor: Descriptor,
369 mode: LockMode,
370 blocking: bool,
371 ) LockError!bool {
372 return switch (descriptorPolicy()) {
373 .unsupported => error.UnsupportedPlatform,
374 .linux_syscall => lockLinux(descriptor, mode, blocking),
375 .posix_host => lockPosix(descriptor, mode, blocking),
376 };
377 }
378
379 pub fn identity(descriptor: Descriptor) IdentityError!Identity {
380 return switch (comptime native_os) {
381 .linux => identityLinux(descriptor),
382 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => identityDarwin(descriptor),
383 else => error.UnsupportedPlatform,
384 };
385 }
386
387 pub fn status(descriptor: Descriptor) StatusError!Status {
388 if (comptime native_os == .linux) {
389 return statusLinux(descriptor, "", linux.AT.EMPTY_PATH);
390 }
391 if (comptime is_darwin) return statusDarwin(descriptor);
392 return error.UnsupportedPlatform;
393 }
394
395 pub fn statusAt(directory: Descriptor, name: []const u8) StatusError!Status {
396 if (name.len == 0 or std.mem.indexOfScalar(u8, name, 0) != null) {
397 return error.BadPathName;
398 }
399 const name_z = std.posix.toPosixPath(name) catch return error.NameTooLong;
400 if (comptime native_os == .linux) {
401 return statusLinux(directory, &name_z, linux.AT.SYMLINK_NOFOLLOW);
402 }
403 if (comptime is_darwin) return statusAtDarwin(directory, &name_z);
404 return error.UnsupportedPlatform;
405 }
406
407 pub fn filesystemSpace(descriptor: Descriptor) FilesystemSpaceError!FilesystemSpace {
408 if (comptime native_os == .linux) return filesystemSpaceLinux(descriptor);
409 if (comptime is_darwin) return filesystemSpaceDarwin(descriptor);
410 return error.UnsupportedPlatform;
411 }
412
413 pub fn mountIdentity(descriptor: Descriptor) MountIdentityError!MountIdentity {
414 if (comptime native_os == .linux) return mountIdentityLinux(descriptor);
415 if (comptime is_darwin) return mountIdentityDarwin(descriptor);
416 return error.UnsupportedPlatform;
417 }
418
419 pub fn directoryEmpty(descriptor: Descriptor) DirectoryEmptyError!bool {
420 return switch (comptime native_os) {
421 .linux => directoryEmptyLinux(descriptor),
422 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => directoryEmptyDarwin(descriptor),
423 else => error.UnsupportedPlatform,
424 };
425 }
426
427 fn changeWorkingDirectoryLinux(descriptor: Descriptor) ChangeDirectoryError!void {
428 if (comptime native_os != .linux) return error.UnsupportedPlatform;
429 return switch (linux.errno(linux.fchdir(descriptor))) {
430 .SUCCESS => {},
431 .BADF => error.InvalidDescriptor,
432 .NOTDIR => error.NotDir,
433 else => error.ChangeDirectoryFailed,
434 };
435 }
436
437 fn changeWorkingDirectoryPosix(descriptor: Descriptor) ChangeDirectoryError!void {
438 if (!@hasDecl(system, "fchdir") or @TypeOf(system.fchdir) == void) {
439 return error.UnsupportedPlatform;
440 }
441 return switch (posix.errno(system.fchdir(descriptor))) {
442 .SUCCESS => {},
443 .BADF => error.InvalidDescriptor,
444 .NOTDIR => error.NotDir,
445 else => error.ChangeDirectoryFailed,
446 };
447 }
448
449 fn directoryEmptyLinux(descriptor: Descriptor) DirectoryEmptyError!bool {
450 if (comptime native_os != .linux) return error.UnsupportedPlatform;
451 var buffer: [1024]u8 align(@alignOf(linux.dirent64)) = undefined;
452 for (0..directory_read_attempts_max) |_| {
453 const result = linux.getdents64(descriptor, &buffer, buffer.len);
454 switch (linux.errno(result)) {
455 .SUCCESS => {},
456 .INTR => continue,
457 .BADF => return error.InvalidDescriptor,
458 .NOTDIR => return error.NotDir,
459 else => return error.DirectoryReadFailed,
460 }
461 if (result == 0) return true;
462 var index: usize = 0;
463 while (index < result) {
464 const entry: *align(1) const linux.dirent64 = @ptrCast(&buffer[index]);
465 const header = @offsetOf(linux.dirent64, "name");
466 if (entry.reclen <= header or entry.reclen > result - index) {
467 return error.InvalidDirectoryEntry;
468 }
469 const padded: [*]const u8 = @ptrCast(&entry.name);
470 const name = std.mem.sliceTo(padded[0 .. entry.reclen - header], 0);
471 if (!dotDirectoryEntry(name) and entry.ino != 0) return false;
472 index += entry.reclen;
473 }
474 }
475 return error.DirectoryReadCapacity;
476 }
477
478 fn directoryEmptyDarwin(descriptor: Descriptor) DirectoryEmptyError!bool {
479 if (comptime !is_darwin) return error.UnsupportedPlatform;
480 var buffer: [2048]u8 align(@alignOf(std.c.dirent)) = undefined;
481 var base: i64 = 0;
482 for (0..directory_read_attempts_max) |_| {
483 const result = system.getdirentries(descriptor, &buffer, buffer.len, &base);
484 switch (posix.errno(result)) {
485 .SUCCESS => {},
486 .INTR => continue,
487 .BADF => return error.InvalidDescriptor,
488 .NOTDIR => return error.NotDir,
489 else => return error.DirectoryReadFailed,
490 }
491 if (result == 0) return true;
492 const count: usize = @intCast(result);
493 var index: usize = 0;
494 while (index < count) {
495 const entry: *align(1) const std.c.dirent = @ptrCast(&buffer[index]);
496 if (entry.reclen == 0 or entry.reclen > count - index) {
497 return error.InvalidDirectoryEntry;
498 }
499 const name = entry.name[0..entry.namlen];
500 if (!dotDirectoryEntry(name) and entry.ino != 0) return false;
501 index += entry.reclen;
502 }
503 }
504 return error.DirectoryReadCapacity;
505 }
506
507 fn dotDirectoryEntry(name: []const u8) bool {
508 return std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..");
509 }
510
511 fn identityLinux(descriptor: Descriptor) IdentityError!Identity {
512 if (comptime native_os != .linux) return error.UnsupportedPlatform;
513 var stat = std.mem.zeroes(linux.Statx);
514 const result = linux.statx(
515 descriptor,
516 "",
517 linux.AT.EMPTY_PATH,
518 linux.STATX.BASIC_STATS,
519 &stat,
520 );
521 return switch (linux.errno(result)) {
522 .SUCCESS => .{
523 .device_major = stat.dev_major,
524 .device_minor = stat.dev_minor,
525 .inode = stat.ino,
526 .regular = linux.S.ISREG(stat.mode),
527 },
528 .BADF => error.InvalidDescriptor,
529 else => error.IdentityUnavailable,
530 };
531 }
532
533 fn identityDarwin(descriptor: Descriptor) IdentityError!Identity {
534 if (comptime !is_darwin) return error.UnsupportedPlatform;
535 var stat = std.mem.zeroes(posix.Stat);
536 return switch (posix.errno(system.fstat(descriptor, &stat))) {
537 .SUCCESS => value: {
538 const device: u32 = @bitCast(stat.dev);
539 const parts = darwinDeviceParts(device);
540 break :value .{
541 .device_major = parts.major,
542 .device_minor = parts.minor,
543 .inode = @intCast(stat.ino),
544 .regular = posix.S.ISREG(@intCast(stat.mode)),
545 };
546 },
547 .BADF => error.InvalidDescriptor,
548 else => error.IdentityUnavailable,
549 };
550 }
551
552 fn statusLinux(
553 directory: Descriptor,
554 name: [*:0]const u8,
555 flags: u32,
556 ) StatusError!Status {
557 if (comptime native_os != .linux) return error.UnsupportedPlatform;
558 while (true) {
559 var observed = std.mem.zeroes(linux.Statx);
560 const result = linux.statx(
561 directory,
562 name,
563 flags,
564 linux.STATX.BASIC_STATS,
565 &observed,
566 );
567 switch (linux.errno(result)) {
568 .SUCCESS => {
569 if (!observed.mask.TYPE or !observed.mask.MTIME or
570 !observed.mask.INO or !observed.mask.BLOCKS or
571 !observed.mask.NLINK)
572 {
573 return error.StatusUnavailable;
574 }
575 const allocated_bytes = std.math.mul(
576 u64,
577 observed.blocks,
578 512,
579 ) catch return error.Overflow;
580 return .{
581 .identity = .{
582 .device_major = observed.dev_major,
583 .device_minor = observed.dev_minor,
584 .inode = observed.ino,
585 .regular = linux.S.ISREG(observed.mode),
586 },
587 .kind = kindLinux(observed.mode),
588 .allocated_bytes = allocated_bytes,
589 .modified_ns = @as(i128, observed.mtime.sec) * std.time.ns_per_s +
590 observed.mtime.nsec,
591 .links = observed.nlink,
592 };
593 },
594 .INTR => {},
595 .BADF => return error.InvalidDescriptor,
596 .NOENT => return error.FileNotFound,
597 .NOTDIR => return error.NotDir,
598 .LOOP => return error.SymLinkLoop,
599 .NAMETOOLONG => return error.NameTooLong,
600 else => return error.StatusUnavailable,
601 }
602 }
603 }
604
605 fn statusDarwin(descriptor: Descriptor) StatusError!Status {
606 if (comptime !is_darwin) return error.UnsupportedPlatform;
607 while (true) {
608 var observed = std.mem.zeroes(posix.Stat);
609 switch (posix.errno(system.fstat(descriptor, &observed))) {
610 .SUCCESS => return statusFromDarwin(observed),
611 .INTR => {},
612 .BADF => return error.InvalidDescriptor,
613 else => return error.StatusUnavailable,
614 }
615 }
616 }
617
618 fn statusAtDarwin(directory: Descriptor, name: [*:0]const u8) StatusError!Status {
619 if (comptime !is_darwin) return error.UnsupportedPlatform;
620 while (true) {
621 var observed = std.mem.zeroes(posix.Stat);
622 const result = system.fstatat(
623 directory,
624 name,
625 &observed,
626 posix.AT.SYMLINK_NOFOLLOW,
627 );
628 switch (posix.errno(result)) {
629 .SUCCESS => return statusFromDarwin(observed),
630 .INTR => {},
631 .BADF => return error.InvalidDescriptor,
632 .NOENT => return error.FileNotFound,
633 .NOTDIR => return error.NotDir,
634 .LOOP => return error.SymLinkLoop,
635 .NAMETOOLONG => return error.NameTooLong,
636 else => return error.StatusUnavailable,
637 }
638 }
639 }
640
641 fn statusFromDarwin(observed: posix.Stat) StatusError!Status {
642 if (comptime !is_darwin) return error.UnsupportedPlatform;
643 if (observed.blocks < 0) return error.StatusUnavailable;
644 const allocated_bytes = std.math.mul(
645 u64,
646 @intCast(observed.blocks),
647 512,
648 ) catch return error.Overflow;
649 const device: u32 = @bitCast(observed.dev);
650 const parts = darwinDeviceParts(device);
651 const modified = observed.mtime();
652 return .{
653 .identity = .{
654 .device_major = parts.major,
655 .device_minor = parts.minor,
656 .inode = @intCast(observed.ino),
657 .regular = posix.S.ISREG(@intCast(observed.mode)),
658 },
659 .kind = kindDarwin(observed.mode),
660 .allocated_bytes = allocated_bytes,
661 .modified_ns = @as(i128, modified.sec) * std.time.ns_per_s + modified.nsec,
662 .links = @intCast(observed.nlink),
663 };
664 }
665
666 fn filesystemSpaceLinux(descriptor: Descriptor) FilesystemSpaceError!FilesystemSpace {
667 if (comptime native_os != .linux or !supportedFilesystemSpaceArchitecture()) {
668 return error.UnsupportedPlatform;
669 }
670 while (true) {
671 var observed = std.mem.zeroes(LinuxFilesystemStatus);
672 const result = linux.syscall2(
673 .fstatfs,
674 @as(u32, @bitCast(descriptor)),
675 @intFromPtr(&observed),
676 );
677 switch (linux.errno(result)) {
678 .SUCCESS => return filesystemSpaceFromLinux(observed),
679 .INTR => {},
680 .BADF => return error.InvalidDescriptor,
681 else => return error.SpaceUnavailable,
682 }
683 }
684 }
685
686 fn filesystemSpaceFromLinux(
687 observed: LinuxFilesystemStatus,
688 ) FilesystemSpaceError!FilesystemSpace {
689 const block_size = if (observed.fragment_size > 0)
690 observed.fragment_size
691 else
692 observed.block_size;
693 if (block_size <= 0 or observed.blocks < 0 or observed.blocks_free < 0 or
694 observed.blocks_available < 0)
695 {
696 return error.SpaceUnavailable;
697 }
698 return scaleFilesystemSpace(
699 @intCast(block_size),
700 @intCast(observed.blocks),
701 @intCast(observed.blocks_free),
702 @intCast(observed.blocks_available),
703 );
704 }
705
706 fn filesystemSpaceDarwin(descriptor: Descriptor) FilesystemSpaceError!FilesystemSpace {
707 if (comptime !is_darwin or !supportedFilesystemSpaceArchitecture()) {
708 return error.UnsupportedPlatform;
709 }
710 while (true) {
711 var observed = std.mem.zeroes(DarwinFilesystemStatus);
712 const result = switch (comptime builtin.cpu.arch) {
713 .aarch64 => DarwinFilesystemSystem.fstatfs(descriptor, &observed),
714 .x86_64 => DarwinFilesystemSystem.@"fstatfs$INODE64"(descriptor, &observed),
715 else => unreachable,
716 };
717 switch (posix.errno(result)) {
718 .SUCCESS => return scaleFilesystemSpace(
719 observed.block_size,
720 observed.blocks,
721 observed.blocks_free,
722 observed.blocks_available,
723 ),
724 .INTR => {},
725 .BADF => return error.InvalidDescriptor,
726 else => return error.SpaceUnavailable,
727 }
728 }
729 }
730
731 fn mountIdentityLinux(descriptor: Descriptor) MountIdentityError!MountIdentity {
732 if (comptime native_os != .linux) return error.UnsupportedPlatform;
733 while (true) {
734 var observed = std.mem.zeroes(linux.Statx);
735 const result = linux.statx(
736 descriptor,
737 "",
738 linux.AT.EMPTY_PATH,
739 .{ .MNT_ID = true },
740 &observed,
741 );
742 switch (linux.errno(result)) {
743 .SUCCESS => return mountIdentityFromLinux(observed),
744 .INTR => {},
745 .BADF => return error.InvalidDescriptor,
746 else => return error.MountIdentityUnavailable,
747 }
748 }
749 }
750
751 fn mountIdentityFromLinux(observed: linux.Statx) MountIdentityError!MountIdentity {
752 if (!observed.mask.MNT_ID) return error.MountIdentityUnavailable;
753 return .{ .linux = observed.mnt_id };
754 }
755
756 fn mountIdentityDarwin(descriptor: Descriptor) MountIdentityError!MountIdentity {
757 if (comptime !is_darwin or !supportedFilesystemSpaceArchitecture()) {
758 return error.UnsupportedPlatform;
759 }
760 while (true) {
761 var observed = std.mem.zeroes(DarwinFilesystemStatus);
762 const result = switch (comptime builtin.cpu.arch) {
763 .aarch64 => DarwinFilesystemSystem.fstatfs(descriptor, &observed),
764 .x86_64 => DarwinFilesystemSystem.@"fstatfs$INODE64"(descriptor, &observed),
765 else => unreachable,
766 };
767 switch (posix.errno(result)) {
768 .SUCCESS => return mountIdentityFromDarwin(observed),
769 .INTR => {},
770 .BADF => return error.InvalidDescriptor,
771 else => return error.MountIdentityUnavailable,
772 }
773 }
774 }
775
776 fn mountIdentityFromDarwin(
777 observed: DarwinFilesystemStatus,
778 ) MountIdentityError!MountIdentity {
779 const unavailable_filesystem_id = [2]i32{ -1, -1 };
780 if (std.mem.eql(i32, &observed.filesystem_id, &unavailable_filesystem_id)) {
781 return error.MountIdentityUnavailable;
782 }
783 const mounted_on_length = std.mem.indexOfScalar(u8, &observed.mounted_on, 0) orelse
784 return error.MountIdentityUnavailable;
785 if (mounted_on_length == 0) return error.MountIdentityUnavailable;
786
787 var result: DarwinMountIdentity = .{
788 .filesystem_id = observed.filesystem_id,
789 .mounted_on_length = @intCast(mounted_on_length),
790 .mounted_on = @splat(0),
791 };
792 @memcpy(result.mounted_on[0..mounted_on_length], observed.mounted_on[0..mounted_on_length]);
793 return .{ .darwin = result };
794 }
795
796 fn scaleFilesystemSpace(
797 block_size: u64,
798 total_blocks: u64,
799 free_blocks: u64,
800 available_blocks: u64,
801 ) FilesystemSpaceError!FilesystemSpace {
802 if (block_size == 0) return error.SpaceUnavailable;
803 return .{
804 .total_bytes = std.math.mul(u64, total_blocks, block_size) catch return error.Overflow,
805 .free_bytes = std.math.mul(u64, free_blocks, block_size) catch return error.Overflow,
806 .available_bytes = std.math.mul(
807 u64,
808 available_blocks,
809 block_size,
810 ) catch return error.Overflow,
811 };
812 }
813
814 fn supportedFilesystemSpaceArchitecture() bool {
815 return builtin.cpu.arch == .aarch64 or builtin.cpu.arch == .x86_64;
816 }
817
818 fn kindLinux(mode: u16) FileKind {
819 if (linux.S.ISREG(mode)) return .regular;
820 if (linux.S.ISDIR(mode)) return .directory;
821 if (linux.S.ISLNK(mode)) return .symbolic_link;
822 return .other;
823 }
824
825 fn kindDarwin(mode: posix.mode_t) FileKind {
826 if (posix.S.ISREG(@intCast(mode))) return .regular;
827 if (posix.S.ISDIR(@intCast(mode))) return .directory;
828 if (posix.S.ISLNK(@intCast(mode))) return .symbolic_link;
829 return .other;
830 }
831
832 fn darwinDeviceParts(device: u32) DeviceParts {
833 return .{
834 .major = device >> 24,
835 .minor = device & 0x00ff_ffff,
836 };
837 }
838
839 pub fn setNonBlocking(descriptor: Descriptor) FlagError!void {
840 return switch (descriptorPolicy()) {
841 .unsupported => error.UnsupportedPlatform,
842 .linux_syscall => setNonBlockingLinux(descriptor),
843 .posix_host => setNonBlockingPosix(descriptor),
844 };
845 }
846
847 pub fn closeOnExec(descriptor: Descriptor) FlagError!bool {
848 return switch (descriptorPolicy()) {
849 .unsupported => error.UnsupportedPlatform,
850 .linux_syscall => closeOnExecLinux(descriptor),
851 .posix_host => closeOnExecPosix(descriptor),
852 };
853 }
854
855 pub fn nonBlocking(descriptor: Descriptor) FlagError!bool {
856 return switch (descriptorPolicy()) {
857 .unsupported => error.UnsupportedPlatform,
858 .linux_syscall => nonBlockingLinux(descriptor),
859 .posix_host => nonBlockingPosix(descriptor),
860 };
861 }
862
863 pub fn isOpen(descriptor: Descriptor) bool {
864 return switch (descriptorPolicy()) {
865 .unsupported => false,
866 .linux_syscall => isOpenLinux(descriptor),
867 .posix_host => isOpenPosix(descriptor),
868 };
869 }
870
871 pub fn read(descriptor: Descriptor, buffer: []u8) ReadError!usize {
872 if (buffer.len == 0) return 0;
873
874 return switch (descriptorPolicy()) {
875 .unsupported => error.UnsupportedPlatform,
876 .linux_syscall => readLinux(descriptor, buffer),
877 .posix_host => readPosix(descriptor, buffer),
878 };
879 }
880
881 pub fn write(descriptor: Descriptor, bytes: []const u8) WriteError!usize {
882 if (bytes.len == 0) return 0;
883
884 return switch (descriptorPolicy()) {
885 .unsupported => error.UnsupportedPlatform,
886 .linux_syscall => writeLinux(descriptor, bytes),
887 .posix_host => writePosix(descriptor, bytes),
888 };
889 }
890
891 pub fn duplicate(descriptor: Descriptor) DuplicateError!Descriptor {
892 return switch (descriptorPolicy()) {
893 .unsupported => error.UnsupportedPlatform,
894 .linux_syscall => duplicateLinux(descriptor),
895 .posix_host => duplicatePosix(descriptor),
896 };
897 }
898
899 pub fn duplicateAtLeast(
900 descriptor: Descriptor,
901 minimum: Descriptor,
902 ) DuplicateError!Descriptor {
903 return switch (descriptorPolicy()) {
904 .unsupported => error.UnsupportedPlatform,
905 .linux_syscall => duplicateAtLeastLinux(descriptor, minimum),
906 .posix_host => duplicateAtLeastPosix(descriptor, minimum),
907 };
908 }
909
910 pub fn duplicateTo(source: Descriptor, target: Descriptor) DuplicateError!void {
911 return switch (descriptorPolicy()) {
912 .unsupported => error.UnsupportedPlatform,
913 .linux_syscall => duplicateToLinux(source, target),
914 .posix_host => duplicateToPosix(source, target),
915 };
916 }
917
918 pub fn pipe(flags: PipeFlags) PipeError![2]Descriptor {
919 return switch (descriptorPolicy()) {
920 .unsupported => error.UnsupportedPlatform,
921 .linux_syscall => pipeLinux(flags),
922 .posix_host => pipePosix(flags),
923 };
924 }
925
926 pub fn pipeWithOptions(options: PipeOptions) PipeError![2]Descriptor {
927 return pipe(pipeFlags(options));
928 }
929
930 pub fn socketPairUnixStream(options: SocketPairOptions) SocketPairError![2]Descriptor {
931 return switch (descriptorPolicy()) {
932 .unsupported => error.UnsupportedPlatform,
933 .linux_syscall => socketPairUnixStreamLinux(options),
934 .posix_host => socketPairUnixStreamPosix(options),
935 };
936 }
937
938 fn closeLinux(descriptor: Descriptor) void {
939 if (comptime native_os != .linux) return;
940 _ = linux.close(descriptor);
941 }
942
943 fn closePosix(descriptor: Descriptor) void {
944 if (@hasDecl(system, "close")) {
945 _ = system.close(descriptor);
946 }
947 }
948
949 fn setCloseOnExecLinux(descriptor: Descriptor) FlagError!void {
950 if (comptime native_os != .linux) return error.UnsupportedPlatform;
951 var operations: LinuxDescriptorFlagOperations = .{};
952 return setCloseOnExecWith(&operations, descriptor, linux.FD_CLOEXEC);
953 }
954
955 fn setInheritOnExecLinux(descriptor: Descriptor) FlagError!void {
956 if (comptime native_os != .linux) return error.UnsupportedPlatform;
957 var operations: LinuxDescriptorFlagOperations = .{};
958 return clearCloseOnExecWith(&operations, descriptor, linux.FD_CLOEXEC);
959 }
960
961 fn setCloseOnExecPosix(descriptor: Descriptor) FlagError!void {
962 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
963 return error.UnsupportedPlatform;
964 }
965 var operations: PosixDescriptorFlagOperations = .{};
966 return setCloseOnExecWith(&operations, descriptor, posix.FD_CLOEXEC);
967 }
968
969 fn setInheritOnExecPosix(descriptor: Descriptor) FlagError!void {
970 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
971 return error.UnsupportedPlatform;
972 }
973 var operations: PosixDescriptorFlagOperations = .{};
974 return clearCloseOnExecWith(&operations, descriptor, posix.FD_CLOEXEC);
975 }
976
977 const DescriptorFlagResult = union(enum) {
978 success: usize,
979 interrupted,
980 invalid,
981 failed,
982 };
983
984 const LinuxDescriptorFlagOperations = struct {
985 fn get(_: *@This(), descriptor: Descriptor) DescriptorFlagResult {
986 const rc = linux.fcntl(descriptor, linux.F.GETFD, 0);
987 return switch (linux.errno(rc)) {
988 .SUCCESS => .{ .success = rc },
989 .INTR => .interrupted,
990 .INVAL => .invalid,
991 else => .failed,
992 };
993 }
994
995 fn set(_: *@This(), descriptor: Descriptor, flags: usize) DescriptorFlagResult {
996 const rc = linux.fcntl(descriptor, linux.F.SETFD, flags);
997 return switch (linux.errno(rc)) {
998 .SUCCESS => .{ .success = rc },
999 .INTR => .interrupted,
1000 .INVAL => .invalid,
1001 else => .failed,
1002 };
1003 }
1004 };
1005
1006 const PosixDescriptorFlagOperations = struct {
1007 fn get(_: *@This(), descriptor: Descriptor) DescriptorFlagResult {
1008 const rc = system.fcntl(descriptor, posix.F.GETFD, @as(usize, 0));
1009 return switch (posix.errno(rc)) {
1010 .SUCCESS => .{ .success = @intCast(rc) },
1011 .INTR => .interrupted,
1012 .INVAL => .invalid,
1013 else => .failed,
1014 };
1015 }
1016
1017 fn set(_: *@This(), descriptor: Descriptor, flags: usize) DescriptorFlagResult {
1018 const rc = system.fcntl(descriptor, posix.F.SETFD, flags);
1019 return switch (posix.errno(rc)) {
1020 .SUCCESS => .{ .success = @intCast(rc) },
1021 .INTR => .interrupted,
1022 .INVAL => .invalid,
1023 else => .failed,
1024 };
1025 }
1026 };
1027
1028 fn setCloseOnExecWith(
1029 operations: anytype,
1030 descriptor: Descriptor,
1031 close_on_exec: usize,
1032 ) FlagError!void {
1033 const flags = while (true) {
1034 switch (operations.get(descriptor)) {
1035 .success => |value| break value,
1036 .interrupted => continue,
1037 .invalid, .failed => return error.FlagUpdateFailed,
1038 }
1039 };
1040 const updated = flags | close_on_exec;
1041
1042 while (true) {
1043 switch (operations.set(descriptor, updated)) {
1044 .success => return,
1045 .interrupted => continue,
1046 .invalid => return error.InvalidFlags,
1047 .failed => return error.FlagUpdateFailed,
1048 }
1049 }
1050 }
1051
1052 fn clearCloseOnExecWith(
1053 operations: anytype,
1054 descriptor: Descriptor,
1055 close_on_exec: usize,
1056 ) FlagError!void {
1057 const flags = while (true) {
1058 switch (operations.get(descriptor)) {
1059 .success => |value| break value,
1060 .interrupted => continue,
1061 .invalid, .failed => return error.FlagUpdateFailed,
1062 }
1063 };
1064 const updated = flags & ~close_on_exec;
1065 while (true) {
1066 switch (operations.set(descriptor, updated)) {
1067 .success => return,
1068 .interrupted => continue,
1069 .invalid => return error.InvalidFlags,
1070 .failed => return error.FlagUpdateFailed,
1071 }
1072 }
1073 }
1074
1075 fn lockLinux(
1076 descriptor: Descriptor,
1077 mode: LockMode,
1078 blocking: bool,
1079 ) LockError!bool {
1080 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1081 var operation: i32 = switch (mode) {
1082 .shared => posix.LOCK.SH,
1083 .exclusive => posix.LOCK.EX,
1084 };
1085 if (!blocking) operation |= posix.LOCK.NB;
1086 while (true) switch (linux.errno(linux.flock(descriptor, operation))) {
1087 .SUCCESS => return true,
1088 .INTR => continue,
1089 .AGAIN => return false,
1090 .BADF => return error.InvalidDescriptor,
1091 else => return error.LockFailed,
1092 };
1093 }
1094
1095 fn lockPosix(
1096 descriptor: Descriptor,
1097 mode: LockMode,
1098 blocking: bool,
1099 ) LockError!bool {
1100 if (!@hasDecl(system, "flock") or @TypeOf(system.flock) == void) {
1101 return error.UnsupportedPlatform;
1102 }
1103 var operation: i32 = switch (mode) {
1104 .shared => posix.LOCK.SH,
1105 .exclusive => posix.LOCK.EX,
1106 };
1107 if (!blocking) operation |= posix.LOCK.NB;
1108 while (true) switch (posix.errno(system.flock(descriptor, operation))) {
1109 .SUCCESS => return true,
1110 .INTR => continue,
1111 .AGAIN => return false,
1112 .BADF => return error.InvalidDescriptor,
1113 else => return error.LockFailed,
1114 };
1115 }
1116
1117 fn setNonBlockingLinux(descriptor: Descriptor) FlagError!void {
1118 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1119
1120 var flags: usize = while (true) {
1121 const rc = linux.fcntl(descriptor, linux.F.GETFL, 0);
1122 switch (linux.errno(rc)) {
1123 .SUCCESS => break @intCast(rc),
1124 .INTR => continue,
1125 else => return error.FlagUpdateFailed,
1126 }
1127 };
1128 flags |= @as(u32, @bitCast(linux.O{ .NONBLOCK = true }));
1129
1130 while (true) {
1131 const rc = linux.fcntl(descriptor, linux.F.SETFL, flags);
1132 switch (linux.errno(rc)) {
1133 .SUCCESS => return,
1134 .INTR => continue,
1135 .INVAL => return error.InvalidFlags,
1136 else => return error.FlagUpdateFailed,
1137 }
1138 }
1139 }
1140
1141 fn setNonBlockingPosix(descriptor: Descriptor) FlagError!void {
1142 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
1143 return error.UnsupportedPlatform;
1144 }
1145
1146 var flags: usize = while (true) {
1147 const rc = system.fcntl(descriptor, posix.F.GETFL, @as(usize, 0));
1148 switch (posix.errno(rc)) {
1149 .SUCCESS => break @intCast(rc),
1150 .INTR => continue,
1151 else => return error.FlagUpdateFailed,
1152 }
1153 };
1154 flags |= @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
1155
1156 while (true) {
1157 const rc = system.fcntl(descriptor, posix.F.SETFL, flags);
1158 switch (posix.errno(rc)) {
1159 .SUCCESS => return,
1160 .INTR => continue,
1161 .INVAL => return error.InvalidFlags,
1162 else => return error.FlagUpdateFailed,
1163 }
1164 }
1165 }
1166
1167 fn closeOnExecLinux(descriptor: Descriptor) FlagError!bool {
1168 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1169 while (true) {
1170 const rc = linux.fcntl(descriptor, linux.F.GETFD, 0);
1171 switch (linux.errno(rc)) {
1172 .SUCCESS => return rc & linux.FD_CLOEXEC != 0,
1173 .INTR => continue,
1174 else => return error.FlagUpdateFailed,
1175 }
1176 }
1177 }
1178
1179 fn closeOnExecPosix(descriptor: Descriptor) FlagError!bool {
1180 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
1181 return error.UnsupportedPlatform;
1182 }
1183 while (true) {
1184 const rc = system.fcntl(descriptor, posix.F.GETFD, @as(usize, 0));
1185 switch (posix.errno(rc)) {
1186 .SUCCESS => return @as(usize, @intCast(rc)) & posix.FD_CLOEXEC != 0,
1187 .INTR => continue,
1188 else => return error.FlagUpdateFailed,
1189 }
1190 }
1191 }
1192
1193 fn nonBlockingLinux(descriptor: Descriptor) FlagError!bool {
1194 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1195 while (true) {
1196 const rc = linux.fcntl(descriptor, linux.F.GETFL, 0);
1197 switch (linux.errno(rc)) {
1198 .SUCCESS => {
1199 const nonblocking: u32 = @bitCast(linux.O{ .NONBLOCK = true });
1200 return rc & nonblocking != 0;
1201 },
1202 .INTR => continue,
1203 else => return error.FlagUpdateFailed,
1204 }
1205 }
1206 }
1207
1208 fn nonBlockingPosix(descriptor: Descriptor) FlagError!bool {
1209 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
1210 return error.UnsupportedPlatform;
1211 }
1212 while (true) {
1213 const rc = system.fcntl(descriptor, posix.F.GETFL, @as(usize, 0));
1214 switch (posix.errno(rc)) {
1215 .SUCCESS => {
1216 const nonblocking: u32 = @bitCast(posix.O{ .NONBLOCK = true });
1217 return @as(usize, @intCast(rc)) & nonblocking != 0;
1218 },
1219 .INTR => continue,
1220 else => return error.FlagUpdateFailed,
1221 }
1222 }
1223 }
1224
1225 fn isOpenLinux(descriptor: Descriptor) bool {
1226 if (comptime native_os != .linux) return false;
1227 while (true) {
1228 const rc = linux.fcntl(descriptor, linux.F.GETFD, 0);
1229 switch (linux.errno(rc)) {
1230 .SUCCESS => return true,
1231 .INTR => continue,
1232 .BADF => return false,
1233 else => return false,
1234 }
1235 }
1236 }
1237
1238 fn isOpenPosix(descriptor: Descriptor) bool {
1239 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) return false;
1240 while (true) {
1241 const rc = system.fcntl(descriptor, posix.F.GETFD, @as(usize, 0));
1242 switch (posix.errno(rc)) {
1243 .SUCCESS => return true,
1244 .INTR => continue,
1245 .BADF => return false,
1246 else => return false,
1247 }
1248 }
1249 }
1250
1251 fn readLinux(descriptor: Descriptor, buffer: []u8) ReadError!usize {
1252 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1253 while (true) {
1254 const rc = linux.read(descriptor, buffer.ptr, buffer.len);
1255 switch (linux.errno(rc)) {
1256 .SUCCESS => return @intCast(rc),
1257 .INTR => continue,
1258 .AGAIN => return error.WouldBlock,
1259 .IO => return error.InputOutput,
1260 .PIPE => return error.BrokenPipe,
1261 .CONNRESET => return error.ConnectionResetByPeer,
1262 else => return error.ReadFailed,
1263 }
1264 }
1265 }
1266
1267 fn readPosix(descriptor: Descriptor, buffer: []u8) ReadError!usize {
1268 if (@hasDecl(system, "read")) {
1269 while (true) {
1270 const rc = system.read(descriptor, buffer.ptr, buffer.len);
1271 switch (posix.errno(rc)) {
1272 .SUCCESS => return @intCast(rc),
1273 .INTR => continue,
1274 .AGAIN => return error.WouldBlock,
1275 .IO => return error.InputOutput,
1276 .PIPE => return error.BrokenPipe,
1277 .CONNRESET => return error.ConnectionResetByPeer,
1278 else => return error.ReadFailed,
1279 }
1280 }
1281 }
1282
1283 return error.UnsupportedPlatform;
1284 }
1285
1286 fn writeLinux(descriptor: Descriptor, bytes: []const u8) WriteError!usize {
1287 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1288 while (true) {
1289 const rc = linux.write(descriptor, bytes.ptr, bytes.len);
1290 switch (linux.errno(rc)) {
1291 .SUCCESS => return @intCast(rc),
1292 .INTR => continue,
1293 .AGAIN => return error.WouldBlock,
1294 .PIPE => return error.BrokenPipe,
1295 else => return error.WriteFailed,
1296 }
1297 }
1298 }
1299
1300 fn writePosix(descriptor: Descriptor, bytes: []const u8) WriteError!usize {
1301 if (@hasDecl(system, "write")) {
1302 while (true) {
1303 const rc = system.write(descriptor, bytes.ptr, bytes.len);
1304 switch (posix.errno(rc)) {
1305 .SUCCESS => return @intCast(rc),
1306 .INTR => continue,
1307 .AGAIN => return error.WouldBlock,
1308 .PIPE => return error.BrokenPipe,
1309 else => return error.WriteFailed,
1310 }
1311 }
1312 }
1313
1314 return error.UnsupportedPlatform;
1315 }
1316
1317 fn duplicateLinux(descriptor: Descriptor) DuplicateError!Descriptor {
1318 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1319 while (true) {
1320 const rc = linux.dup(descriptor);
1321 switch (linux.errno(rc)) {
1322 .SUCCESS => return @intCast(rc),
1323 .INTR => continue,
1324 .MFILE => return error.ProcessFdQuotaExceeded,
1325 .NFILE => return error.SystemFdQuotaExceeded,
1326 else => return error.DuplicateFailed,
1327 }
1328 }
1329 }
1330
1331 fn duplicatePosix(descriptor: Descriptor) DuplicateError!Descriptor {
1332 if (@hasDecl(system, "dup") and @TypeOf(system.dup) != void) {
1333 while (true) {
1334 const rc = system.dup(descriptor);
1335 switch (posix.errno(rc)) {
1336 .SUCCESS => return @intCast(rc),
1337 .INTR => continue,
1338 .MFILE => return error.ProcessFdQuotaExceeded,
1339 .NFILE => return error.SystemFdQuotaExceeded,
1340 else => return error.DuplicateFailed,
1341 }
1342 }
1343 }
1344
1345 return error.UnsupportedPlatform;
1346 }
1347
1348 fn duplicateAtLeastLinux(
1349 descriptor: Descriptor,
1350 minimum: Descriptor,
1351 ) DuplicateError!Descriptor {
1352 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1353 if (minimum < 0) return error.DuplicateFailed;
1354 while (true) {
1355 const rc = linux.fcntl(descriptor, linux.F.DUPFD_CLOEXEC, @intCast(minimum));
1356 switch (linux.errno(rc)) {
1357 .SUCCESS => return @intCast(rc),
1358 .INTR => continue,
1359 .MFILE => return error.ProcessFdQuotaExceeded,
1360 .NFILE => return error.SystemFdQuotaExceeded,
1361 else => return error.DuplicateFailed,
1362 }
1363 }
1364 }
1365
1366 fn duplicateAtLeastPosix(
1367 descriptor: Descriptor,
1368 minimum: Descriptor,
1369 ) DuplicateError!Descriptor {
1370 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
1371 return error.UnsupportedPlatform;
1372 }
1373 if (minimum < 0) return error.DuplicateFailed;
1374 while (true) {
1375 const rc = system.fcntl(
1376 descriptor,
1377 posix.F.DUPFD_CLOEXEC,
1378 @as(usize, @intCast(minimum)),
1379 );
1380 switch (posix.errno(rc)) {
1381 .SUCCESS => return @intCast(rc),
1382 .INTR => continue,
1383 .MFILE => return error.ProcessFdQuotaExceeded,
1384 .NFILE => return error.SystemFdQuotaExceeded,
1385 else => return error.DuplicateFailed,
1386 }
1387 }
1388 }
1389
1390 fn duplicateToLinux(source: Descriptor, target: Descriptor) DuplicateError!void {
1391 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1392 while (true) {
1393 const rc = linux.dup2(source, target);
1394 switch (linux.errno(rc)) {
1395 .SUCCESS => {
1396 std.debug.assert(rc == @as(usize, @intCast(target)));
1397 return;
1398 },
1399 .INTR => continue,
1400 .MFILE => return error.ProcessFdQuotaExceeded,
1401 .NFILE => return error.SystemFdQuotaExceeded,
1402 else => return error.DuplicateFailed,
1403 }
1404 }
1405 }
1406
1407 fn duplicateToPosix(source: Descriptor, target: Descriptor) DuplicateError!void {
1408 if (@hasDecl(system, "dup2") and @TypeOf(system.dup2) != void) {
1409 while (true) {
1410 const rc = system.dup2(source, target);
1411 switch (posix.errno(rc)) {
1412 .SUCCESS => {
1413 std.debug.assert(rc == target);
1414 return;
1415 },
1416 .INTR => continue,
1417 .MFILE => return error.ProcessFdQuotaExceeded,
1418 .NFILE => return error.SystemFdQuotaExceeded,
1419 else => return error.DuplicateFailed,
1420 }
1421 }
1422 }
1423 return error.UnsupportedPlatform;
1424 }
1425
1426 fn pipeLinux(flags: PipeFlags) PipeError![2]Descriptor {
1427 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1428 var descriptors: [2]Descriptor = undefined;
1429 while (true) {
1430 switch (linux.errno(linux.pipe2(&descriptors, linuxPipeFlags(flags)))) {
1431 .SUCCESS => return descriptors,
1432 .INTR => continue,
1433 .INVAL => return error.InvalidFlags,
1434 .NFILE => return error.SystemFdQuotaExceeded,
1435 .MFILE => return error.ProcessFdQuotaExceeded,
1436 else => return error.PipeFailed,
1437 }
1438 }
1439 }
1440
1441 fn pipePosix(flags: PipeFlags) PipeError![2]Descriptor {
1442 var descriptors: [2]Descriptor = undefined;
1443 if (@hasDecl(system, "pipe2") and @TypeOf(system.pipe2) != void) {
1444 while (true) {
1445 switch (posix.errno(system.pipe2(&descriptors, flags))) {
1446 .SUCCESS => return descriptors,
1447 .INTR => continue,
1448 .INVAL => return error.InvalidFlags,
1449 .NFILE => return error.SystemFdQuotaExceeded,
1450 .MFILE => return error.ProcessFdQuotaExceeded,
1451 else => return error.PipeFailed,
1452 }
1453 }
1454 }
1455
1456 if (@hasDecl(system, "pipe") and @TypeOf(system.pipe) != void) {
1457 while (true) {
1458 switch (posix.errno(system.pipe(&descriptors))) {
1459 .SUCCESS => break,
1460 .INTR => continue,
1461 .NFILE => return error.SystemFdQuotaExceeded,
1462 .MFILE => return error.ProcessFdQuotaExceeded,
1463 else => return error.PipeFailed,
1464 }
1465 }
1466 errdefer {
1467 close(descriptors[0]);
1468 close(descriptors[1]);
1469 }
1470 try applyPipeFlags(descriptors, flags);
1471 return descriptors;
1472 }
1473
1474 return error.UnsupportedPlatform;
1475 }
1476
1477 fn socketPairUnixStreamLinux(options: SocketPairOptions) SocketPairError![2]Descriptor {
1478 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1479 const close_flag: u32 = if (options.close_on_exec) linux.SOCK.CLOEXEC else 0;
1480 const socket_type: u32 = linux.SOCK.STREAM | close_flag;
1481 var descriptors: [2]Descriptor = undefined;
1482 while (true) {
1483 const rc = linux.socketpair(linux.AF.UNIX, socket_type, 0, &descriptors);
1484 switch (linux.errno(rc)) {
1485 .SUCCESS => return descriptors,
1486 .INTR => continue,
1487 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1488 .INVAL => return error.ProtocolUnsupportedBySystem,
1489 .MFILE => return error.ProcessFdQuotaExceeded,
1490 .NFILE => return error.SystemFdQuotaExceeded,
1491 .NOBUFS, .NOMEM => return error.SystemResources,
1492 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
1493 .PROTOTYPE => return error.SocketModeUnsupported,
1494 else => return error.SocketPairFailed,
1495 }
1496 }
1497 }
1498
1499 fn socketPairUnixStreamPosix(options: SocketPairOptions) SocketPairError![2]Descriptor {
1500 if (!@hasDecl(system, "socketpair") or @TypeOf(system.socketpair) == void) {
1501 return error.UnsupportedPlatform;
1502 }
1503
1504 const close_flag: u32 = if (options.close_on_exec and !socket_flags_unsupported) posix.SOCK.CLOEXEC else 0;
1505 const socket_type: u32 = posix.SOCK.STREAM | close_flag;
1506
1507 var descriptors: [2]Descriptor = undefined;
1508 while (true) {
1509 const rc = system.socketpair(posix.AF.UNIX, socket_type, 0, &descriptors);
1510 switch (posix.errno(rc)) {
1511 .SUCCESS => {
1512 if (options.close_on_exec and socket_flags_unsupported) {
1513 errdefer {
1514 close(descriptors[0]);
1515 close(descriptors[1]);
1516 }
1517 setCloseOnExec(descriptors[0]) catch return error.SocketPairFailed;
1518 setCloseOnExec(descriptors[1]) catch return error.SocketPairFailed;
1519 }
1520 return descriptors;
1521 },
1522 .INTR => continue,
1523 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1524 .INVAL => return error.ProtocolUnsupportedBySystem,
1525 .MFILE => return error.ProcessFdQuotaExceeded,
1526 .NFILE => return error.SystemFdQuotaExceeded,
1527 .NOBUFS, .NOMEM => return error.SystemResources,
1528 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
1529 .PROTOTYPE => return error.SocketModeUnsupported,
1530 else => return error.SocketPairFailed,
1531 }
1532 }
1533 }
1534
1535 fn linuxPipeFlags(flags: PipeFlags) linux.O {
1536 return @bitCast(@as(u32, @bitCast(flags)));
1537 }
1538
1539 fn applyPipeFlags(descriptors: [2]Descriptor, flags: PipeFlags) PipeError!void {
1540 if (@as(u32, @bitCast(flags)) == 0) return;
1541
1542 if (flags.CLOEXEC) {
1543 setCloseOnExec(descriptors[0]) catch return error.PipeFailed;
1544 setCloseOnExec(descriptors[1]) catch return error.PipeFailed;
1545 }
1546
1547 var status_flags = flags;
1548 status_flags.CLOEXEC = false;
1549 const status_flag_bits: u32 = @bitCast(status_flags);
1550 if (status_flag_bits == 0) return;
1551
1552 try setStatusFlagsForPipe(descriptors[0], status_flag_bits);
1553 try setStatusFlagsForPipe(descriptors[1], status_flag_bits);
1554 }
1555
1556 fn pipeFlags(options: PipeOptions) PipeFlags {
1557 return .{
1558 .CLOEXEC = options.close_on_exec,
1559 .NONBLOCK = options.nonblocking,
1560 };
1561 }
1562
1563 fn setStatusFlagsForPipe(descriptor: Descriptor, flags: u32) PipeError!void {
1564 return switch (descriptorPolicy()) {
1565 .unsupported => error.UnsupportedPlatform,
1566 .linux_syscall => setStatusFlagsForPipeLinux(descriptor, flags),
1567 .posix_host => setStatusFlagsForPipePosix(descriptor, flags),
1568 };
1569 }
1570
1571 fn setStatusFlagsForPipeLinux(descriptor: Descriptor, flags: u32) PipeError!void {
1572 if (comptime native_os != .linux) return error.UnsupportedPlatform;
1573 switch (linux.errno(linux.fcntl(descriptor, linux.F.SETFL, flags))) {
1574 .SUCCESS => {},
1575 .INVAL => return error.InvalidFlags,
1576 else => return error.PipeFailed,
1577 }
1578 }
1579
1580 fn setStatusFlagsForPipePosix(descriptor: Descriptor, flags: u32) PipeError!void {
1581 if (!@hasDecl(system, "fcntl") or @TypeOf(system.fcntl) == void) {
1582 return error.UnsupportedPlatform;
1583 }
1584
1585 switch (posix.errno(system.fcntl(descriptor, posix.F.SETFL, flags))) {
1586 .SUCCESS => {},
1587 .INVAL => return error.InvalidFlags,
1588 else => return error.PipeFailed,
1589 }
1590 }
1591
1592 test "descriptor policy is explicit" {
1593 const expected: DescriptorPolicy = switch (native_os) {
1594 .windows, .wasi, .freestanding => .unsupported,
1595 .linux => .linux_syscall,
1596 else => .posix_host,
1597 };
1598 try std.testing.expectEqual(expected, descriptorPolicy());
1599 }
1600
1601 test "descriptor status measures allocation and does not follow links" {
1602 if (comptime native_os != .linux and !is_darwin) return error.SkipZigTest;
1603 var temporary = std.testing.tmpDir(.{});
1604 defer temporary.cleanup();
1605 var file = try temporary.dir.createFile(std.testing.io, "target", .{ .read = true });
1606 defer file.close(std.testing.io);
1607 var payload: [4096]u8 = @splat(0x5a);
1608 try file.writeStreamingAll(std.testing.io, &payload);
1609 try temporary.dir.symLink(std.testing.io, "target", "linked", .{});
1610
1611 const target = try statusAt(temporary.dir.handle, "target");
1612 const linked = try statusAt(temporary.dir.handle, "linked");
1613 try std.testing.expectEqual(FileKind.regular, target.kind);
1614 try std.testing.expect(target.allocated_bytes >= payload.len);
1615 try std.testing.expectEqual(FileKind.symbolic_link, linked.kind);
1616 try std.testing.expect(linked.identity.inode != target.identity.inode);
1617 }
1618
1619 test "filesystem space scales block counts with checked arithmetic" {
1620 const observed = try scaleFilesystemSpace(4096, 10, 4, 3);
1621 try std.testing.expectEqual(@as(u64, 40_960), observed.total_bytes);
1622 try std.testing.expectEqual(@as(u64, 16_384), observed.free_bytes);
1623 try std.testing.expectEqual(@as(u64, 12_288), observed.available_bytes);
1624 try std.testing.expectError(
1625 error.SpaceUnavailable,
1626 scaleFilesystemSpace(0, 10, 4, 3),
1627 );
1628 try std.testing.expectError(
1629 error.Overflow,
1630 scaleFilesystemSpace(2, std.math.maxInt(u64), 4, 3),
1631 );
1632 }
1633
1634 test "filesystem space reports an open directory descriptor" {
1635 if (comptime native_os != .linux and !is_darwin) return error.SkipZigTest;
1636 if (comptime !supportedFilesystemSpaceArchitecture()) return error.SkipZigTest;
1637 var temporary = std.testing.tmpDir(.{});
1638 defer temporary.cleanup();
1639
1640 const observed = try filesystemSpace(temporary.dir.handle);
1641 try std.testing.expect(observed.total_bytes > 0);
1642 try std.testing.expect(observed.free_bytes <= observed.total_bytes);
1643 try std.testing.expect(observed.available_bytes <= observed.free_bytes);
1644 try std.testing.expectError(error.InvalidDescriptor, filesystemSpace(-1));
1645 }
1646
1647 test "mount identity is bound to an open descriptor" {
1648 if (comptime native_os != .linux and !is_darwin) return error.SkipZigTest;
1649 if (comptime is_darwin and !supportedFilesystemSpaceArchitecture()) {
1650 return error.SkipZigTest;
1651 }
1652 var temporary = std.testing.tmpDir(.{});
1653 defer temporary.cleanup();
1654 var file = try temporary.dir.createFile(std.testing.io, "member", .{ .read = true });
1655 defer file.close(std.testing.io);
1656
1657 const directory_identity = try mountIdentity(temporary.dir.handle);
1658 const file_identity = try mountIdentity(file.handle);
1659 try std.testing.expect(directory_identity.eql(file_identity));
1660 try std.testing.expectError(error.InvalidDescriptor, mountIdentity(-1));
1661
1662 if (comptime native_os == .linux) {
1663 var proc = std.Io.Dir.openDirAbsolute(std.testing.io, "/proc", .{}) catch
1664 return error.SkipZigTest;
1665 defer proc.close(std.testing.io);
1666 try std.testing.expect(!directory_identity.eql(try mountIdentity(proc.handle)));
1667 }
1668 }
1669
1670 test "Linux mount identity separates same-device mount identifiers" {
1671 if (comptime native_os != .linux) return error.SkipZigTest;
1672 var first = std.mem.zeroes(linux.Statx);
1673 first.mask.MNT_ID = true;
1674 first.dev_major = 8;
1675 first.dev_minor = 1;
1676 first.mnt_id = 41;
1677 var second = first;
1678 second.mnt_id = 42;
1679
1680 try std.testing.expect(!(try mountIdentityFromLinux(first)).eql(
1681 try mountIdentityFromLinux(second),
1682 ));
1683 first.mask.MNT_ID = false;
1684 try std.testing.expectError(
1685 error.MountIdentityUnavailable,
1686 mountIdentityFromLinux(first),
1687 );
1688 }
1689
1690 test "Darwin mount identity includes the exact mounted-on path" {
1691 var first_status = std.mem.zeroes(DarwinFilesystemStatus);
1692 first_status.filesystem_id = .{ 7, 9 };
1693 @memcpy(first_status.mounted_on[0..5], "/site");
1694 const first = try mountIdentityFromDarwin(first_status);
1695
1696 var second_status = first_status;
1697 @memcpy(second_status.mounted_on[0..5], "/else");
1698 const second = try mountIdentityFromDarwin(second_status);
1699 try std.testing.expect(!first.eql(second));
1700
1701 second_status = first_status;
1702 second_status.filesystem_id = .{ 7, 10 };
1703 try std.testing.expect(!first.eql(try mountIdentityFromDarwin(second_status)));
1704
1705 first_status.filesystem_id = .{ -1, -1 };
1706 try std.testing.expectError(
1707 error.MountIdentityUnavailable,
1708 mountIdentityFromDarwin(first_status),
1709 );
1710 first_status.filesystem_id = .{ 7, 9 };
1711 first_status.mounted_on = @splat(0);
1712 try std.testing.expectError(
1713 error.MountIdentityUnavailable,
1714 mountIdentityFromDarwin(first_status),
1715 );
1716 first_status.mounted_on = @splat('x');
1717 try std.testing.expectError(
1718 error.MountIdentityUnavailable,
1719 mountIdentityFromDarwin(first_status),
1720 );
1721 }
1722
1723 const MountInfoEntry = struct {
1724 mount_id: []const u8,
1725 device: []const u8,
1726 path: []const u8,
1727 };
1728
1729 const SameDeviceMountBoundary = enum {
1730 absent,
1731 separated,
1732 collapsed,
1733 };
1734
1735 fn parseMountInfoEntry(line: []const u8) ?MountInfoEntry {
1736 var fields = std.mem.tokenizeScalar(u8, line, ' ');
1737 const mount_id = fields.next() orelse return null;
1738 _ = fields.next() orelse return null;
1739 const device = fields.next() orelse return null;
1740 _ = fields.next() orelse return null;
1741 const path = fields.next() orelse return null;
1742 if (path.len == 0 or path[0] != '/' or std.mem.indexOfScalar(u8, path, '\\') != null) {
1743 return null;
1744 }
1745 return .{ .mount_id = mount_id, .device = device, .path = path };
1746 }
1747
1748 fn observeSameDeviceMountBoundary(mountinfo: []const u8) SameDeviceMountBoundary {
1749 var first_lines = std.mem.splitScalar(u8, mountinfo, '\n');
1750 while (first_lines.next()) |first_line| {
1751 const first = parseMountInfoEntry(first_line) orelse continue;
1752 var second_lines = std.mem.splitScalar(u8, mountinfo, '\n');
1753 while (second_lines.next()) |second_line| {
1754 const second = parseMountInfoEntry(second_line) orelse continue;
1755 if (std.mem.eql(u8, first.mount_id, second.mount_id) or
1756 !std.mem.eql(u8, first.device, second.device) or
1757 std.mem.eql(u8, first.path, second.path)) continue;
1758
1759 var first_dir = std.Io.Dir.openDirAbsolute(
1760 std.testing.io,
1761 first.path,
1762 .{},
1763 ) catch continue;
1764 defer first_dir.close(std.testing.io);
1765 var second_dir = std.Io.Dir.openDirAbsolute(
1766 std.testing.io,
1767 second.path,
1768 .{},
1769 ) catch continue;
1770 defer second_dir.close(std.testing.io);
1771 const first_status = status(first_dir.handle) catch continue;
1772 const second_status = status(second_dir.handle) catch continue;
1773 if (first_status.identity.device_major != second_status.identity.device_major or
1774 first_status.identity.device_minor != second_status.identity.device_minor) continue;
1775
1776 const first_identity = mountIdentity(first_dir.handle) catch continue;
1777 const second_identity = mountIdentity(second_dir.handle) catch continue;
1778 return if (first_identity.eql(second_identity)) .collapsed else .separated;
1779 }
1780 }
1781 return .absent;
1782 }
1783
1784 test "Linux runtime separates visible same-device mounts" {
1785 if (comptime native_os != .linux) return error.SkipZigTest;
1786 var mountinfo_file = std.Io.Dir.openFileAbsolute(
1787 std.testing.io,
1788 "/proc/self/mountinfo",
1789 .{},
1790 ) catch return error.SkipZigTest;
1791 defer mountinfo_file.close(std.testing.io);
1792 var storage: [128 * 1024]u8 = undefined;
1793 var length: usize = 0;
1794 while (length < storage.len) {
1795 const read_count = try read(mountinfo_file.handle, storage[length..]);
1796 if (read_count == 0) break;
1797 length += read_count;
1798 }
1799 if (length == storage.len) return error.SkipZigTest;
1800
1801 const observed = observeSameDeviceMountBoundary(storage[0..length]);
1802 if (observed == .absent) return error.SkipZigTest;
1803 try std.testing.expectEqual(SameDeviceMountBoundary.separated, observed);
1804 }
1805
1806 const DescriptorFlagRetryFixture = struct {
1807 get_results: []const DescriptorFlagResult,
1808 set_results: []const DescriptorFlagResult,
1809 get_index: usize = 0,
1810 set_index: usize = 0,
1811 updated: usize = 0,
1812
1813 fn get(self: *@This(), _: Descriptor) DescriptorFlagResult {
1814 const result = self.get_results[self.get_index];
1815 self.get_index += 1;
1816 return result;
1817 }
1818
1819 fn set(self: *@This(), _: Descriptor, flags: usize) DescriptorFlagResult {
1820 self.updated = flags;
1821 const result = self.set_results[self.set_index];
1822 self.set_index += 1;
1823 return result;
1824 }
1825 };
1826
1827 test "close on exec preserves descriptor flags and retries interruptions" {
1828 const get_results = [_]DescriptorFlagResult{
1829 .interrupted,
1830 .{ .success = 0x40 },
1831 };
1832 const set_results = [_]DescriptorFlagResult{
1833 .interrupted,
1834 .{ .success = 0 },
1835 };
1836 var operations: DescriptorFlagRetryFixture = .{
1837 .get_results = &get_results,
1838 .set_results = &set_results,
1839 };
1840
1841 try setCloseOnExecWith(&operations, 7, 0x01);
1842
1843 try std.testing.expectEqual(@as(usize, 2), operations.get_index);
1844 try std.testing.expectEqual(@as(usize, 2), operations.set_index);
1845 try std.testing.expectEqual(@as(usize, 0x41), operations.updated);
1846 }
1847
1848 const DescriptorFlagFailureFixture = struct {
1849 get_result: DescriptorFlagResult,
1850 set_result: DescriptorFlagResult,
1851
1852 fn get(self: *@This(), _: Descriptor) DescriptorFlagResult {
1853 return self.get_result;
1854 }
1855
1856 fn set(self: *@This(), _: Descriptor, _: usize) DescriptorFlagResult {
1857 return self.set_result;
1858 }
1859 };
1860
1861 test "close on exec reports descriptor and update failures" {
1862 var invalid_descriptor: DescriptorFlagFailureFixture = .{
1863 .get_result = .failed,
1864 .set_result = .{ .success = 0 },
1865 };
1866 try std.testing.expectError(
1867 error.FlagUpdateFailed,
1868 setCloseOnExecWith(&invalid_descriptor, -1, 0x01),
1869 );
1870
1871 var invalid_update: DescriptorFlagFailureFixture = .{
1872 .get_result = .{ .success = 0x40 },
1873 .set_result = .invalid,
1874 };
1875 try std.testing.expectError(
1876 error.InvalidFlags,
1877 setCloseOnExecWith(&invalid_update, 7, 0x01),
1878 );
1879 }
1880
1881 test "pipe read write round trip" {
1882 const descriptors = pipe(.{ .CLOEXEC = true }) catch |err| switch (err) {
1883 error.UnsupportedPlatform => return error.SkipZigTest,
1884 else => return err,
1885 };
1886 defer close(descriptors[0]);
1887 defer close(descriptors[1]);
1888
1889 try std.testing.expectEqual(@as(usize, 5), try write(descriptors[1], "hello"));
1890
1891 var buffer: [8]u8 = undefined;
1892 const count = try read(descriptors[0], &buffer);
1893 try std.testing.expectEqualStrings("hello", buffer[0..count]);
1894 }
1895
1896 test "duplicate descriptor writes to the same pipe" {
1897 const descriptors = pipe(.{ .CLOEXEC = true }) catch |err| switch (err) {
1898 error.UnsupportedPlatform => return error.SkipZigTest,
1899 else => return err,
1900 };
1901 defer close(descriptors[0]);
1902 defer close(descriptors[1]);
1903
1904 const copied = duplicate(descriptors[1]) catch |err| switch (err) {
1905 error.UnsupportedPlatform => return error.SkipZigTest,
1906 else => return err,
1907 };
1908 defer close(copied);
1909
1910 try std.testing.expectEqual(@as(usize, 4), try write(copied, "copy"));
1911
1912 var buffer: [8]u8 = undefined;
1913 const count = try read(descriptors[0], &buffer);
1914 try std.testing.expectEqualStrings("copy", buffer[0..count]);
1915 }
1916
1917 test "duplicate at least preserves a high inherited lease range" {
1918 const descriptors = pipe(.{ .CLOEXEC = true }) catch |err| switch (err) {
1919 error.UnsupportedPlatform => return error.SkipZigTest,
1920 else => return err,
1921 };
1922 defer close(descriptors[0]);
1923 defer close(descriptors[1]);
1924 const copied = duplicateAtLeast(descriptors[1], 128) catch |err| switch (err) {
1925 error.UnsupportedPlatform => return error.SkipZigTest,
1926 else => return err,
1927 };
1928 defer close(copied);
1929 try std.testing.expect(copied >= 128);
1930 try std.testing.expect(try closeOnExec(copied));
1931 }
1932
1933 test "duplicate to replaces one exact descriptor" {
1934 const descriptors = pipe(.{ .CLOEXEC = true }) catch |err| switch (err) {
1935 error.UnsupportedPlatform => return error.SkipZigTest,
1936 else => return err,
1937 };
1938 defer close(descriptors[0]);
1939 defer close(descriptors[1]);
1940 const target = try duplicate(descriptors[0]);
1941 close(target);
1942 try duplicateTo(descriptors[1], target);
1943 defer close(target);
1944 try std.testing.expectEqual(@as(usize, 5), try write(target, "exact"));
1945 var buffer: [8]u8 = undefined;
1946 const count = try read(descriptors[0], &buffer);
1947 try std.testing.expectEqualStrings("exact", buffer[0..count]);
1948 }
1949
1950 test "Unix stream socket pair read write round trip" {
1951 const descriptors = socketPairUnixStream(.{ .close_on_exec = true }) catch |err| switch (err) {
1952 error.UnsupportedPlatform => return error.SkipZigTest,
1953 else => return err,
1954 };
1955 defer close(descriptors[0]);
1956 defer close(descriptors[1]);
1957
1958 try std.testing.expectEqual(@as(usize, 4), try write(descriptors[0], "ping"));
1959
1960 var buffer: [8]u8 = undefined;
1961 const count = try read(descriptors[1], &buffer);
1962 try std.testing.expectEqualStrings("ping", buffer[0..count]);
1963 }