Archived
3
0

Add library in src/ to exchange file descriptor.

This commit is contained in:
Philippe Pittoli 2023-01-03 01:31:20 +01:00
parent b43fd57704
commit 78670e5b71

View File

@ -0,0 +1,217 @@
const std = @import("std");
const testing = std.testing;
const os = std.os;
const print = std.debug.print;
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(@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(*Header, self);
}
pub fn dataPtr(self: *Self) *align(data_align) T {
return @ptrCast(*T, self.bytes[data_offset..]);
}
};
}
test {
std.testing.refAllDecls(Cmsghdr([3]std.os.fd_t));
}
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, .{
.name = undefined,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = &cmsg,
.controllen = @sizeOf(@TypeOf(cmsg)),
.flags = 0,
}, 0) catch |err| {
print("error sendmsg failed with {s}", .{@errorName(err)});
return;
};
if (len != msg.len) {
// we don't have much choice but to exit here
// log.err(@src(), "expected sendmsg to return {} but got {}", .{msg.len, len});
print("expected sendmsg to return {} but got {}", .{msg.len, len});
os.exit(0xff);
}
}
// WARNING: errors aren't RECEPTION errors.
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(*std.x.os.Socket.Message, &m), @intCast(c_int, 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(usize, rc);
}
} else {
switch (errno(rc)) {
.SUCCESS => return @intCast(usize, 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),
}
}
}
}
pub fn receive_fd(sockfd: os.socket_t) !os.fd_t {
var buffer: [100]u8 = undefined;
var iov = [1]os.iovec{
.{
.iov_base = buffer[0..],
.iov_len = buffer.len,
},
};
var cmsg = Cmsghdr(os.fd_t).init(.{
.level = os.SOL.SOCKET,
.@"type" = SCM_RIGHTS,
.data = undefined,
});
var msg: std.os.msghdr = .{
.name = undefined,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = &cmsg,
.controllen = @sizeOf(@TypeOf(cmsg)),
.flags = 0,
};
const len = recvmsg(sockfd, msg, 0) catch |err| {
print("error sendmsg failed with {s}", .{@errorName(err)});
return 0;
};
print("received {} bytes, fd is {}\n", .{len, @as(i32, cmsg.dataPtr().*)});
print("iov base {s}\n", .{iov[0].iov_base[0..iov[0].iov_len - 1]});
return @as(i32, cmsg.dataPtr().*);
}