diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 01547a1a9df1c53ffdac6a370f2108136d6b6af2..9fe2661af17a69649629252ea200407a0151b798 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1378,7 +1378,7 @@ pub const Stream = struct { pub const Reader = struct { io: Io, interface: Io.Reader, - control_buffer: []u8, + control_buffer: []align(cmsg_align) u8, control_len: usize, control_truncated: bool, stream: Stream, @@ -1391,7 +1391,12 @@ pub const Stream = struct { } /// Same as `init`, but also provides a buffer for storing control data. - pub fn initWithControl(stream: Stream, io: Io, buffer: []u8, control_buffer: []u8) Reader { + pub fn initWithControl( + stream: Stream, + io: Io, + buffer: []u8, + control_buffer: []align(cmsg_align) u8, + ) Reader { return .{ .io = io, .interface = .{ @@ -1442,11 +1447,16 @@ pub const Stream = struct { return result.data_len; } - /// Get buffered control data. - pub fn controlSlice(r: *const Reader) []u8 { + /// Access raw buffered control data. + pub fn controlSlice(r: *const Reader) []align(cmsg_align) u8 { return r.control_buffer[0..r.control_len]; } + /// Iterate over buffered control messages. + pub fn controlIterator(r: *const Reader) cmsg.Iterator { + return .{ .control = r.controlSlice() }; + } + /// Clear buffered control data. pub fn clearControl(r: *Reader) void { r.control_len = 0; @@ -1542,7 +1552,12 @@ pub const Stream = struct { } /// Same as `reader`, but also provides a buffer for storing control data. - pub fn readerWithControl(stream: Stream, io: Io, buffer: []u8, control_buffer: []u8) Reader { + pub fn readerWithControl( + stream: Stream, + io: Io, + buffer: []u8, + control_buffer: []align(cmsg_align) u8, + ) Reader { return .initWithControl(stream, io, buffer, control_buffer); } @@ -1551,6 +1566,53 @@ pub const Stream = struct { } }; +pub const cmsg_align = if (@TypeOf(std.posix.cmsg_align) == void) 1 else std.posix.cmsg_align; +/// Utility Functions for interacting with POSIX socket control messages. +/// See also https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html +pub const cmsg = struct { + const cmsghdr = std.posix.cmsghdr; + + /// Equivalent to `CMSG_SPACE` in C. + pub fn space(data_len: usize) usize { + return std.mem.alignForward(usize, @sizeOf(cmsghdr), cmsg_align) + + std.mem.alignForward(usize, data_len, cmsg_align); + } + + /// Equivalent to `CMSG_LEN` in C. + pub fn len(data_len: @FieldType(cmsghdr, "len")) @TypeOf(data_len) { + return std.mem.alignForward(@TypeOf(data_len), @sizeOf(cmsghdr), cmsg_align) + data_len; + } + + /// Equivalent to `CMSG_DATA` in C. + pub fn data(header: *align(cmsg_align) cmsghdr) []align(cmsg_align) u8 { + const bytes: [*]u8 = @ptrCast(header); + return @alignCast(bytes[0..header.len][std.mem.alignForward(usize, @sizeOf(cmsghdr), cmsg_align)..]); + } + + /// Equivalent to `CMSG_FIRSTHDR` and `CMSG_NXTHDR` in C. + pub const Iterator = struct { + control: []align(cmsg_align) u8, + + pub const Message = struct { + header: *cmsghdr, + data: []align(cmsg_align) u8, + }; + + pub fn next(it: *Iterator) ?Message { + if (it.control.len < @sizeOf(cmsghdr)) return null; + const header: *align(cmsg_align) cmsghdr = @ptrCast(it.control.ptr); + if (it.control.len < header.len) return null; + const next_header = std.mem.alignForward(usize, header.len, cmsg_align); + if (it.control.len < next_header) { + it.control = &.{}; + } else { + it.control = @alignCast(it.control[next_header..]); + } + return .{ .header = header, .data = data(header) }; + } + }; +}; + pub const Server = struct { socket: Socket, options: AcceptOptions, diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 0edd1fbee6e0a30b9ae38904daf6a74e8a697062..2c5a4a852e201d6480b1e4e790560c787d881602 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -4,6 +4,7 @@ const std = @import("std"); const Io = std.Io; const net = std.Io.net; const mem = std.mem; +const posix = std.posix; const testing = std.testing; const Allocator = std.mem.Allocator; @@ -321,26 +322,6 @@ test "listen on a unix socket, pass file descriptor" { }; defer server.socket.close(io); - const cmsg_alignment = switch (builtin.os.tag) { - .driverkit, - .ios, - .maccatalyst, - .macos, - .tvos, - .visionos, - .watchos, - => 4, - else => @sizeOf(usize), - }; - - const cmsghdr_aligned_len = comptime mem.alignForward(usize, @sizeOf(std.posix.cmsghdr), cmsg_alignment); - const cmsghdr_align = @alignOf(std.posix.cmsghdr); - const fd_t_len = @sizeOf(std.posix.fd_t); - - const cmsg_buf_len = - cmsghdr_aligned_len + - comptime mem.alignForward(usize, fd_t_len, cmsg_alignment); - const S = struct { fn clientFn(path: []const u8, file_path: []const u8) !void { const temp_file = try Io.Dir.cwd().createFile(io, file_path, .{}); @@ -350,16 +331,16 @@ test "listen on a unix socket, pass file descriptor" { var stream = try server_path.connect(io); defer stream.close(io); - var cmsg_buf: [cmsg_buf_len]u8 align(cmsghdr_align) = @splat(0); + var cmsg_buf: [net.cmsg.space(@sizeOf(posix.fd_t))]u8 align(Io.net.cmsg_align) = @splat(0); - const header_ptr: *std.posix.cmsghdr = @ptrCast(&cmsg_buf); - header_ptr.* = .{ - .len = @intCast(cmsghdr_aligned_len + fd_t_len), - .level = std.posix.SOL.SOCKET, - .type = std.posix.SCM.RIGHTS, + const header: *align(Io.net.cmsg_align) posix.cmsghdr = @ptrCast(&cmsg_buf); + header.* = .{ + .len = net.cmsg.len(@sizeOf(posix.fd_t)), + .level = posix.SOL.SOCKET, + .type = posix.SCM.RIGHTS, }; - const fds = mem.bytesAsSlice(std.posix.fd_t, cmsg_buf[cmsghdr_aligned_len..]); + const fds: []posix.fd_t = @ptrCast(net.cmsg.data(header)); fds[0] = temp_file.handle; var stream_writer = stream.writer(io, &.{}); @@ -376,27 +357,26 @@ test "listen on a unix socket, pass file descriptor" { var stream = try server.accept(io); defer stream.close(io); - var control_buf: [32]u8 align(cmsghdr_align) = undefined; + var control_buf: [32]u8 align(Io.net.cmsg_align) = undefined; var buf: [16]u8 = undefined; var stream_reader = stream.readerWithControl(io, &.{}, &control_buf); const n = try stream_reader.interface.readSliceShort(&buf); - const cmsg_buf = stream_reader.controlSlice(); try testing.expect(!stream_reader.control_truncated); // The control buffer should be big enough to receive the entire control message try testing.expectEqual(12, n); try testing.expectEqualStrings("Hello world!", buf[0..n]); - try testing.expect(cmsg_buf.len >= cmsghdr_aligned_len + fd_t_len); + var it = stream_reader.controlIterator(); + const control = it.next() orelse return error.TestUnexpectedResult; + try testing.expectEqual(null, it.next()); - const header_ptr: *const std.posix.cmsghdr = @ptrCast(@alignCast(cmsg_buf)); + try testing.expectEqual(net.cmsg.len(@sizeOf(posix.fd_t)), control.header.len); + try testing.expectEqual(posix.SOL.SOCKET, control.header.level); + try testing.expectEqual(posix.SCM.RIGHTS, control.header.type); - try testing.expectEqual(cmsghdr_aligned_len + fd_t_len, header_ptr.len); - try testing.expectEqual(std.posix.SOL.SOCKET, header_ptr.level); - try testing.expectEqual(std.posix.SCM.RIGHTS, header_ptr.type); - - const fds = mem.bytesAsSlice(std.posix.fd_t, cmsg_buf[cmsghdr_aligned_len..]); + const fds: []posix.fd_t = @ptrCast(control.data); const temp_file: Io.File = .{ .handle = fds[0], diff --git a/lib/std/c.zig b/lib/std/c.zig index 51c6656d4f21dc23717f757faddc76f109049e2a..1ce75c984c1ac06382bfe2ec79303d0ec26ae128 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -4432,6 +4432,68 @@ const posix_cmsghdr = extern struct { type: c_int, }; +// Alignment used by POSIX specified CMSG_* Macros +// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html +pub const cmsg_align = switch (native_os) { + .linux => linux.cmsg_align, + // https://github.com/emscripten-core/emscripten/blob/96371ed7888fc78c040179f4d4faa82a6a07a116/system/lib/libc/musl/include/sys/socket.h#L362 + .emscripten => @sizeOf(usize), + // https://github.com/freebsd/freebsd-src/blob/b197d2abcb6895d78bc9df8404e374397aa44748/sys/sys/socket.h#L584 + // https://github.com/freebsd/freebsd-src/blob/d2018cedb414ef17e77b7fc2fa19fd81328da46b/sys/sys/_align.h#L30 + .freebsd => @sizeOf(*anyopaque), + // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d1f4fb943c73e2b61ddabff6aa48ce7551db72f4/sys/sys/socket.h#L441 + // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d1f4fb943c73e2b61ddabff6aa48ce7551db72f4/sys/cpu/x86_64/include/alignbytes.h#L35 + .dragonfly => switch (builtin.cpu.arch) { + .x86_64 => @sizeOf(c_long), + else => {}, + }, + // https://github.com/NetBSD/src/blob/ba8e1774fd9c0c26ecca461c07bc95d9ebb69579/sys/sys/socket.h#L544 + .netbsd => switch (builtin.cpu.arch) { + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/arm/include/cdefs.h#L63 + .aarch64, .aarch64_be => @sizeOf(i128), + .arm, .armeb => @sizeOf(c_longlong), + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/i386/include/cdefs.h#L9 + .x86 => @sizeOf(c_int), + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/amd64/include/cdefs.h#L6 + .x86_64 => @sizeOf(c_long), + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/mips/include/cdefs.h#L74 + .mips, .mips64, .mipsel, .mips64el => 8, + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/sparc/include/cdefs.h#L8 + .sparc64 => 16, + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/sparc/include/cdefs.h#L10 + .sparc => 8, + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/alpha/include/cdefs.h#L6 + .alpha => 8, + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/m68k/include/cdefs.h#L6 + .m68k => @sizeOf(c_int), + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/hppa/include/cdefs.h#L12 + .hppa => 8, + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/riscv/include/cdefs.h#L5 + .riscv32, .riscv32be, .riscv64, .riscv64be => @as(comptime_int, builtin.target.cMaxIntAlignment()), + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/powerpc/include/cdefs.h#L6 + .powerpc, .powerpcle, .powerpc64, .powerpc64le => @sizeOf(f64), + // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/or1k/include/cdefs.h#L6 + .or1k => @as(comptime_int, builtin.target.cMaxIntAlignment()), + else => {}, + }, + // https://github.com/openbsd/src/blob/4a7ecdb29021f26ee0ef397afdd2931e20e63071/sys/sys/socket.h#L564 + // https://github.com/openbsd/src/blob/4a7ecdb29021f26ee0ef397afdd2931e20e63071/sys/arch/amd64/include/_types.h#L50 + .openbsd => @sizeOf(c_long), + // https://github.com/illumos/illumos-gate/blob/c1e950d976948602134f0e0e5dadf3d48a7ee996/usr/src/uts/common/sys/socket.h#L469 + .illumos => 4, + // https://github.com/haiku/haiku/blob/b54f586058fd6623645512e4631468cede9933b9/headers/posix/sys/socket.h#L152 + // https://github.com/haiku/haiku/blob/6059d64cb7cd145f0f1eb5b89c383f71e4b6bebf/headers/posix/sys/param.h#L28 + .haiku => @sizeOf(c_long), + // https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1077 + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => 4, + else => {}, +}; +comptime { + if (@TypeOf(cmsg_align) != void and @TypeOf(cmsghdr) != void) { + assert(cmsg_align >= @alignOf(cmsghdr)); + } +} + pub const nfds_t = switch (native_os) { .linux => linux.nfds_t, .emscripten => emscripten.nfds_t, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index be4e4fd069e736ca170cde09def4e0545f9e8e21..38e4abd197ba6c17245e7fe7038d7db0528ca395 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct { type: i32, }; +// https://github.com/torvalds/linux/blob/72d3fcf802c45d00b300f25b848a93c3a2bd7c7e/include/linux/socket.h#L133 +pub const cmsg_align = @sizeOf(c_long); + pub const riscv_hwprobe = extern struct { key: i64, value: u64, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index ab621d54d7f6a059b307b75c541b28d03e289e8f..d845a8607dbb89550018673c8b20ec2db7aebdce 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -151,6 +151,7 @@ pub const blksize_t = system.blksize_t; pub const clock_t = system.clock_t; pub const clockid_t = system.clockid_t; pub const cmsghdr = system.cmsghdr; +pub const cmsg_align = system.cmsg_align; pub const timerfd_clockid_t = system.timerfd_clockid_t; pub const cpu_set_t = system.cpu_set_t; pub const dev_t = system.dev_t;