2023-02-06 10:44:51 +01:00
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 ;
2023-02-07 07:47:00 +01:00
const errno = std . os . errno ;
2023-02-06 10:44:51 +01:00
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 ,
2023-02-07 07:47:00 +01:00
type : c_int ,
2023-02-06 10:44:51 +01:00
} ;
const data_align = @sizeOf ( usize ) ;
2023-07-01 13:43:41 +02:00
const data_offset = std . mem . alignForward ( usize , @sizeOf ( Header ) , data_align ) ;
2023-02-06 10:44:51 +01:00
return extern struct {
const Self = @This ( ) ;
bytes : [ data_offset + @sizeOf ( T ) ] u8 align ( @alignOf ( Header ) ) ,
pub fn init ( args : struct {
level : c_int ,
2023-02-07 07:47:00 +01:00
type : c_int ,
2023-02-06 10:44:51 +01:00
data : T ,
} ) Self {
var self : Self = undefined ;
self . headerPtr ( ) . * = . {
. len = data_offset + @sizeOf ( T ) ,
. level = args . level ,
2023-02-07 07:47:00 +01:00
. type = args . type ,
2023-02-06 10:44:51 +01:00
} ;
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 ,
2023-02-07 07:47:00 +01:00
type : c_int ,
2023-02-06 10:44:51 +01:00
} ) Self {
var self : Self = undefined ;
self . headerPtr ( ) . * = . {
. len = data_offset + @sizeOf ( T ) ,
. level = args . level ,
2023-02-07 07:47:00 +01:00
. type = args . type ,
2023-02-06 10:44:51 +01:00
} ;
return self ;
}
pub fn headerPtr ( self : * Self ) * Header {
2023-07-01 13:43:41 +02:00
return @ptrCast ( self ) ;
2023-02-06 10:44:51 +01:00
}
pub fn dataPtr ( self : * Self ) * align ( data_align ) T {
2023-07-01 13:43:41 +02:00
return @ptrCast ( self . bytes [ data_offset . . ] ) ;
2023-02-06 10:44:51 +01:00
}
} ;
}
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 ,
2023-02-07 07:47:00 +01:00
. type = SCM_RIGHTS ,
2023-02-06 10:44:51 +01:00
. data = fd ,
} ) ;
2023-02-06 14:15:48 +01:00
const len = os . sendmsg ( sockfd , & std . os . msghdr_const {
2023-02-06 10:44:51 +01:00
. name = null ,
. namelen = 0 ,
. iov = & iov ,
. iovlen = iov . len ,
. control = & cmsg ,
. controllen = @sizeOf ( @TypeOf ( cmsg ) ) ,
. flags = 0 ,
2023-02-07 07:47:00 +01:00
} , 0 ) catch | err | {
2023-02-06 10:44:51 +01:00
log . err ( " error sendmsg failed with {s} " , . { @errorName ( err ) } ) ;
return ;
} ;
if ( len ! = msg . len ) {
// We don't have much choice but to exit here.
2023-02-07 07:47:00 +01:00
log . err ( " expected sendmsg to return {} but got {} " , . { msg . len , len } ) ;
2023-02-06 10:44:51 +01:00
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 ;
2023-07-01 13:43:41 +02:00
const rc = system . recvmsg ( sockfd , @ptrCast ( & m ) , @intCast ( flags ) ) ;
2023-02-06 10:44:51 +01:00
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 {
2023-07-01 13:43:41 +02:00
return @intCast ( rc ) ;
2023-02-06 10:44:51 +01:00
}
} else {
switch ( errno ( rc ) ) {
2023-07-01 13:43:41 +02:00
. SUCCESS = > return @intCast ( rc ) ,
2023-02-06 10:44:51 +01:00
. 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 {
2023-02-07 07:47:00 +01:00
. { . iov_base = msg_buffer [ 0 . . ] , . iov_len = msg_buffer . len } ,
2023-02-06 10:44:51 +01:00
} ;
var cmsg = Cmsghdr ( os . fd_t ) . init ( . {
. level = os . SOL . SOCKET ,
2023-02-07 07:47:00 +01:00
. type = SCM_RIGHTS ,
2023-02-06 10:44:51 +01:00
. data = 0 ,
} ) ;
2024-03-21 02:53:25 +01:00
const msg : std . os . msghdr = . { . name = null , . namelen = 0 , . iov = & iov , . iovlen = 1 , . control = & cmsg , . controllen = @sizeOf ( @TypeOf ( cmsg ) ) , . flags = 0 } ;
2023-02-06 10:44:51 +01:00
2024-03-21 02:53:25 +01:00
const msglen = recvmsg ( sockfd , msg , 0 ) catch | err | {
2023-02-06 10:44:51 +01:00
log . err ( " error recvmsg failed with {s} " , . { @errorName ( err ) } ) ;
return 0 ;
} ;
2024-03-21 02:53:25 +01:00
const received_fd = @as ( i32 , cmsg . dataPtr ( ) . * ) ;
2023-02-06 10:44:51 +01:00
std . mem . copy ( u8 , buffer , & msg_buffer ) ;
msg_size . * = msglen ;
return received_fd ;
}