#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <bglpersonality.h>
static void __zoid_signal_handler(int signo);
static int server_sock;
/*
 * Function run at startup.  Just installs the signal handler that zoidd
 * will later trigger, and a server-side unix domain socket.
 */
void __zoid_ciod_startup(void) __attribute__((constructor));
void __zoid_ciod_startup(void)
{
    struct sigaction sa;
    struct sockaddr_un addr;
    sa.sa_handler = __zoid_signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGUSR1, &sa, NULL))
	perror("installing signal handler");
    if (!(server_sock = socket(PF_UNIX, SOCK_STREAM, 0)))
    {
	perror("create unix domain socket");
	return;
    }
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/zoidd.socket");
    if (bind(server_sock, (struct sockaddr*)&addr, sizeof(addr)))
    {
	perror("bind /tmp/zoidd.socket");
	return;
    }
    if (listen(server_sock, 5) < 0)
    {
	perror("listen");
	return;
    }
}
static void __zoid_signal_handler(int signo)
{
    BGLPersonality personality;
    int fd;
    FILE* file;
    char buffer[1024];
    int inode_8000 = -1, fd_8000 = -1;
    DIR* dir;
    struct dirent *de;
    struct msghdr msg = {0};
    struct cmsghdr *cmsg;
    char msgbuf[CMSG_SPACE(sizeof(int))];
    uid_t uid;
    int transfer_sock;
    struct iovec iov;
    char tmp;
    /* Get service node's IP address from personality.  */
    if ((fd = open("/proc/personality", O_RDONLY)) < 0)
    {
	perror("open /proc/personality");
	return;
    }
    if (read(fd, &personality, sizeof(personality)) != sizeof(personality))
    {
	perror("read personality");
	return;
    }
    close(fd);
    /* Find the inode of the port 8000 connection to the service node,
       used by CIOD to forward stdout/stderr.  */
    if (!(file = fopen("/proc/net/tcp", "r")))
    {
	perror("open /proc/net/tcp");
	return;
    }
    while (fgets(buffer, sizeof(buffer), file) == buffer)
    {
	int local_port, remote_address, inode;
	if (sscanf(buffer,
		   "%*d: %*x:%x %x:%*x %*x %*x:%*x %*x:%*x %*x %*d %*d %d",
		   &local_port, &remote_address, &inode) == 3 &&
	    remote_address == personality.serviceNode &&
	    local_port == 8000)
	{
	    inode_8000 = inode;
	    break;
	}
    }
    fclose(file);
    if (inode_8000 < 0)
    {
	fprintf(stderr, "Socket connection to the service node not found!\n");
	return;
    }
    /* Now locate the file descriptor that owns this socket.
       We need to switch back to root for a second to do that...  */
    uid = geteuid();
    if (seteuid(0) < 0)
    {
	perror("seteuid");
	return;
    }
    if (!(dir = opendir("/proc/self/fd")))
    {
	perror("open /proc/self/fd");
	return;
    }
    while ((de = readdir(dir)))
    {
	char buf2[1024];
	int len;
	sprintf(buffer, "/proc/self/fd/%s", de->d_name);
	if ((len = readlink(buffer, buf2, sizeof(buf2))) > 0 &&
	    len < sizeof(buf2))
	{
	    buf2[len] = '\0';
	    if (strncmp(buf2, "socket:", strlen("socket:")) == 0)
	    {
		int inode;
		char* endbuf;
		inode = strtol(buf2 + strlen("socket:["), &endbuf, 10);
		if (inode == inode_8000)
		{
		    fd_8000 = strtol(de->d_name, NULL, 10);
		    break;
		}
	    }
	}
    }
    closedir(dir);
    if (seteuid(uid) < 0)
    {
	perror("seteuid");
	return;
    }
    if (fd_8000 < 0)
    {
	fprintf(stderr, "Socket connection to the service node not found!\n");
	return;
    }
    if ((transfer_sock = accept(server_sock, NULL, 0)) < 0)
    {
	perror("accept");
	return;
    }
    /* This magic is taken from cmsg(3).  */
    iov.iov_base = &tmp;
    iov.iov_len = 1;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = msgbuf;
    msg.msg_controllen = sizeof(msgbuf);
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(cmsg), &fd_8000, sizeof(int));
    msg.msg_controllen = cmsg->cmsg_len;
    if (sendmsg(transfer_sock, &msg, 0) < 0)
    {
	perror("sendmsg");
	return;
    }
    close(transfer_sock);
    close(server_sock);
    kill(getpid(), SIGSTOP);
}