view scid.c @ 3:215c0c3b3609

misc: use JSON everywhere (scictl/sciwebd)
author David Demelier <markand@malikania.fr>
date Mon, 14 Jun 2021 22:08:24 +0200
parents 5fa3d2f479b2
children 9c4fea43803c
line wrap: on
line source

/*
 * scid.c -- main scid(8) program file
 *
 * Copyright (c) 2021 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/un.h>
#include <sys/socket.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "db.h"
#include "log.h"
#include "types.h"
#include "util.h"

static char dbpath[PATH_MAX] = VARDIR "/db/sci/sci.db";
static char scpath[PATH_MAX] = VARDIR "/run/sci.sock";
static int sc;

noreturn static inline void
usage(void)
{
	fprintf(stderr, "usage: %s [-d database]\n", getprogname());
	exit(1);
}

static void
init(void)
{
	struct sockaddr_un sun;
	int reuse = 1;

	memset(&sun, 0, sizeof (sun));
	strlcpy(sun.sun_path, scpath, sizeof (sun.sun_path));
	sun.sun_family = PF_LOCAL;

	log_open("scid");
	log_info("opening database %s", dbpath);
	log_info("opening incoming connection on %s", scpath);

	remove(scpath);

	if ((sc = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0)
		err(1, "socket");
	if (setsockopt(sc, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) < 0)
		err(1, "setsockopt");
	if (bind(sc, (const struct sockaddr *)&sun, sizeof (sun)) < 0)
		err(1, "bind");
	if (listen(sc, 32) < 0)
		err(1, "listen");
	if (db_open(dbpath) < 0)
		err(1, "unable to open database");
}

static int
answer(int fd, json_t *json)
{
	char *str;
	int ret = 0;

	str = json_dumps(json, JSON_COMPACT);

	if (send(fd, str, strlen(str), 0) < 0 || send(fd, "\r\n", 2, 0) < 0)
		ret = -1;

	free(str);
	json_decref(json);

	return ret;
}

static inline int
ok(int fd)
{
	return answer(fd, json_pack("{ss}", "status", "ok"));
}

static inline int
error(int fd, const char *fmt, ...)
{
	char buf[1024];
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof (buf), fmt, ap);
	va_end(ap);

	return answer(fd, json_pack("{ss ss}",
	    "status", "error",
	    "error", buf
	));
}

static int
cmd_job_add(int fd, json_t *doc)
{
	struct job job = {0};

	if (job_from(&job, 1, doc) < 0)
		return EINVAL;
	if (db_job_add(&job) < 0)
		return error(fd, "unable to create job");

	log_info("queued new job (%d), with tag %s", job.id, job.tag);

	return ok(fd);
}

static int
cmd_job_todo(int fd, json_t *doc)
{
	struct db_ctx ctx;
	struct job jobs[SCI_JOB_LIST_MAX];
	ssize_t jobsz;
	int worker_id, ret;

	if (json_unpack(doc, "{si}", "worker_id", &worker_id) < 0)
		return EINVAL;
	if ((jobsz = db_job_todo(&ctx, jobs, UTIL_SIZE(jobs), worker_id)) < 0)
		return error(fd, "unable to retrieve list");

	ret = answer(fd, job_to(jobs, jobsz));
	db_ctx_finish(&ctx);

	return ret;
}

static int
cmd_jobresult_add(int fd, json_t *doc)
{
	struct jobresult res = {0};

	if (jobresult_from(&res, 1, doc) < 0)
		return EINVAL;
	if (db_jobresult_add(&res) < 0)
		return error(fd, "unable to create job result");

	log_info("insert new result (%d), with job_id %d (exitcode=%d)",
	    res.id, res.job_id, res.exitcode);

	return ok(fd);
}

static int
cmd_project_add(int fd, json_t *doc)
{
	struct project proj = {0};

	if (project_from(&proj, 1, doc) < 0)
		return EINVAL;
	if (db_project_add(&proj) < 0)
		return error(fd, "unable to create project");

	log_info("created new project (%d) %s", proj.id, proj.name);

	return ok(fd);
}

static int
cmd_project_find(int fd, json_t *doc)
{
	struct db_ctx ctx;
	struct project proj = {0};
	int ret;

	if (json_unpack(doc, "{ss}", "name", &proj.name) < 0)
		return EINVAL;
	if (db_project_find(&ctx, &proj) < 0)
		return error(fd, "unable to retrieve project");

	ret = answer(fd, project_to(&proj, 1));
	db_ctx_finish(&ctx);

	return ret;
}

static int
cmd_project_find_id(int fd, json_t *doc)
{
	struct db_ctx ctx;
	struct project proj = {0};
	int ret;

	if (json_unpack(doc, "{si}", "id", &proj.id) < 0)
		return EINVAL;
	if (db_project_find_id(&ctx, &proj) < 0)
		return error(fd, "unable to retrieve project");

	ret = answer(fd, project_to(&proj, 1));
	db_ctx_finish(&ctx);

	return ret;
}

static int
cmd_project_list(int fd, json_t *doc)
{
	(void)doc;

	struct db_ctx ctx;
	struct project projects[SCI_PROJECT_MAX];
	ssize_t projectsz;
	int ret;

	if ((projectsz = db_project_list(&ctx, projects, UTIL_SIZE(projects))) < 0)
		return EINVAL;

	ret = answer(fd, project_to(projects, projectsz));
	db_ctx_finish(&ctx);

	return ret;
}

static int
cmd_worker_add(int fd, json_t *doc)
{
	struct worker wk = {0};

	if (worker_from(&wk, 1, doc) < 0)
		return EINVAL;
	if (db_worker_add(&wk) <0)
		return error(fd, "unable to create worker");

	log_info("created new worker (%d) %s", wk.id, wk.name);

	return ok(fd);
}

static int
cmd_worker_find(int fd, json_t *doc)
{
	struct worker wk = {0};
	struct db_ctx ctx;
	int ret;

	if (json_unpack(doc, "{ss}", "name", &wk.name) < 0)
		return EINVAL;
	if (db_worker_find(&ctx, &wk) < 0)
		return error(fd, "unable to retrieve worker");

	ret = answer(fd, worker_to(&wk, 1));
	db_ctx_finish(&ctx);

	return ret;
}

static int
cmd_worker_find_id(int fd, json_t *doc)
{
	struct worker wk = {0};
	struct db_ctx ctx;
	int ret;

	if (json_unpack(doc, "{si}", "id", &wk.id) < 0)
		return EINVAL;
	if (db_worker_find_id(&ctx, &wk) < 0)
		return error(fd, "unable to retrieve worker");

	ret = answer(fd, worker_to(&wk, 1));
	db_ctx_finish(&ctx);

	return ret;
}

static int
cmd_worker_list(int fd, json_t *doc)
{
	(void)doc;

	struct db_ctx ctx;
	struct worker workers[SCI_WORKER_MAX];
	ssize_t workersz;
	int ret;

	if ((workersz = db_worker_list(&ctx, workers, UTIL_SIZE(workers))) < 0)
		return error(fd, "unable to retrieve worker list");

	ret = answer(fd, worker_to(workers, workersz));
	db_ctx_finish(&ctx);

	return ret;
}

static void
dispatch(int fd, const char *msg)
{
	static const struct {
		const char *name;
		int (*exec)(int, json_t *);
	} cmds[] = {
		{ "job-add",            cmd_job_add             },
		{ "job-todo",           cmd_job_todo            },
		{ "jobresult-add",      cmd_jobresult_add       },
		{ "project-add",        cmd_project_add         },
		{ "project-find",       cmd_project_find        },
		{ "project-find-id",    cmd_project_find_id     },
		{ "project-list",       cmd_project_list        },
		{ "worker-add",         cmd_worker_add          },
		{ "worker-find",        cmd_worker_find         },
		{ "worker-find-id",     cmd_worker_find_id      },
		{ "worker-list",        cmd_worker_list         },
		{ NULL,                 NULL                    }
	};

	json_t *doc, *cmd;
	json_error_t err;

	if (!(doc = json_loads(msg, 0, &err)))
		return log_warn("json error: %s", err.text);
	if (!json_is_string((cmd = json_object_get(doc, "cmd")))) {
		json_decref(doc);
		return log_warn("json invalid");
	}

	for (size_t i = 0; cmds[i].name; ++i) {
		if (strcmp(cmds[i].name, json_string_value(cmd)) == 0) {
			int ret = 0;

			if ((ret = cmds[i].exec(fd, doc)) != 0)
				error(fd, strerror(errno));

			json_decref(doc);
			break;
		}
	}
}

static void
run(void)
{
	int client;
	FILE *fp;
	char *msg, *endl, buf[BUFSIZ];
	ssize_t nr;
	struct timeval tv = { .tv_sec = 30 };

	if ((client = accept(sc, NULL, 0)) < 0)
		err(1, "accept");
	if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)) < 0)
		err(1, "setsockopt");
	if (setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (tv)) < 0)
		err(1, "setsockopt");

	fp = util_fmemopen((msg = util_calloc(1, SCI_MSG_MAX)), SCI_MSG_MAX, "w");
	setbuf(fp, NULL);

	while (!strstr(msg, "\r\n") && (nr = recv(client, buf, sizeof (buf), 0)) > 0)
		if (fwrite(buf, 1, nr, fp) != (size_t)nr)
			warn("fwrite");

	if (nr < 0)
		warn("recv");
	if ((endl = strstr(msg, "\r\n")))
		*endl = 0;

	fclose(fp);
	dispatch(client, msg);
	close(client);
	free(msg);
}

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

	setprogname("scid");

	while ((ch = getopt(argc, argv, "d:s:")) != -1) {
		switch (ch) {
		case 'd':
			strlcpy(dbpath, optarg, sizeof (dbpath));
			break;
		case 's':
			strlcpy(scpath, optarg, sizeof (scpath));
			break;
		default:
			usage();
			break;
		}
	}

	argc -= optind;
	argv += optind;

	init();

	for (;;)
		run();
}