const std = @import("std"); const testing = std.testing; const os = std.os; const log = std.log.scoped(.libipc_exchangefd); const builtin = @import("builtin"); const windows = std.os.windows; const errno = std.os.errno; const system = std.os.system; const unexpectedErrno = std.os.unexpectedErrno; const SendMsgError = std.os.SendMsgError; const SCM_RIGHTS: c_int = 1; /// This definition enables the use of Zig types with a cmsghdr structure. /// The oddity of this layout is that the data must be aligned to @sizeOf(usize) /// rather than its natural alignment. pub fn Cmsghdr(comptime T: type) type { const Header = extern struct { len: usize, level: c_int, type: c_int, }; const data_align = @sizeOf(usize); const data_offset = std.mem.alignForward(usize, @sizeOf(Header), data_align); return extern struct { const Self = @This(); bytes: [data_offset + @sizeOf(T)]u8 align(@alignOf(Header)), pub fn init(args: struct { level: c_int, type: c_int, data: T, }) Self { var self: Self = undefined; self.headerPtr().* = .{ .len = data_offset + @sizeOf(T), .level = args.level, .type = args.type, }; self.dataPtr().* = args.data; return self; } // TODO: include this version if we submit a PR to add this to std pub fn initNoData(args: struct { level: c_int, type: c_int, }) Self { var self: Self = undefined; self.headerPtr().* = .{ .len = data_offset + @sizeOf(T), .level = args.level, .type = args.type, }; return self; } pub fn headerPtr(self: *Self) *Header { return @ptrCast(self); } pub fn dataPtr(self: *Self) *align(data_align) T { return @ptrCast(self.bytes[data_offset..]); } }; } test { std.testing.refAllDecls(Cmsghdr([3]std.os.fd_t)); } /// Send a file descriptor and a message through a UNIX socket. /// TODO: currently voluntarily crashes if data isn't sent properly, should return an error instead. pub fn send_fd(sockfd: os.socket_t, msg: []const u8, fd: os.fd_t) void { var iov = [_]os.iovec_const{ .{ .iov_base = msg.ptr, .iov_len = msg.len, }, }; var cmsg = Cmsghdr(os.fd_t).init(.{ .level = os.SOL.SOCKET, .type = SCM_RIGHTS, .data = fd, }); const len = os.sendmsg(sockfd, &std.os.msghdr_const{ .name = null, .namelen = 0, .iov = &iov, .iovlen = iov.len, .control = &cmsg, .controllen = @sizeOf(@TypeOf(cmsg)), .flags = 0, }, 0) catch |err| { log.err("error sendmsg failed with {s}", .{@errorName(err)}); return; }; if (len != msg.len) { // We don't have much choice but to exit here. log.err("expected sendmsg to return {} but got {}", .{ msg.len, len }); os.exit(0xff); } } /// WARNING: recvmsg is a WIP. /// WARNING: errors aren't RECEPTION errors. /// WARNING: can only work on linux for now (recvmsg is lacking on other systems). pub fn recvmsg( /// The file descriptor of the sending socket. sockfd: os.socket_t, /// Message header and iovecs msg: std.os.msghdr, flags: u32, ) SendMsgError!usize { while (true) { var m = msg; const rc = system.recvmsg(sockfd, @ptrCast(&m), @intCast(flags)); if (builtin.os.tag == .windows) { if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { .WSAEACCES => return error.AccessDenied, .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, .WSAECONNRESET => return error.ConnectionResetByPeer, .WSAEMSGSIZE => return error.MessageTooBig, .WSAENOBUFS => return error.SystemResources, .WSAENOTSOCK => return error.FileDescriptorNotASocket, .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, .WSAEDESTADDRREQ => unreachable, // A destination address is required. .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. .WSAEHOSTUNREACH => return error.NetworkUnreachable, // TODO: WSAEINPROGRESS, WSAEINTR .WSAEINVAL => unreachable, .WSAENETDOWN => return error.NetworkSubsystemFailed, .WSAENETRESET => return error.ConnectionResetByPeer, .WSAENETUNREACH => return error.NetworkUnreachable, .WSAENOTCONN => return error.SocketNotConnected, .WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH. .WSAEWOULDBLOCK => return error.WouldBlock, .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function. else => |err| return windows.unexpectedWSAError(err), } } else { return @intCast(rc); } } else { switch (errno(rc)) { .SUCCESS => return @intCast(rc), .ACCES => return error.AccessDenied, .AGAIN => return error.WouldBlock, .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => unreachable, // always a race condition .CONNRESET => return error.ConnectionResetByPeer, .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. .FAULT => unreachable, // An invalid user space address was specified for an argument. .INTR => continue, .INVAL => unreachable, // Invalid argument passed. .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified .MSGSIZE => return error.MessageTooBig, .NOBUFS => return error.SystemResources, .NOMEM => return error.SystemResources, .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. .PIPE => return error.BrokenPipe, .AFNOSUPPORT => return error.AddressFamilyNotSupported, .LOOP => return error.SymLinkLoop, .NAMETOOLONG => return error.NameTooLong, .NOENT => return error.FileNotFound, .NOTDIR => return error.NotDir, .HOSTUNREACH => return error.NetworkUnreachable, .NETUNREACH => return error.NetworkUnreachable, .NOTCONN => return error.SocketNotConnected, .NETDOWN => return error.NetworkSubsystemFailed, else => |err| return unexpectedErrno(err), } } } } /// Receive a file descriptor through a UNIX socket. /// A message can be carried with it, copied into 'buffer'. /// WARNING: buffer must be at least 1500 bytes. pub fn receive_fd(sockfd: os.socket_t, buffer: []u8, msg_size: *usize) !os.fd_t { var msg_buffer = [_]u8{0} ** 1500; var iov = [_]os.iovec{ .{ .iov_base = msg_buffer[0..], .iov_len = msg_buffer.len }, }; var cmsg = Cmsghdr(os.fd_t).init(.{ .level = os.SOL.SOCKET, .type = SCM_RIGHTS, .data = 0, }); const msg: std.os.msghdr = .{ .name = null, .namelen = 0, .iov = &iov, .iovlen = 1, .control = &cmsg, .controllen = @sizeOf(@TypeOf(cmsg)), .flags = 0 }; const msglen = recvmsg(sockfd, msg, 0) catch |err| { log.err("error recvmsg failed with {s}", .{@errorName(err)}); return 0; }; const received_fd = @as(i32, cmsg.dataPtr().*); std.mem.copy(u8, buffer, &msg_buffer); msg_size.* = msglen; return received_fd; }