view sciworkerd/task.c @ 64:562372396019

misc: improve manual pages and documentation
author David Demelier <markand@malikania.fr>
date Thu, 18 Aug 2022 20:17:18 +0200
parents 67470b67e460
children 71cd8447e3a4
line wrap: on
line source

/*
 * task.c -- worker task management
 *
 * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "log.h"
#include "task.h"
#include "util.h"

#define MODE S_IRUSR | S_IWUSR | S_IXUSR

struct task {
	enum taskstatus status;
	pid_t child;
	int pipe[2];
	int exitcode;
	int sigcode;
	char *job_tag;
	FILE *fp;
	char *console;
	size_t consolesz;
	char scriptpath[PATH_MAX];
	time_t startup;
};

struct task *
task_new(const char *tag)
{
	struct task *task;

	task = util_calloc(1, sizeof (*task));
	task->job_tag = util_strdup(tag);
	task->pipe[0] = task->pipe[1] = -1;
	task->child = -1;
	task->fp = util_open_memstream(&task->console, &task->consolesz);

	return task;
}

int
task_setup(struct task *self, const char *script)
{
	assert(self);
	assert(script);

	const size_t len = strlen(script);
	int fd = -1, ret = -1;

	snprintf(self->scriptpath, sizeof (self->scriptpath), "/tmp/sciworkerd-XXXXXX");

	if ((fd = mkstemp(self->scriptpath)) < 0)
		goto failed;
	if (fchmod(fd, MODE) < 0)
		goto failed;
	if (write(fd, script, len) != (ssize_t)len)
		goto failed;

	ret = 0;

failed:
	if (ret == -1)
		log_warn("%s", strerror(errno));

	close(fd);

	return ret;
}

pid_t
task_start(struct task *self)
{
	assert(self);
	assert(self->status == TASKSTATUS_PENDING);

	if (pipe(self->pipe) < 0)
		return -1;

	switch ((self->child = fork())) {
	case -1:
		return -1;
	case 0:
		dup2(self->pipe[1], STDOUT_FILENO);
		dup2(self->pipe[1], STDERR_FILENO);
		close(self->pipe[0]);
		close(self->pipe[1]);
		close(STDIN_FILENO);

		if (execl(self->scriptpath, self->scriptpath, self->job_tag, NULL) < 0)
			_exit(1);
		break;
	default:
		close(self->pipe[1]);
		self->pipe[1] = -1;
		self->status = TASKSTATUS_RUNNING;
		self->startup = time(NULL);
		break;
	}

	return self->child;
}

int
task_wait(struct task *self)
{
	assert(self);
	assert(self->status == TASKSTATUS_RUNNING);

	int status;
	pid_t ret;

	while ((ret = waitpid(self->child, &status, 0)) < 0 && errno == EINTR)
		continue;
	if (ret < 0)
		return ret;

	if (WIFEXITED(status)) {
		self->exitcode = WEXITSTATUS(status);
		self->status = TASKSTATUS_EXITED;
	} else if (WIFSIGNALED(status)) {
		self->sigcode = WTERMSIG(status);
		self->status = TASKSTATUS_KILLED;
	}

	self->child = -1;

	/* Close file output so user can get access to the console. */
	fclose(self->fp);
	self->fp = NULL;

	return 0;
}

int
task_kill(struct task *self)
{
	assert(self);
	assert(self->status == TASKSTATUS_RUNNING);

	if (kill(self->child, SIGTERM) < 0)
		return -1;

	return 0;
}

time_t
task_uptime(const struct task *self)
{
	assert(self);

	return self->startup;
}

pid_t
task_pid(const struct task *self)
{
	assert(self);
	assert(self->status == TASKSTATUS_RUNNING);

	return self->child;
}

const char *
task_console(const struct task *self)
{
	assert(self);

	return self->console;
}

void
task_prepare(struct task *self, struct pollfd *fd)
{
	assert(self);
	assert(self->status == TASKSTATUS_RUNNING);
	assert(fd);

	fd->fd = self->pipe[0];
	fd->events = POLLIN | POLLPRI;
}

int
task_sync(struct task *self, const struct pollfd *fd)
{
	assert(self);
	assert(self->status == TASKSTATUS_RUNNING);
	assert(fd->fd == self->pipe[0]);

	char buf[BUFSIZ];
	ssize_t nr;

	if (fd->revents & POLLHUP)
		return 0;
	/* If we read EOF, it usually means the process has exited correctly. */
	if (fd->revents & POLLIN) {
		if ((nr = read(self->pipe[0], buf, sizeof (buf))) <= 0)
			return nr;
		if (fwrite(buf, 1, nr, self->fp) != (size_t)nr)
			return -1;
	}

	return 1;
}

enum taskstatus
task_status(const struct task *self)
{
	assert(self);

	return self->status;
}

struct taskcode
task_code(const struct task *self)
{
	assert(self);

	return (struct taskcode) {
		.exitcode = self->exitcode,
		.sigcode = self->sigcode
	};
}

void
task_free(struct task *self)
{
	assert(self);
	assert(self->status != TASKSTATUS_RUNNING);

	if (self->pipe[0])
		close(self->pipe[0]);
	if (self->pipe[1])
		close(self->pipe[1]);
	if (self->fp)
		fclose(self->fp);
	if (self->scriptpath[0])
		unlink(self->scriptpath);

	free(self->job_tag);
	free(self->console);
	free(self);
}