// SPDX-License-Identifier: MIT
#include <libfebug.hpp>
#include <sys/un.h>
#include <errno.h>
#include <inttypes.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#if __APPLE__
// GUESS WHAT?? DARWIN DEFINES SOCK_SEQPACKET BUT DOESN'T ACTUALLY FUCKING SUPPORT IT (i.e. socket(2) returns EPROTONOSUPPORT). WHY? BECAUSE FUCK YOU.
#undef SOCK_SEQPACKET
#define SOCK_SEQPACKET SOCK_STREAM
#endif
febug::controlled_socket::controlled_socket(const char * path) noexcept {
if(path && std::getenv("FEBUG_DONT"))
return;
if((this->fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) == -1) {
std::fprintf(stderr, "febug::controlled_socket: socket: %s\n", std::strerror(errno));
return;
}
if(auto pe = std::getenv("FEBUG_SOCKET"))
path = pe;
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if(connect(this->fd, (const sockaddr *)&addr, sizeof(addr)) == -1) {
std::fprintf(stderr, "febug::controlled_socket: connect: %s\n", std::strerror(errno));
close(this->fd);
this->fd = -1;
return;
}
#if __linux__ || __OpenBSD__ || __APPLE__
// Handled automatically with SO_PASSCRED, also the manual variant didn't work for some reason
// Only way is getsockopt(SO_PEERCRED)
// Only way is getsockopt(LOCAL_PEERCRED)+getsockopt(LOCAL_PEEREPID)
#elif __NetBSD__
// Correct way is automatically via LOCAL_CREDS
// However, the message /must/ be sent after the peer sets it; use a sync message from the server for this,
// otherwise we sent the first febug_message too quickly sometimes
attn_febug_message sync_msg{};
if(recv(this->fd, &sync_msg, sizeof(sync_msg), 0) != sizeof(sync_msg)) {
std::fprintf(stderr, "febug::controlled_socket: recv: %s\n", std::strerror(errno));
close(this->fd);
this->fd = -1;
return;
}
#else
// From FreeBSD 12.1-RELEASE-p7 /usr/include/socket.h:
/*
* Credentials structure, used to verify the identity of a peer
* process that has sent us a message. This is allocated by the
* peer process but filled in by the kernel. This prevents the
* peer from lying about its identity. (Note that cmcred_groups[0]
* is the effective GID.)
*/
char cmsgbuf[CMSG_SPACE(sizeof(cmsgcred))];
msghdr msg{};
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
auto cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDS;
cmsg->cmsg_len = CMSG_LEN(sizeof(cmsgcred));
msg.msg_controllen = cmsg->cmsg_len; // total size of all control blocks
if((sendmsg(this->fd, &msg, 0)) == -1) {
fprintf(stderr, "febug::controlled_socket: sendmsg: %m\n");
close(this->fd);
this->fd = -1;
return;
}
#endif
}
febug::controlled_socket::~controlled_socket() noexcept {
if(this->fd != -1) {
close(this->fd);
this->fd = -1;
}
}
febug::controlled_socket febug::global_controlled_socket;
std::map<std::size_t, void (*)(int, std::size_t)> febug::formatters;
void febug::debug_handler(int) noexcept {
if(global_controlled_socket == -1)
return;
attn_febug_message afmsg{};
iovec buf_i{&afmsg, sizeof(afmsg)};
int retpipe = -1;
char cmsgbuf[CMSG_SPACE(sizeof(retpipe))];
msghdr msg{};
msg.msg_iov = &buf_i;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
if(recvmsg(global_controlled_socket, &msg, 0) == -1) {
std::fprintf(stderr, "recvmsg: %s\n", std::strerror(errno));
return;
}
if(auto cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr && cmsg->cmsg_type == SCM_RIGHTS) {
memcpy(&retpipe, CMSG_DATA(cmsg), sizeof(retpipe));
// std::cerr << "got " << retpipe << ": " << afmsg.variable_id << ", " << afmsg.variable_type << '\n';
if(const auto itr = formatters.find(afmsg.variable_type); itr != formatters.end())
itr->second(retpipe, afmsg.variable_id);
else
dprintf(retpipe, "Unknown variable type %" PRIu64 " with ID %" PRIu64 "\n", afmsg.variable_type, afmsg.variable_id);
close(retpipe);
} else
std::fprintf(stderr, "febug::debug_handler: cmsg: no fd\n");
}