voyeur-0.1.0.0: libvoyeur/src/voyeur.c
#ifdef __linux__
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <pthread.h>
#include <spawn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <unistd.h>
#ifdef __linux__
#include <bsd/bsd.h>
#endif
#include <voyeur.h>
#include "env.h"
#include "event.h"
#include "net.h"
#include "util.h"
typedef struct {
struct sockaddr_un sockinfo;
int server_sock;
void* env_buf;
} server_state;
voyeur_context_t voyeur_context_create()
{
voyeur_context* ctx = calloc(1, sizeof(voyeur_context));
return (voyeur_context_t) ctx;
}
void voyeur_context_destroy(voyeur_context_t ctx)
{
voyeur_context* context = (voyeur_context*) ctx;
if (context->resource_path) {
free(context->resource_path);
}
if (context->server_state) {
server_state* state = (server_state*) context->server_state;
free(state->env_buf);
free(state);
}
free(context);
}
void voyeur_set_resource_path(voyeur_context_t ctx,
const char* path)
{
voyeur_context* context = (voyeur_context*) ctx;
context->resource_path = calloc(1, strnlen(path, 4096));
strlcat(context->resource_path, path, 4096);
}
typedef struct {
pid_t child_pid;
int child_pipe_input;
} waitpid_thread_arg;
static void* waitpid_thread(void* arg_ptr)
{
waitpid_thread_arg* arg = (waitpid_thread_arg*) arg_ptr;
int status;
waitpid(arg->child_pid, &status, 0);
voyeur_write_int(arg->child_pipe_input, status);
voyeur_close_socket(arg->child_pipe_input);
free(arg);
return NULL;
}
static int start_waitpid_thread(pid_t child_pid)
{
// Create a pipe that will be used to announce the termination of
// the child process.
int waitpid_pipe[2];
ABORT_ON_FAIL(pipe, waitpid_pipe);
waitpid_thread_arg* arg = malloc(sizeof(waitpid_thread_arg));
arg->child_pid = child_pid;
arg->child_pipe_input = waitpid_pipe[1];
pthread_t thread;
pthread_create(&thread, NULL, waitpid_thread, (void*) arg);
pthread_detach(thread);
return waitpid_pipe[0];
}
static int accept_connection(int server_sock)
{
struct sockaddr_un client_info;
socklen_t client_info_len = sizeof(struct sockaddr_un);
int client_sock =
accept(server_sock, (struct sockaddr *) &client_info, &client_info_len);
WARN_ON_FAIL_VALUE(client_sock, "accept");
return client_sock;
}
static int handle_message(voyeur_context* context, int sock)
{
voyeur_msg_type msgtype;
if (voyeur_read_msg_type(sock, &msgtype) < 0) {
return -1;
}
if (msgtype == VOYEUR_MSG_DONE) {
// The client is done sending messages on this socket.
return -1;
} else if (msgtype == VOYEUR_MSG_EVENT) {
voyeur_event_type type;
if (voyeur_read_event_type(sock, &type) < 0) {
return -1;
}
// Got a voyeur event; dispatch to the appropriate handler.
voyeur_handle_event(context, type, sock);
return 0;
} else {
// Got an unknown message type.
voyeur_log("Unknown message type\n");
return -1;
}
}
static int run_server(voyeur_context* context,
int server_sock,
int child_pipe_output)
{
fd_set active_fd_set, read_fd_set, error_fd_set;
FD_ZERO(&active_fd_set);
FD_SET(server_sock, &active_fd_set);
FD_SET(child_pipe_output, &active_fd_set);
int child_exited = 0;
int child_status = 0;
while (!child_exited) {
// Block until input arrives.
read_fd_set = active_fd_set;
error_fd_set = active_fd_set;
if (select(FD_SETSIZE, &read_fd_set, NULL, &error_fd_set, NULL) < 0) {
if (errno == EAGAIN || errno == EINTR) {
continue; // This is a temporary error.
} else {
perror("select");
break; // This is unrecoverable.
}
}
for (int fd = 0 ; fd < FD_SETSIZE ; ++fd) {
if (FD_ISSET(fd, &error_fd_set)) {
voyeur_log("Closed file descriptor due to error\n");
voyeur_close_socket(fd);
FD_CLR(fd, &active_fd_set);
} else if (FD_ISSET(fd, &read_fd_set)) {
if (fd == server_sock) {
int client_sock = accept_connection(server_sock);
if (client_sock >= 0) {
FD_SET(client_sock, &active_fd_set);
}
} else if (fd == child_pipe_output) {
child_exited = 1;
voyeur_read_int(fd, &child_status);
voyeur_close_socket(fd);
FD_CLR(fd, &active_fd_set);
} else if (handle_message(context, fd) < 0) {
voyeur_close_socket(fd);
FD_CLR(fd, &active_fd_set);
}
}
}
}
voyeur_close_socket(server_sock);
// Clean up any stragglers.
for (int fd = 0 ; fd < FD_SETSIZE ; ++fd) {
if (FD_ISSET(fd, &active_fd_set)) {
voyeur_close_socket(fd);
}
}
if (WIFEXITED(child_status)) {
return WEXITSTATUS(child_status);
} else {
fprintf(stderr, "libvoyeur: child process did not terminate normally\n");
return -1;
}
}
char** voyeur_prepare(voyeur_context_t ctx, char* const envp[])
{
voyeur_context* context = (voyeur_context*) ctx;
server_state* state = calloc(1, sizeof(server_state));
context->server_state = (void*) state;
// Prepare the server. We need to do this in advance both to avoid
// racing and so that we can include the socket path in the
// environment variables.
state->server_sock = voyeur_create_server_socket(&state->sockinfo);
if (state->server_sock < 0) {
return NULL;
}
// Add libvoyeur-specific environment variables.
char* libs = voyeur_requested_libs(context);
char* opts = voyeur_requested_opts(context);
char** voyeur_envp = voyeur_augment_environment(envp, libs, opts,
state->sockinfo.sun_path,
&state->env_buf);
return voyeur_envp;
}
int voyeur_start(voyeur_context_t ctx, pid_t child_pid)
{
voyeur_context* context = (voyeur_context*) ctx;
server_state* state = (server_state*) context->server_state;
// Run the server.
int child_pipe_output = start_waitpid_thread(child_pid);
int res = run_server(context,
state->server_sock,
child_pipe_output);
// Clean up the socket file.
WARN_ON_FAIL(unlink, state->sockinfo.sun_path);
char* last_slash = strrchr(state->sockinfo.sun_path, '/');
if (last_slash) {
*last_slash = '\0';
}
WARN_ON_FAIL(rmdir, state->sockinfo.sun_path);
return res;
}
int voyeur_exec(voyeur_context_t ctx,
const char* path,
char* const argv[],
char* const envp[])
{
char** voyeur_envp = voyeur_prepare(ctx, envp);
if (!voyeur_envp) {
return -1;
}
pid_t child_pid;
if (posix_spawnp(&child_pid, path, NULL, NULL, argv, voyeur_envp) != 0) {
free(voyeur_envp);
return -1;
}
int retval = voyeur_start(ctx, child_pid);
free(voyeur_envp);
return retval;
}