view sciworkerd.c @ 1:5afdb14df924

sci: add support for storing results
author David Demelier <markand@malikania.fr>
date Tue, 08 Jun 2021 08:40:01 +0200
parents f1de39079243
children 5fa3d2f479b2
line wrap: on
line source

#include <sys/queue.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "project.h"
#include "config.h"
#include "log.h"
#include "util.h"

struct job {
	pid_t child;
	int running;
	int pipe[2];
	char project[PROJECT_NAME_MAX];
	char out[SCI_CONSOLE_MAX];
	TAILQ_ENTRY(job) link;
};

struct fds {
	struct pollfd *list;
	size_t listsz;
};

struct result {
	pid_t pid;
	int status;
};

TAILQ_HEAD(jobs, job);

static struct jobs running = TAILQ_HEAD_INITIALIZER(running);
static struct jobs queue = TAILQ_HEAD_INITIALIZER(queue);
static int sigpipe[2];

static struct job *
find(const char *project)
{
	struct job *j;

	TAILQ_FOREACH(j, &running, link)
		if (strcmp(j->project, project) == 0)
			return j;

	return NULL;
}

static struct job *
find_by_fd(int fd)
{
	struct job *j;

	TAILQ_FOREACH(j, &running, link)
		if (j->pipe[0] == fd)
			return j;

	return NULL;
}

static struct job *
find_by_pid(pid_t pid)
{
	struct job *j;

	TAILQ_FOREACH(j, &running, link)
		if (j->child == pid)
			return j;

	return NULL;
}

static int
spawn(const char *project, const char *script)
{
	struct job *j;

	if (find(project))
		return -1;

	j = util_calloc(1, sizeof (*j));
	j->pipe[0] = j->pipe[1] = -1;
	j->running = 1;
	strlcpy(j->project, project, sizeof (j->project));

	if (pipe(j->pipe) < 0)
		goto cleanup;

	switch ((j->child = fork())) {
	case -1:
		goto cleanup;
	case 0:
		/* Child. */
		dup2(j->pipe[1], STDOUT_FILENO);
		dup2(j->pipe[1], STDERR_FILENO);
		close(j->pipe[0]);
		close(j->pipe[1]);

		if (execl(script, script, NULL) < 0)
			exit(0);
		break;
	default:
		/* Parent */
		TAILQ_INSERT_TAIL(&running, j, link);
		break;
	}

	return 0;

cleanup:
	if (j->pipe[0] != -1)
		close(j->pipe[0]);
	if (j->pipe[1] != -1)
		close(j->pipe[1]);

	free(j);

	return -1;
}

static void
complete(int signum, siginfo_t *sinfo, void *ctx)
{
	(void)ctx;
	(void)signum;

	struct result r;

	if (sinfo->si_code != CLD_EXITED)
		return;

	r.pid = sinfo->si_pid;
	r.status = 0;

	if (waitpid(sinfo->si_pid, &r.status, 0) < 0) {
		log_warn("waitpid: %s", strerror(errno));
		return;
	}

	/*
	 * Signal may happen at any time from any thread so we can't use
	 * mutexes so use the good old self-pipe trick. Yes, signals are
	 * probably the most fundamental broken UNIX feature.
	 */
	if (write(sigpipe[1], &r, sizeof (r)) < 0)
		err(1, "write");
}

static void
init(void)
{
	struct sigaction sa;
	int flags;

	sa.sa_flags = SA_SIGINFO;
	sa.sa_sigaction = complete;
	sigemptyset(&sa.sa_mask);

	if (sigaction(SIGCHLD, &sa, NULL) < 0)
		err(1, "sigaction");

	log_open("sciworkerd");

	if (pipe(sigpipe) < 0)
		err(1, "pipe");
	if ((flags = fcntl(sigpipe[1], F_GETFL, 0)) < 0 ||
	    fcntl(sigpipe[1], F_SETFL, flags | O_NONBLOCK) < 0)
		err(1, "fcntl");
}

static struct fds
prepare(void)
{
	struct fds fds = {0};
	struct job *j;
	size_t i = 1;

	TAILQ_FOREACH(j, &running, link)
		fds.listsz++;

	fds.list = util_calloc(++fds.listsz, sizeof (*fds.list));
	fds.list[0].fd = sigpipe[0];
	fds.list[0].events = POLLIN;

	TAILQ_FOREACH(j, &running, link) {
		fds.list[i].fd = j->pipe[0];
		fds.list[i++].events = POLLIN | POLLPRI;
	}

	return fds;
}

static void
finished(pid_t pid)
{
	struct job *job;

	if (!(job = find_by_pid(pid)))
		return;

	/* TODO: send response. */

	TAILQ_REMOVE(&running, job, link);
	free(job);
}

static void
run(void)
{
	struct fds fds;
	struct result r;
	struct job *job;
	char buf[BUFSIZ];
	ssize_t nr;

	fds = prepare();

	if (poll(fds.list, fds.listsz, -1) < 0 && errno != EINTR)
		err(1, "poll");

	for (size_t i = 1; i < fds.listsz; ++i) {
		if (fds.list[i].revents == 0)
			continue;
		if (!(job = find_by_fd(fds.list[i].fd)))
			continue;

		if ((nr = read(fds.list[i].fd, buf, sizeof (buf) - 1)) <= 0) {
			finished(job->child);
		} else {
			buf[nr] = 0;
			strlcat(job->out, buf, sizeof (job->out));
		}
	}

	if (fds.list->revents) {
		r.pid = 0;
		r.status = 0;

		if (read(sigpipe[0], &r, sizeof (r)) <= 0 && errno != EINTR)
			err(1, "read");

		finished(r.pid);
	}
}

int
main(int argc, char **argv)
{
	(void)argc;
	(void)argv;

	init();

	for (;;)
		run();
}