Mercurial > sci
changeset 18:600204c31bf0
misc: refactor
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 12 Jul 2022 20:20:51 +0200 |
parents | 40fe70256fb0 |
children | de4bf839b565 |
files | .hgignore Makefile config.mk db.c db.h doc/api.md http.c http.h lib/log.c lib/log.h lib/types.c lib/types.h lib/util.c lib/util.h log.c log.h page-api-jobs.c page-api-jobs.h page-api-projects.c page-api-projects.h page-api-workers.c page-api-workers.h page-index.c page.c page.h req.c req.h scictl.c scid.c scid/db.c scid/db.h scid/http.c scid/http.h scid/main.c scid/page-api-jobs.c scid/page-api-jobs.h scid/page-api-projects.c scid/page-api-projects.h scid/page-api-workers.c scid/page-api-workers.h scid/page-index.c scid/page.c scid/page.h sciwebd.c sciworkerd.c sciworkerd/main.c sciworkerd/task.c sciworkerd/task.h types.c types.h util.c util.h |
diffstat | 52 files changed, 3251 insertions(+), 3626 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed Oct 06 16:22:23 2021 +0200 +++ b/.hgignore Tue Jul 12 20:20:51 2022 +0200 @@ -20,10 +20,6 @@ # executables. ^bcc$ -^scictl$ -^scid$ -^sciwebd$ -^sciworkerd$ # tests. ^tests/test-db$
--- a/Makefile Wed Oct 06 16:22:23 2021 +0200 +++ b/Makefile Tue Jul 12 20:20:51 2022 +0200 @@ -20,53 +20,43 @@ include config.mk -LIBSCI_SRCS= db.c \ - log.c \ - req.c \ - types.c \ - util.c \ - extern/libsqlite/sqlite3.c -LIBSCI_DATA= sql/init.h \ - sql/job-add.h \ - sql/job-todo.h \ - sql/jobresult-add.h \ - sql/project-add.h \ - sql/project-find-id.h \ - sql/project-find.h \ - sql/project-list.h \ - sql/project-update.h \ - sql/worker-add.h \ - sql/worker-find.h \ - sql/worker-find-id.h \ - sql/worker-list.h +LIBSCI_SRCS= lib/log.c \ + lib/types.c \ + lib/util.c LIBSCI_OBJS= ${LIBSCI_SRCS:.c=.o} LIBSCI_DEPS= ${LIBSCI_SRCS:.c=.d} -SCID_SRCS= scid.c +SCID_SQL= sql/init.h \ + sql/job-add.h \ + sql/job-todo.h \ + sql/jobresult-add.h \ + sql/project-add.h \ + sql/project-find-id.h \ + sql/project-find.h \ + sql/project-list.h \ + sql/project-update.h \ + sql/worker-add.h \ + sql/worker-find.h \ + sql/worker-find-id.h \ + sql/worker-list.h + +SCID_SRCS= extern/libsqlite/sqlite3.c \ + scid/db.c \ + scid/http.c \ + scid/main.c \ + scid/page-api-jobs.c \ + scid/page-api-projects.c \ + scid/page-api-workers.c \ + scid/page.c SCID_OBJS= ${SCID_SRCS:.c=.o} SCID_DEPS= ${SCID_SRCS:.c=.d} -SCIWORKERD_SRCS= sciworkerd.c +SCIWORKERD_SRCS= sciworkerd/main.c sciworkerd/task.c SCIWORKERD_OBJS= ${SCIWORKERD_SRCS:.c=.o} SCIWORKERD_DEPS= ${SCIWORKERD_SRCS:.c=.d} -SCICTL_SRCS= scictl.c -SCICTL_OBJS= ${SCICTL_SRCS:.c=.o} -SCICTL_DEPS= ${SCICTL_SRCS:.c=.d} - -SCIWEBD_SRCS= http.c \ - page-api-jobs.c \ - page-api-projects.c \ - page-api-workers.c \ - page.c \ - sciwebd.c -SCIWEBD_OBJS= ${SCIWEBD_SRCS:.c=.o} -SCIWEBD_DEPS= ${SCIWEBD_SRCS:.c=.d} - MAN7= man/sci.7 -MAN8= man/scictl.8 \ - man/scid.8 \ - man/sciwebd.8 \ +MAN8= man/scid.8 \ man/sciworkerd.8 TESTS= tests/test-db.c @@ -87,6 +77,7 @@ INCS= -Iextern/libsqlite \ -Iextern/libgreatest \ + -Ilib \ -I. DEFS= -DVARDIR=\"${VARDIR}\" \ -DTMPDIR=\"${TMPDIR}\" \ @@ -98,7 +89,7 @@ .SUFFIXES: .SUFFIXES: .c .o .sql .h .in -all: scid scictl sciwebd sciworkerd ${MAN7} ${MAN8} +all: scid/scid sciworkerd/sciworkerd ${MAN7} ${MAN8} # for unit tests. .c: @@ -116,7 +107,7 @@ .sql.h: ./bcc -sc0 $< $< > $@ --include ${LIBSCI_DEPS} ${SCID_DEPS} ${SCIWORKERD_DEPS} ${SCICTL_DEPS} ${SCIWEBD_DEPS} ${TESTS_DEPS} +-include ${LIBSCI_DEPS} ${SCID_DEPS} ${SCIWORKERD_DEPS} ${TESTS_DEPS} config.h: @echo "using default configuration" @@ -125,39 +116,25 @@ bcc: extern/bcc/bcc.c ${CC} ${CFLAGS} -o $@ $< ${LDFLAGS} -${LIBSCI_DATA}: bcc -${LIBSCI_OBJS}: config.h ${LIBSCI_DATA} +${LIBSCI_OBJS}: config.h -libsci.a: ${LIBSCI_OBJS} +lib/libsci.a: ${LIBSCI_OBJS} ${AR} -rc $@ ${LIBSCI_OBJS} -${SCID_OBJS}: libsci.a - -scid: ${SCID_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCID_OBJS} libsci.a ${LIBBSD_LIBS} \ - ${JANSSON_LIBS} ${LDFLAGS} - -${SCIWORKERD_OBJS}: libsci.a - -sciworkerd: ${SCIWORKERD_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCIWORKERD_OBJS} libsci.a ${LIBBSD_LIBS} \ - ${LIBCURL_LIBS} ${JANSSON_LIBS} ${LDFLAGS} +${SCID_SQL}: bcc +${SCID_OBJS}: ${SCID_SQL} -${SCICTL_OBJS}: libsci.a - -scictl: ${SCICTL_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCICTL_OBJS} libsci.a ${LIBBSD_LIBS} \ - ${JANSSON_LIBS} ${LDFLAGS} +scid/scid: lib/libsci.a ${SCID_OBJS} + ${CC} ${CFLAGS} -o $@ ${SCID_OBJS} lib/libsci.a ${LIBBSD_LIBS} \ + ${JANSSON_LIBS} ${KCGI_LIBS} ${LDFLAGS} -${SCIWEBD_OBJS}: libsci.a - -sciwebd: ${SCIWEBD_OBJS} - ${CC} ${CFLAGS} -o $@ ${SCIWEBD_OBJS} libsci.a ${LIBBSD_LIBS} \ - ${KCGI_LIBS} ${JANSSON_LIBS} ${LDFLAGS} +sciworkerd/sciworkerd: lib/libsci.a ${SCIWORKERD_OBJS} + ${CC} ${CFLAGS} -o $@ ${SCIWORKERD_OBJS} lib/libsci.a ${LIBBSD_LIBS} \ + ${LIBCURL_LIBS} ${JANSSON_LIBS} ${LDFLAGS} install: mkdir -p ${DESTDIR}${BINDIR} - cp scid scictl sciwebd sciworkerd ${DESTDIR}${BINDIR} + cp scid/scid sciworkerd/sciworkerd ${DESTDIR}${BINDIR} mkdir -p ${DESTDIR}${MANDIR}/man7 cp ${MAN7} ${DESTDIR}${MANDIR}/man7 mkdir -p ${DESTDIR}${MANDIR}/man8 @@ -165,16 +142,12 @@ clean: rm -f bcc config.h tags cscope.out ${MAN7} ${MAN8} - rm -f libsci.a ${LIBSCI_OBJS} ${LIBSCI_DATA} ${LIBSCI_DEPS} - rm -f scid ${SCID_OBJS} ${SCID_DEPS} - rm -f scictl ${SCICTL_OBJS} ${SCICTL_DEPS} - rm -f sciworkerd ${SCIWORKERD_OBJS} ${SCIWORKERD_DEPS} - rm -f sciwebd ${SCIWEBD_OBJS} ${SCIWEBD_DEPS} + rm -f libsci.a ${LIBSCI_OBJS} ${LIBSCI_DEPS} + rm -f scid/scid ${SCID_OBJS} ${SCID_DEPS} + rm -f sciworkerd/sciworkerd ${SCIWORKERD_OBJS} ${SCIWORKERD_DEPS} rm -f ${TESTS_OBJS} ${TESTS_DEPS} -${TESTS_OBJS}: libsci.a - -tests: ${TESTS_OBJS} +tests: lib/libsci.a ${TESTS_OBJS} for t in ${TESTS_OBJS}; do $$t -v; done .PHONY: all clean install tests
--- a/config.mk Wed Oct 06 16:22:23 2021 +0200 +++ b/config.mk Tue Jul 12 20:20:51 2022 +0200 @@ -1,5 +1,5 @@ CC= cc -CFLAGS= -Wall -Wextra -g -O0 -Wno-cpp +CFLAGS= -g -O0 -Wno-cpp #CFLAGS= -Wall -Wextra -fsanitize=address,undefined -g -O0 #LDFLAGS= -fsanitize=address,undefined @@ -7,5 +7,4 @@ BINDIR= ${PREFIX}/bin MANDIR= ${PREFIX}/share/man SHAREDIR= ${PREFIX}/share -TMPDIR= ${PREFIX}/tmp VARDIR= ${PREFIX}/var
--- a/db.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,419 +0,0 @@ -/* - * db.c -- scid database access - * - * 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/queue.h> -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include <sqlite3.h> - -#include "db.h" -#include "log.h" -#include "types.h" -#include "util.h" - -#include "sql/init.h" -#include "sql/job-add.h" -#include "sql/job-todo.h" -#include "sql/jobresult-add.h" -#include "sql/project-add.h" -#include "sql/project-update.h" -#include "sql/project-find.h" -#include "sql/project-find-id.h" -#include "sql/project-list.h" -#include "sql/worker-add.h" -#include "sql/worker-find.h" -#include "sql/worker-find-id.h" -#include "sql/worker-list.h" - -#define CHAR(v) (const char *)(v) - -static sqlite3 *db; - -typedef void (*unpacker)(sqlite3_stmt *, struct db_ctx *, void *); - -struct str { - char *str; - SLIST_ENTRY(str) link; -}; - -struct list { - unpacker unpack; - void *data; - size_t datasz; - size_t elemwidth; - struct db_ctx *ctx; -}; - -SLIST_HEAD(strlist, str); - -static struct strlist * -strlist_new(void) -{ - struct strlist *l; - - l = util_calloc(1, sizeof (*l)); - SLIST_INIT(l); - - return l; -} - -static const char * -strlist_add(struct strlist *l, const char *text) -{ - struct str *s; - - s = util_calloc(1, sizeof (*s)); - s->str = util_strdup(text); - - SLIST_INSERT_HEAD(l, s, link); - - return s->str; -} - -static void -strlist_free(struct strlist *l) -{ - struct str *s, *tmp; - - SLIST_FOREACH_SAFE(s, l, link, tmp) { - free(s->str); - free(s); - } - - free(l); -} - -static void -project_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct project *project) -{ - project->id = sqlite3_column_int(stmt, 0); - project->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1))); - project->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2))); - project->url = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 3))); - project->script = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 4))); -} - -static void -worker_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct worker *w) -{ - w->id = sqlite3_column_int(stmt, 0); - w->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1))); - w->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2))); -} - -static void -job_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct job *job) -{ - job->id = sqlite3_column_int(stmt, 0); - job->tag = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1))); - job->project_id = sqlite3_column_int(stmt, 2); -} - -static void -vbind(sqlite3_stmt *stmt, const char *fmt, va_list ap) -{ - for (int index = 1; *fmt; ++fmt) { - switch (*fmt) { - case 'i': - sqlite3_bind_int(stmt, index++, va_arg(ap, int)); - break; - case 's': - sqlite3_bind_text(stmt, index++, va_arg(ap, const char *), -1, SQLITE_STATIC); - break; - case 'z': - sqlite3_bind_int64(stmt, index++, va_arg(ap, size_t)); - break; - default: - break; - } - } -} - -static int -insert(const char *sql, const char *fmt, ...) -{ - assert(sql); - assert(fmt); - - sqlite3_stmt *stmt = NULL; - va_list ap; - int ret = -1; - - if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) - return log_warn("db: %s", sqlite3_errmsg(db)), -1; - - va_start(ap, fmt); - vbind(stmt, fmt, ap); - va_end(ap); - - if (sqlite3_step(stmt) != SQLITE_DONE) - log_warn("db: %s", sqlite3_errmsg(db)); - else - ret = sqlite3_last_insert_rowid(db); - - sqlite3_finalize(stmt); - - return ret; -} - -static int -update(const char *sql, const char *fmt, ...) -{ - assert(sql); - assert(fmt); - - sqlite3_stmt *stmt = NULL; - va_list ap; - int ret = 1; - - if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) - return log_warn("db: %s", sqlite3_errmsg(db)), -1; - - va_start(ap, fmt); - vbind(stmt, fmt, ap); - va_end(ap); - - if (sqlite3_step(stmt) != SQLITE_DONE) - log_warn("db: %s", sqlite3_errmsg(db)); - else - ret = 0; - - sqlite3_finalize(stmt); - - return ret; -} - -static ssize_t -list(struct list *sel, const char *sql, const char *args, ...) -{ - sqlite3_stmt *stmt = NULL; - - va_list ap; - int step; - ssize_t ret = -1; - size_t tot = 0; - - sel->ctx->handle = NULL; - - if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) - return log_warn("db: %s", sqlite3_errmsg(db)), -1; - - va_start(ap, args); - vbind(stmt, args, ap); - va_end(ap); - - sel->ctx->handle = strlist_new(); - - while (tot < sel->datasz && (step = sqlite3_step(stmt)) == SQLITE_ROW) - sel->unpack(stmt, sel->ctx, (unsigned char *)sel->data + (tot++ * sel->elemwidth)); - - if (step == SQLITE_OK || step == SQLITE_DONE || step == SQLITE_ROW) - ret = tot; - else { - memset(sel->data, 0, sel->datasz * sel->elemwidth); - strlist_free(sel->ctx->handle); - sel->ctx->handle = NULL; - } - - sqlite3_finalize(stmt); - - return ret; -} - -int -db_open(const char *path) -{ - assert(path); - - if (sqlite3_open(path, &db) != SQLITE_OK) - return log_warn("db: open error: %s", sqlite3_errmsg(db)), -1; - - /* Wait for 30 seconds to lock the database. */ - sqlite3_busy_timeout(db, 30000); - - if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) - return log_warn("db: initialization error: %s", sqlite3_errmsg(db)), -1; - - return 0; -} - -int -db_project_add(struct project *p) -{ - return (p->id = insert(CHAR(sql_project_add), "ssss", p->name, p->desc, - p->url, p->script)) < 0 ? -1 : 0; -} - -int -db_project_update(const struct project *p) -{ - assert(p); - - return update(CHAR(sql_project_update), "ssssi", p->name, p->desc, - p->url, p->script, p->id); -} - -ssize_t -db_project_list(struct db_ctx *ctx, struct project *projects, size_t projectsz) -{ - struct list sel = { - .unpack = (unpacker)project_unpacker, - .data = projects, - .datasz = projectsz, - .elemwidth = sizeof (*projects), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_project_list), "z", projectsz); -} - -int -db_project_find(struct db_ctx *ctx, struct project *project) -{ - struct list sel = { - .unpack = (unpacker)project_unpacker, - .data = project, - .datasz = 1, - .elemwidth = sizeof (*project), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_project_find), "s", project->name) == 1 ? 0 : -1; -} - -int -db_project_find_id(struct db_ctx *ctx, struct project *project) -{ - struct list sel = { - .unpack = (unpacker)project_unpacker, - .data = project, - .datasz = 1, - .elemwidth = sizeof (*project), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_project_find_id), "i", project->id) == 1 ? 0 : -1; -} - -int -db_worker_add(struct worker *wk) -{ - assert(wk); - - return (wk->id = insert(CHAR(sql_worker_add), "ss", wk->name, wk->desc)) < 0 ? -1 : 0; -} - -ssize_t -db_worker_list(struct db_ctx *ctx, struct worker *wk, size_t wksz) -{ - assert(ctx); - assert(wk); - - struct list sel = { - .unpack = (unpacker)worker_unpacker, - .data = wk, - .datasz = wksz, - .elemwidth = sizeof (*wk), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_worker_list), "z", wksz); -} - -int -db_worker_find(struct db_ctx *ctx, struct worker *wk) -{ - struct list sel = { - .unpack = (unpacker)worker_unpacker, - .data = wk, - .datasz = 1, - .elemwidth = sizeof (*wk), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_worker_find), "s", wk->name) == 1 ? 0 : -1; -} - -int -db_worker_find_id(struct db_ctx *ctx, struct worker *wk) -{ - struct list sel = { - .unpack = (unpacker)worker_unpacker, - .data = wk, - .datasz = 1, - .elemwidth = sizeof (*wk), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_worker_find_id), "i", wk->id) == 1 ? 0 : -1; -} - -int -db_job_add(struct job *job) -{ - assert(job); - - return (job->id = insert(CHAR(sql_job_add), - "si", job->tag, job->project_id)) < 0 ? -1 : 0; -} - -ssize_t -db_job_todo(struct db_ctx *ctx, struct job *jobs, size_t jobsz, int worker_id) -{ - assert(ctx); - assert(jobs); - - struct list sel = { - .unpack = (unpacker)job_unpacker, - .data = jobs, - .datasz = jobsz, - .elemwidth = sizeof (*jobs), - .ctx = ctx - }; - - return list(&sel, CHAR(sql_job_todo), "iiz", worker_id, worker_id, jobsz); -} - -int -db_jobresult_add(struct jobresult *r) -{ - assert(r); - - return (r->id = insert(CHAR(sql_jobresult_add), "iiis", r->job_id, - r->worker_id, r->exitcode, r->log)) < 0 ? -1 : 0; -} - -void -db_finish(void) -{ - if (db) { - sqlite3_close(db); - db = NULL; - } -} - -void -db_ctx_finish(struct db_ctx *ctx) -{ - if (ctx->handle) { - strlist_free(ctx->handle); - ctx->handle = NULL; - } -}
--- a/db.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/* - * db.h -- scid database access - * - * 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. - */ - -#ifndef SCI_DB_H -#define SCI_DB_H - -#include <sys/types.h> -#include <stddef.h> - -struct project; -struct worker; -struct job; -struct jobresult; - -struct db_ctx { - void *handle; -}; - -int -db_open(const char *); - -int -db_job_add(struct job *); - -ssize_t -db_job_todo(struct db_ctx *, struct job *, size_t, int); - -int -db_jobresult_add(struct jobresult *); - -int -db_project_add(struct project *); - -int -db_project_update(const struct project *); - -ssize_t -db_project_list(struct db_ctx *, struct project *, size_t); - -int -db_project_find(struct db_ctx *, struct project *); - -int -db_project_find_id(struct db_ctx *, struct project *); - -int -db_worker_add(struct worker *); - -ssize_t -db_worker_list(struct db_ctx *, struct worker *, size_t); - -int -db_worker_find(struct db_ctx *, struct worker *); - -int -db_worker_find_id(struct db_ctx *, struct worker *); - -void -db_finish(void); - -void -db_ctx_finish(struct db_ctx *); - -#endif /* !SCI_DB_H */
--- a/doc/api.md Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# Network REST API - -This document describes the network API for access through the `scid` program -from `sciworker`. - -# Base definitions - -The API is versioned and starts with the `/api/v?` where the question mark is -replaced with the current version. - -# API v1 - -## (GET) /jobs/<w> - -Get the jobs pending that the worker `w` is supposed to run. - -Request: - -No data. - -Response: - -``` -[ - { - "project": "foobar", - "tag": "1234" - } -] -``` - -## (POST) /jobs/<w> - -Post the result of the a job from the worker `w`. - -Request: - -``` -{ - "project": "foobar", - "tag": 1234", - "output": "stdout/stderr combined" -} -``` - -### (GET) /script/<p> - -Get script code for project `p`. - -Request: - -No data. - -Reponse: - -``` -{ - "code": "#!/bin/sh exit 0" -} -```
--- a/http.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/* - * http.c -- HTTP parsing and rendering - * - * 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/types.h> -#include <assert.h> -#include <stdarg.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - -#include <kcgi.h> - -#include "http.h" -#include "log.h" -#include "page.h" -#include "page-api-jobs.h" -#include "page-api-projects.h" -#include "page-api-workers.h" -#include "req.h" - -enum page { - PAGE_API, - PAGE_LAST /* Not used. */ -}; - -static void -dispatch_api(struct kreq *req) -{ - static const struct { - const char *prefix; - void (*handler)(struct kreq *); - } apis[] = { - { "v1/jobs", page_api_v1_jobs }, - { "v1/projects", page_api_v1_projects }, - { "v1/workers", page_api_v1_workers }, - { NULL, NULL } - }; - - for (size_t i = 0; apis[i].prefix; ++i) - if (strncmp(req->path, apis[i].prefix, strlen(apis[i].prefix)) == 0) - return apis[i].handler(req); - - page(req, NULL, KHTTP_404, KMIME_TEXT_HTML, "pages/404.html"); -} - -static const char *pages[] = { - [PAGE_API] = "api" -}; - -static void (*handlers[])(struct kreq *req) = { - [PAGE_API] = dispatch_api -}; - -static void -process(struct kreq *req) -{ - assert(req); - - log_debug("http: accessing page '%s'", req->path); - - if (req->page == PAGE_LAST) - page(req, NULL, KHTTP_404, KMIME_TEXT_HTML, "pages/404.html"); - else - handlers[req->page](req); -} - -void -http_fcgi_run(void) -{ - struct kreq req; - struct kfcgi *fcgi; - - if (khttp_fcgi_init(&fcgi, NULL, 0, pages, PAGE_LAST, 0) != KCGI_OK) - return; - - while (khttp_fcgi_parse(fcgi, &req) == KCGI_OK) - process(&req); - - khttp_fcgi_free(fcgi); -} - -void -http_cgi_run(void) -{ - struct kreq req; - - if (khttp_parse(&req, NULL, 0, pages, PAGE_LAST, 0) == KCGI_OK) - process(&req); -}
--- a/http.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -/* - * http.h -- HTTP parsing and rendering - * - * 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. - */ - -#ifndef SCI_HTTP_H -#define SCI_HTTP_H - -void -http_fcgi_run(void); - -void -http_cgi_run(void); - -#endif /* !SCI_HTTP_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/log.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,76 @@ +/* + * log.c -- logging routines + * + * Copyright (c) 2020-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 <assert.h> +#include <stdio.h> +#include <syslog.h> + +#include "log.h" + +/* TODO: replace. */ +static struct { + enum log_level verbosity; +} config = { + .verbosity = LOG_LEVEL_DEBUG +}; + +static int syslog_levels[] = { + [LOG_LEVEL_DEBUG] = LOG_DEBUG, + [LOG_LEVEL_INFO] = LOG_INFO, + [LOG_LEVEL_WARNING] = LOG_WARNING +}; + +void +log_open(const char *name) +{ + openlog(name, 0, LOG_USER); +} + +void +log_write(enum log_level level, const char *fmt, ...) +{ + assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); + assert(fmt); + + if (config.verbosity >= level) { + va_list ap; + + va_start(ap, fmt); + log_vwrite(level, fmt, ap); + va_end(ap); + } +} + +void +log_vwrite(enum log_level level, const char *fmt, va_list ap) +{ + assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); + assert(fmt); + + char line[BUFSIZ]; + + vsnprintf(line, sizeof (line), fmt, ap); + syslog(syslog_levels[level], "%s", line); +} + +void +log_finish(void) +{ + if (config.verbosity > 0) + closelog(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/log.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,51 @@ +/* + * log.h -- logging routines + * + * Copyright (c) 2020-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. + */ + +#ifndef SCI_LOG_H +#define SCI_LOG_H + +#include <stdarg.h> +#include <stdlib.h> + +enum log_level { + LOG_LEVEL_WARNING = 1, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG +}; + +#define log_debug(...) log_write(LOG_LEVEL_DEBUG, __VA_ARGS__) +#define log_warn(...) log_write(LOG_LEVEL_WARNING, __VA_ARGS__) +#define log_info(...) log_write(LOG_LEVEL_INFO, __VA_ARGS__) +#define log_die(...) do { \ + log_write(LOG_LEVEL_WARNING, __VA_ARGS__); \ + exit(1); \ +} while (0) + +void +log_open(const char *); + +void +log_write(enum log_level, const char *, ...); + +void +log_vwrite(enum log_level, const char *, va_list); + +void +log_finish(void); + +#endif /* !SCI_LOG_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/types.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,222 @@ +/* + * types.c -- type definitions and conversions + * + * 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 <assert.h> +#include <errno.h> + +#include "types.h" + +typedef json_t * (*packer)(const void *); +typedef int (*unpacker)(void *, json_t *); + +static inline json_t * +job_packer(const struct job *job) +{ + return json_pack("{si si ss}", + "id", job->id, + "project_id", job->project_id, + "tag", job->tag + ); +} + +static inline int +job_unpacker(struct job *job, json_t *doc) +{ + return json_unpack(doc, "{si si ss}", + "id", &job->id, + "project_id", &job->project_id, + "tag", &job->tag + ); +} + +static inline json_t * +jobresult_packer(const struct jobresult *res) +{ + return json_pack("{si si si si ss}", + "id", res->id, + "job_id", res->job_id, + "worker_id", res->worker_id, + "exitcode", res->exitcode, + "log", res->log + ); +} + +static inline int +jobresult_unpacker(struct jobresult *res, json_t *doc) +{ + return json_unpack(doc, "{si si si si ss}", + "id", &res->id, + "job_id", &res->job_id, + "worker_id", &res->worker_id, + "exitcode", &res->exitcode, + "log", &res->log + ); +} + +static inline json_t * +worker_packer(const struct worker *w) +{ + return json_pack("{si ss ss}", + "id", w->id, + "name", w->name, + "desc", w->desc + ); +} + +static inline int +worker_unpacker(struct worker *w, json_t *doc) +{ + return json_unpack(doc, "{si ss ss}", + "id", &w->id, + "name", &w->name, + "desc", &w->desc + ); +} + +static inline json_t * +project_packer(struct project *p) +{ + return json_pack("{si ss ss ss ss}", + "id", p->id, + "name", p->name, + "desc", p->desc, + "url", p->url, + "script", p->script + ); +} + +static inline int +project_unpacker(struct project *p, json_t *doc) +{ + return json_unpack(doc, "{si ss ss ss ss}", + "id", &p->id, + "name", &p->name, + "desc", &p->desc, + "url", &p->url, + "script", &p->script + ); +} + +static json_t * +to(const void *array, size_t arraysz, size_t width, packer fn) +{ + json_t *doc; + + if (arraysz == 1) + doc = fn(array); + else { + doc = json_array(); + + for (size_t i = 0; i < arraysz; ++i) + json_array_append(doc, fn((char *)array + (i * width))); + } + + return doc; +} + +static ssize_t +from(void *array, size_t arraysz, size_t width, json_t *doc, unpacker fn) +{ + json_t *val; + size_t i, tot = 0; + + if (json_is_array(doc)) { + json_array_foreach(doc, i, val) { + if (tot >= arraysz) + return errno = ERANGE, -1; + if (fn((char *)array + (tot++ * width), val) < 0) + return errno = EILSEQ, -1; + } + } else if (json_is_object(doc)) { + tot = 1; + + if (fn(array, doc) < 0) + return errno = EILSEQ, -1; + } else + return errno = EINVAL, -1; + + return tot; +} + +json_t * +job_to(const struct job *jobs, size_t jobsz) +{ + assert(jobs); + + return to(jobs, jobsz, sizeof (*jobs), (packer)job_packer); +} + +ssize_t +job_from(struct job *jobs, size_t jobsz, json_t *doc) +{ + assert(jobs); + assert(doc); + + return from(jobs, jobsz, sizeof (*jobs), doc, (unpacker)job_unpacker); +} + +json_t * +jobresult_to(const struct jobresult *res, size_t resz) +{ + assert(res); + + return to(res, resz, sizeof (*res), (packer)jobresult_packer); +} + +ssize_t +jobresult_from(struct jobresult *res, size_t resz, json_t *doc) +{ + assert(res); + assert(doc); + + return from(res, resz, sizeof (*res), doc, (unpacker)jobresult_unpacker); +} + +json_t * +worker_to(const struct worker *w, size_t wsz) +{ + assert(w); + + return to(w, wsz, sizeof (*w), (packer)worker_packer); +} + +ssize_t +worker_from(struct worker *w, size_t wsz, json_t *doc) +{ + assert(w); + assert(doc); + + return from(w, wsz, sizeof (*w), doc, (unpacker)worker_unpacker); +} + +json_t * +project_to(const struct project *proj, size_t projsz) +{ + assert(proj); + + return to(proj, projsz, sizeof (*proj), (packer)project_packer); +} + +ssize_t +project_from(struct project *proj, size_t projsz, json_t *doc) +{ + assert(proj); + assert(doc); + + return from(proj, projsz, sizeof (*proj), doc, (unpacker)project_unpacker); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/types.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,79 @@ +/* + * types.h -- type definitions and conversions + * + * 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. + */ + +#ifndef SCI_TYPES_H +#define SCI_TYPES_H + +#include <sys/types.h> +#include <stddef.h> + +#include <jansson.h> + +struct job { + int id; + int project_id; + const char *tag; +}; + +struct jobresult { + int id; + int job_id; + int worker_id; + int exitcode; + const char *log; +}; + +struct worker { + int id; + const char *name; + const char *desc; +}; + +struct project { + int id; + const char *name; + const char *desc; + const char *url; + const char *script; +}; + +json_t * +job_to(const struct job *, size_t); + +ssize_t +job_from(struct job *, size_t, json_t *); + +json_t * +jobresult_to(const struct jobresult *, size_t); + +ssize_t +jobresult_from(struct jobresult *, size_t, json_t *); + +json_t * +worker_to(const struct worker *, size_t); + +ssize_t +worker_from(struct worker *, size_t, json_t *); + +json_t * +project_to(const struct project *, size_t); + +ssize_t +project_from(struct project *, size_t, json_t *); + +#endif /* !SCI_TYPES_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/util.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,187 @@ +/* + * util.c -- miscellaneous utilities + * + * 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/stat.h> +#include <assert.h> +#include <err.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "util.h" + +void * +util_malloc(size_t size) +{ + void *ret; + + if (!(ret = malloc(size))) + err(1, "malloc"); + + return ret; +} + +void * +util_calloc(size_t n, size_t size) +{ + void *ret; + + if (!(ret = calloc(n, size))) + err(1, "calloc"); + + return ret; +} + +void * +util_realloc(void *ptr, size_t size) +{ + void *ret; + + if (!(ret = realloc(ptr, size)) && size) + err(1, "realloc"); + + return ret; +} + +void * +util_memdup(const void *ptr, size_t size) +{ + void *ret; + + if (!(ret = malloc(size))) + err(1, "malloc"); + + return memcpy(ret, ptr, size); +} + +char * +util_strdup(const char *src) +{ + char *ret; + + if (!(ret = strdup(src))) + err(1, "strdup"); + + return ret; +} + +char * +util_strndup(const char *src, size_t n) +{ + assert(src); + + char *ret; + + if (!(ret = strndup(src, n))) + err(1, "strndup"); + + return ret; +} + +char * +util_basename(const char *str) +{ + static char ret[PATH_MAX]; + char tmp[PATH_MAX]; + + strlcpy(tmp, str, sizeof (tmp)); + strlcpy(ret, basename(tmp), sizeof (ret)); + + return ret; +} + +char * +util_dirname(const char *str) +{ + static char ret[PATH_MAX]; + char tmp[PATH_MAX]; + + strlcpy(tmp, str, sizeof (tmp)); + strlcpy(ret, dirname(tmp), sizeof (ret)); + + return ret; +} + +FILE * +util_fmemopen(void *buf, size_t size, const char *mode) +{ + FILE *fp; + + if (!(fp = fmemopen(buf, size, mode))) + err(1, "fmemopen"); + + return fp; +} + +char * +util_printf(char *buf, size_t bufsz, const char *fmt, ...) +{ + assert(buf); + assert(bufsz); + assert(fmt); + + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, bufsz, fmt, ap); + va_end(ap); + + return buf; +} + +char * +util_read(const char *path) +{ + int fd; + struct stat st; + char *ret; + + if ((fd = open(path, O_RDONLY)) < 0) + return NULL; + if (fstat(fd, &st) < 0) + return close(fd), NULL; + + ret = util_calloc(1, st.st_size + 1); + + if (read(fd, ret, st.st_size) != st.st_size) { + free(ret); + ret = NULL; + } + + close(fd); + + return ret; +} + +const char * +util_path(const char *filename) +{ + assert(filename); + + /* Build path to the template file. */ + static char path[PATH_MAX]; + + //snprintf(path, sizeof (path), "%s/%s", config.themedir, filename); + + return path; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/util.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,66 @@ +/* + * util.h -- miscellaneous utilities + * + * 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. + */ + +#ifndef SCI_UTIL_H +#define SCI_UTIL_H + +#include <stddef.h> +#include <stdio.h> + +#define UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) + +void * +util_malloc(size_t); + +void * +util_calloc(size_t, size_t); + +void * +util_realloc(void *, size_t); + +void * +util_reallocarray(void *, size_t, size_t); + +void * +util_memdup(const void *, size_t); + +char * +util_strdup(const char *); + +char * +util_strndup(const char *, size_t); + +char * +util_basename(const char *); + +char * +util_dirname(const char *); + +FILE * +util_fmemopen(void *, size_t, const char *); + +char * +util_printf(char *, size_t, const char *, ...); + +char * +util_read(const char *); + +const char * +util_path(const char *); + +#endif /* !SCI_UTIL_H */
--- a/log.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* - * log.c -- logging routines - * - * Copyright (c) 2020-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 <assert.h> -#include <stdio.h> -#include <syslog.h> - -#include "log.h" - -/* TODO: replace. */ -static struct { - enum log_level verbosity; -} config = { - .verbosity = LOG_LEVEL_DEBUG -}; - -static int syslog_levels[] = { - [LOG_LEVEL_DEBUG] = LOG_DEBUG, - [LOG_LEVEL_INFO] = LOG_INFO, - [LOG_LEVEL_WARNING] = LOG_WARNING -}; - -void -log_open(const char *name) -{ - openlog(name, 0, LOG_USER); -} - -void -log_write(enum log_level level, const char *fmt, ...) -{ - assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); - assert(fmt); - - if (config.verbosity >= level) { - va_list ap; - - va_start(ap, fmt); - log_vwrite(level, fmt, ap); - va_end(ap); - } -} - -void -log_vwrite(enum log_level level, const char *fmt, va_list ap) -{ - assert(level >= LOG_LEVEL_WARNING && level <= LOG_LEVEL_DEBUG); - assert(fmt); - - char line[BUFSIZ]; - - vsnprintf(line, sizeof (line), fmt, ap); - syslog(syslog_levels[level], "%s", line); -} - -void -log_finish(void) -{ - if (config.verbosity > 0) - closelog(); -}
--- a/log.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * log.h -- logging routines - * - * Copyright (c) 2020-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. - */ - -#ifndef SCI_LOG_H -#define SCI_LOG_H - -#include <stdarg.h> - -enum log_level { - LOG_LEVEL_WARNING = 1, - LOG_LEVEL_INFO, - LOG_LEVEL_DEBUG -}; - -#define log_debug(...) log_write(LOG_LEVEL_DEBUG, __VA_ARGS__) -#define log_warn(...) log_write(LOG_LEVEL_WARNING, __VA_ARGS__) -#define log_info(...) log_write(LOG_LEVEL_INFO, __VA_ARGS__) - -void -log_open(const char *); - -void -log_write(enum log_level, const char *, ...); - -void -log_vwrite(enum log_level, const char *, va_list); - -void -log_finish(void); - -#endif /* !SCI_LOG_H */
--- a/page-api-jobs.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -/* - * page-api-jobs.c -- /api/v?/jobs route - * - * 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/types.h> -#include <assert.h> -#include <stdarg.h> -#include <stdint.h> -#include <string.h> - -#include <kcgi.h> -#include <jansson.h> - -#include "config.h" -#include "log.h" -#include "page-api-jobs.h" -#include "page.h" -#include "req.h" -#include "types.h" -#include "util.h" - -static void -list(struct kreq *r, const struct job *jobs, size_t jobsz) -{ - json_t *doc; - char *dump; - - doc = job_to(jobs, jobsz); - dump = json_dumps(doc, JSON_COMPACT); - - khttp_puts(r, dump); - free(dump); - json_decref(doc); -} - -static int -save(const char *json) -{ - struct req req = {0}; - struct jobresult res = {0}; - int ret = -1; - - json_t *doc; - json_error_t err; - - if (!(doc = json_loads(json, 0, &err))) - log_warn("api/post: invalid JSON input: %s", err.text); - else if (jobresult_from(&res, 1, doc) < 0) - log_warn("api/post: failed to decode parameters"); - else if ((req = req_jobresult_add(&res)).status) - log_warn("api/post: save error: %s", strerror(req.status)); - else - ret = 0; - - json_decref(doc); - req_finish(&req); - - return ret; -} - -static void -get(struct kreq *r) -{ - struct req req; - struct job jobs[SCI_JOB_LIST_MAX]; - size_t jobsz = UTIL_SIZE(jobs); - const char *worker = util_basename(r->path); - - if ((req = req_job_todo(jobs, &jobsz, worker)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - list(r, jobs, jobsz); - req_finish(&req); - khttp_free(r); - } -} - -static void -post(struct kreq *r) -{ - if (r->fieldsz < 1) - page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); - else if (save(r->fields[0].val) < 0) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_free(r); - } -} - -void -page_api_v1_jobs(struct kreq *r) -{ - assert(r); - - switch (r->method) { - case KMETHOD_GET: - get(r); - break; - case KMETHOD_POST: - post(r); - break; - default: - page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); - break; - } -}
--- a/page-api-jobs.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -/* - * page-api-jobs.h -- /api/v?/jobs route - * - * 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. - */ - -#ifndef SCI_PAGE_API_JOBS_H -#define SCI_PAGE_API_JOBS_H - -struct kreq; - -void -page_api_v1_jobs(struct kreq *); - -#endif /* !SCI_PAGE_API_JOBS_H */
--- a/page-api-projects.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/* - * page-api-projects.c -- /api/v?/projects route - * - * 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 <assert.h> - -#include "config.h" -#include "page-api-projects.h" -#include "page.h" -#include "req.h" -#include "types.h" -#include "util.h" - -static void -list(struct kreq *r, const struct project *projects, size_t projectsz) -{ - struct json_t *doc; - char *dump; - - doc = project_to(projects, projectsz); - dump = json_dumps(doc, JSON_COMPACT); - - khttp_puts(r, dump); - free(dump); - json_decref(doc); -} - -static void -push(struct kreq *r, const struct project *p) -{ - struct json_t *json; - char *dump; - - json = project_to(p, 1); - dump = json_dumps(json, JSON_COMPACT); - - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_puts(r, dump); - khttp_free(r); - - free(dump); - json_decref(json); -} - -static void -get_one(struct kreq *r, const char *name) -{ - struct project project; - struct req req; - - if ((req = req_project_find(&project, name)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else - push(r, &project); -} - -static void -get_one_id(struct kreq *r, int id) -{ - struct project project; - struct req req; - - if ((req = req_project_find_id(&project, id)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else - push(r, &project); -} - -static void -get_all(struct kreq *r) -{ - struct project projects[SCI_PROJECT_MAX]; - struct req req; - size_t projectsz = UTIL_SIZE(projects); - - if ((req = req_project_list(projects, &projectsz)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - list(r, projects, projectsz); - req_finish(&req); - khttp_free(r); - } -} - -static void -get(struct kreq *r) -{ - char name[128]; - int id; - - if (sscanf(r->path, "v1/projects/%d", &id) == 1) - get_one_id(r, id); - else if (sscanf(r->path, "v1/projects/%127s", name) == 1) - get_one(r, name); - else - get_all(r); -} - -void -page_api_v1_projects(struct kreq *r) -{ - assert(r); - - switch (r->method) { - case KMETHOD_GET: - get(r); - break; -#if 0 - case KMETHOD_POST: - post(r); - break; -#endif - default: - page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); - break; - } -}
--- a/page-api-projects.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -/* - * page-api-projects.h -- /api/v?/projects route - * - * 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. - */ - -#ifndef SCI_PAGE_API_PROJECTS_H -#define SCI_PAGE_API_PROJECTS_H - -struct kreq; - -void -page_api_v1_projects(struct kreq *); - -#endif /* !SCI_PAGE_API_PROJECTS_H */
--- a/page-api-workers.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -#include <assert.h> - -#include "config.h" -#include "page-api-workers.h" -#include "page.h" -#include "req.h" -#include "types.h" -#include "util.h" - -static void -list(struct kreq *r, const struct worker *workers, size_t workersz) -{ - struct json_t *doc; - char *dump; - - doc = worker_to(workers, workersz); - dump = json_dumps(doc, JSON_COMPACT); - - khttp_puts(r, dump); - free(dump); - json_decref(doc); -} - -static void -push(struct kreq *r, const struct worker *p) -{ - struct json_t *json; - char *dump; - - json = worker_to(p, 1); - dump = json_dumps(json, JSON_COMPACT); - - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_puts(r, dump); - khttp_free(r); - - free(dump); - json_decref(json); -} - -static void -get_one(struct kreq *r, const char *name) -{ - struct worker worker; - struct req req; - - if ((req = req_worker_find(&worker, name)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else - push(r, &worker); -} - -static void -get_one_id(struct kreq *r, int id) -{ - struct worker worker; - struct req req; - - if ((req = req_worker_find_id(&worker, id)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else - push(r, &worker); -} - -static void -get_all(struct kreq *r) -{ - struct worker workers[SCI_PROJECT_MAX]; - struct req req; - size_t workersz = UTIL_SIZE(workers); - - if ((req = req_worker_list(workers, &workersz)).status) - page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - list(r, workers, workersz); - req_finish(&req); - khttp_free(r); - } -} - -static void -get(struct kreq *r) -{ - char name[128]; - int id; - - if (sscanf(r->path, "v1/workers/%d", &id) == 1) - get_one_id(r, id); - else if (sscanf(r->path, "v1/workers/%127s", name) == 1) - get_one(r, name); - else - get_all(r); -} - -void -page_api_v1_workers(struct kreq *r) -{ - assert(r); - - switch (r->method) { - case KMETHOD_GET: - get(r); - break; - default: - page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); - break; - } -}
--- a/page-api-workers.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -#ifndef SCI_PAGE_API_WORKERS_H -#define SCI_PAGE_API_WORKERS_H - -struct kreq; - -void -page_api_v1_workers(struct kreq *); - -#endif /* !SCI_PAGE_API_WORKERS_H */
--- a/page-index.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/* - * page-index.c -- page / - * - * Copyright (c) 2020-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/types.h> -#include <assert.h> -#include <stdarg.h> -#include <stdint.h> - -#include <kcgi.h> - -#include "database.h" -#include "fragment-paste.h" -#include "page-index.h" -#include "page.h" -#include "paste.h" -#include "util.h" - -struct template { - struct kreq *req; - const struct paste *pastes; - size_t pastesz; -}; - -static const char *keywords[] = { - "pastes" -}; - -static int -template(size_t keyword, void *arg) -{ - struct template *tp = arg; - - switch (keyword) { - case 0: - for (size_t i = 0; i < tp->pastesz; ++i) - fragment_paste(tp->req, &tp->pastes[i]); - break; - default: - break; - } - - return 1; -} - -static void -get(struct kreq *r) -{ - struct paste pastes[10] = {0}; - size_t pastesz = NELEM(pastes); - - if (!database_recents(pastes, &pastesz)) - page(r, NULL, KHTTP_500, "pages/500.html"); - else - page_index_render(r, pastes, pastesz); - - for (size_t i = 0; i < pastesz; ++i) - paste_finish(&pastes[i]); -} - -void -page_index_render(struct kreq *r, const struct paste *pastes, size_t pastesz) -{ - struct template data = { - .req = r, - .pastes = pastes, - .pastesz = pastesz - }; - - struct ktemplate kt = { - .key = keywords, - .keysz = NELEM(keywords), - .arg = &data, - .cb = template - }; - - page(r, &kt, KHTTP_200, "pages/index.html"); -} - -void -page_index(struct kreq *r) -{ - switch (r->method) { - case KMETHOD_GET: - get(r); - break; - default: - page(r, NULL, KHTTP_400, "400.html"); - break; - } -}
--- a/page.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -/* - * page.c -- page renderer - * - * 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 "page.h" -#include "util.h" - -void -page(struct kreq *req, - const struct ktemplate *tmpl, - enum khttp status, - enum kmime mime, - const char *file) -{ - khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]); - khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]); - khttp_body(req); - - if (file) { - khttp_template(req, NULL, util_path("fragments/header.html")); - khttp_template(req, tmpl, util_path(file)); - khttp_template(req, NULL, util_path("fragments/footer.html")); - } - - khttp_free(req); -}
--- a/page.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -/* - * page.h -- page renderer - * - * 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. - */ - -#ifndef SCI_PAGE_H -#define SCI_PAGE_H - -#include <sys/types.h> -#include <stdarg.h> -#include <stdint.h> -#include <kcgi.h> - -void -page(struct kreq *, const struct ktemplate *, enum khttp, enum kmime, const char *); - -#endif /* !SCI_PAGE_H */
--- a/req.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,313 +0,0 @@ -/* - * req.c -- client requests to scid - * - * 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/socket.h> -#include <sys/time.h> -#include <sys/un.h> -#include <assert.h> -#include <err.h> -#include <errno.h> -#include <limits.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "config.h" -#include "req.h" -#include "types.h" -#include "util.h" - -static char path[PATH_MAX] = VARDIR "/run/sci.sock"; - -static int -attach(const char *path) -{ - struct sockaddr_un sun; - struct timeval tv = { .tv_sec = 3 }; - int sock; - - if ((sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) - return -1; - - sun.sun_family = PF_LOCAL; - strlcpy(sun.sun_path, path, sizeof (sun.sun_path)); - - if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)) < 0 || - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (tv)) < 0 || - connect(sock, (const struct sockaddr *)&sun, sizeof (sun)) < 0) { - close(sock); - return -1; - } - - return sock; -} - -static struct req -exchange(const char *cmd, json_t *doc) -{ - char *json, *response, buf[BUFSIZ]; - struct req res = {0}; - int sock; - ssize_t nr; - FILE *fp; - - if ((sock = attach(path)) < 0) - goto connect_err; - - json_object_set(doc, "cmd", json_string(cmd)); - json = json_dumps(doc, JSON_COMPACT); - - if (send(sock, json, strlen(json), MSG_NOSIGNAL) <= 0 || - send(sock, "\r\n", 2, MSG_NOSIGNAL) <= 0) - goto send_err; - - response = util_malloc(SCI_MSG_MAX); - fp = util_fmemopen(response, SCI_MSG_MAX, "w"); - setbuf(fp, NULL); - - while ((nr = recv(sock, buf, sizeof (buf), MSG_NOSIGNAL)) > 0) - if (fwrite(buf, 1, nr, fp) != (size_t)nr) - goto io_err; - - if (!(res.msg = json_loads(response, 0, NULL))) - errno = EILSEQ; - else - errno = 0; - - /* TODO: generic error handling */ - -io_err: - fclose(fp); - free(response); - -send_err: - close(sock); - free(json); - -connect_err: - res.status = errno; - json_decref(doc); - - return res; -} - -void -req_set_path(const char *newpath) -{ - assert(newpath); - - strlcpy(path, newpath, sizeof (path)); -} - -struct req -req_job_add(const struct job *job) -{ - assert(job); - - return exchange("job-add", job_to(job, 1)); -} - -struct req -req_job_todo(struct job *jobs, size_t *jobsz, const char *worker) -{ - assert(jobs); - assert(jobsz); - assert(worker); - - struct req st; - struct worker wk; - - /* - * Retrieve worker id first, we don't need JSON document to stay - * because we only refer to the id field. - */ - if ((st = req_worker_find(&wk, worker)).status) - return st; - - req_finish(&st); - - if ((st = exchange("job-todo", json_pack("{si}", "worker_id", wk.id))).status) - return st; - - if ((*jobsz = job_from(jobs, *jobsz, st.msg)) == (size_t)-1) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -struct req -req_jobresult_add(const struct jobresult *res) -{ - assert(res); - - return exchange("jobresult-add", jobresult_to(res, 1)); -} - -struct req -req_project_add(const struct project *p) -{ - assert(p); - - return exchange("project-add", project_to(p, 1)); -} - -struct req -req_project_update(const struct project *p) -{ - assert(p); - - return exchange("project-update", project_to(p, 1)); -} - -struct req -req_project_find(struct project *p, const char *name) -{ - assert(p); - assert(name); - - struct req st; - - if ((st = exchange("project-find", json_pack("{ss}", "name", name))).status) - return st; - - if (project_from(p, 1, st.msg) < 0) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -struct req -req_project_find_id(struct project *p, int id) -{ - assert(p); - - struct req st; - - if ((st = exchange("project-find-id", json_pack("{si}", "id", id))).status) - return st; - - if (project_from(p, 1, st.msg) < 0) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -struct req -req_project_list(struct project *projects, size_t *projectsz) -{ - assert(projects); - assert(projectsz); - - struct req st; - - if ((st = exchange("project-list", json_object())).status) - return st; - - if ((*projectsz = project_from(projects, *projectsz, st.msg)) == (size_t)-1) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -struct req -req_worker_add(const struct worker *w) -{ - assert(w); - - return exchange("worker-add", worker_to(w, 1)); -} - -struct req -req_worker_find(struct worker *wk, const char *name) -{ - assert(wk); - assert(name); - - struct req st; - - if ((st = exchange("worker-find", json_pack("{ss}", "name", name))).status) - return st; - if (worker_from(wk, 1, st.msg) < 0) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -struct req -req_worker_find_id(struct worker *wk, int id) -{ - assert(wk); - - struct req st; - - if ((st = exchange("worker-find-id", json_pack("{si}", "id", id))).status) - return st; - if (worker_from(wk, 1, st.msg) < 0) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -struct req -req_worker_list(struct worker *wk, size_t *wksz) -{ - assert(wk); - assert(wksz); - - struct req st; - - if ((st = exchange("worker-list", json_object())).status) - return st; - - if ((*wksz = worker_from(wk, *wksz, st.msg)) == (size_t)-1) { - json_decref(st.msg); - st.msg = NULL; - st.status = errno; - } - - return st; -} - -void -req_finish(struct req *st) -{ - if (st->msg) - json_decref(st->msg); - - memset(st, 0, sizeof (*st)); -}
--- a/req.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/* - * req.h -- client requests to scid - * - * 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. - */ - -#ifndef SCI_REQ_H -#define SCI_REQ_H - -#include <stddef.h> - -#include <jansson.h> - -struct job; -struct jobresult; -struct project; -struct worker; - -struct req { - int status; - json_t *msg; -}; - -void -req_set_path(const char *path); - -struct req -req_job_add(const struct job *); - -struct req -req_job_todo(struct job *, size_t *, const char *); - -struct req -req_jobresult_add(const struct jobresult *); - -struct req -req_project_add(const struct project *); - -struct req -req_project_update(const struct project *); - -struct req -req_project_find(struct project *, const char *); - -struct req -req_project_find_id(struct project *, int); - -struct req -req_project_list(struct project *, size_t *); - -struct req -req_worker_add(const struct worker *); - -struct req -req_worker_find(struct worker *, const char *); - -struct req -req_worker_find_id(struct worker *, int); - -struct req -req_worker_list(struct worker *, size_t *); - -void -req_finish(struct req *); - -#endif /* !SCI_REQ_H */
--- a/scictl.c Wed Oct 06 16:22:23 2021 +0200 +++ b/scictl.c Tue Jul 12 20:20:51 2022 +0200 @@ -81,17 +81,69 @@ return console; } +static size_t +extract(char *s, size_t w, size_t n, void *data) +{ + return fwrite(s, w, n, data); +} + +static json_t * +parse(const char *data) +{ + json_t *doc; + json_error_t err; + + if (!(json_loads(doc, 0, &err))) + die("abort: unable to parse JSON: %s\n", err.text); + + return doc; +} + +static json_t * +get(const char *url) +{ + CURL *curl; + CURLcode code; + FILE *fp; + char buf[HTTP_BUF_MAX]; + long ret; + + if (!(fp = fmemopen(buf, sizeof (buf), "w"))) + die("abort: %s", strerror(errno)); + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, extract); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); + code = curl_easy_perform(curl); + + if (code != CURLE_OK) + die("abort: %s", curl_easy_strerror(code)); + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &ret); + + if (ret != 200) + die("abort: HTTP %ld\n", ret); + + curl_easy_cleanup(curl); + fclose(fp); + + return parse(buf); +} + static struct req cmd_job_add(int argc, char **argv) { struct job job = {0}; struct project project = {0}; - struct req rp, rj; if (argc < 2) usage(); - if ((rp = req_project_find(&project, argv[0])).status) + project.name = argv[0]; + + if (_project_find(&project, argv[0])).status) return rp; job.project_id = project.id;
--- a/scid.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,427 +0,0 @@ -/* - * 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] [-s sock]\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), MSG_NOSIGNAL) < 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_project_update(int fd, json_t *doc) -{ - struct project pc; - - if (project_from(&pc, 1, doc) < 0) - return EINVAL; - if (db_project_update(&pc) < 0) - return error(fd, "unable to update project"); - - return ok(fd); -} - -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 }, - { "project-update", cmd_project_update }, - { "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), MSG_NOSIGNAL)) > 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(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/db.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,419 @@ +/* + * db.c -- scid database access + * + * 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/queue.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <sqlite3.h> + +#include "db.h" +#include "log.h" +#include "types.h" +#include "util.h" + +#include "sql/init.h" +#include "sql/job-add.h" +#include "sql/job-todo.h" +#include "sql/jobresult-add.h" +#include "sql/project-add.h" +#include "sql/project-update.h" +#include "sql/project-find.h" +#include "sql/project-find-id.h" +#include "sql/project-list.h" +#include "sql/worker-add.h" +#include "sql/worker-find.h" +#include "sql/worker-find-id.h" +#include "sql/worker-list.h" + +#define CHAR(v) (const char *)(v) + +static sqlite3 *db; + +typedef void (*unpacker)(sqlite3_stmt *, struct db_ctx *, void *); + +struct str { + char *str; + SLIST_ENTRY(str) link; +}; + +struct list { + unpacker unpack; + void *data; + size_t datasz; + size_t elemwidth; + struct db_ctx *ctx; +}; + +SLIST_HEAD(strlist, str); + +static struct strlist * +strlist_new(void) +{ + struct strlist *l; + + l = util_calloc(1, sizeof (*l)); + SLIST_INIT(l); + + return l; +} + +static const char * +strlist_add(struct strlist *l, const char *text) +{ + struct str *s; + + s = util_calloc(1, sizeof (*s)); + s->str = util_strdup(text); + + SLIST_INSERT_HEAD(l, s, link); + + return s->str; +} + +static void +strlist_free(struct strlist *l) +{ + struct str *s, *tmp; + + SLIST_FOREACH_SAFE(s, l, link, tmp) { + free(s->str); + free(s); + } + + free(l); +} + +static void +project_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct project *project) +{ + project->id = sqlite3_column_int(stmt, 0); + project->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1))); + project->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2))); + project->url = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 3))); + project->script = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 4))); +} + +static void +worker_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct worker *w) +{ + w->id = sqlite3_column_int(stmt, 0); + w->name = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1))); + w->desc = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 2))); +} + +static void +job_unpacker(sqlite3_stmt *stmt, struct db_ctx *ctx, struct job *job) +{ + job->id = sqlite3_column_int(stmt, 0); + job->tag = strlist_add(ctx->handle, CHAR(sqlite3_column_text(stmt, 1))); + job->project_id = sqlite3_column_int(stmt, 2); +} + +static void +vbind(sqlite3_stmt *stmt, const char *fmt, va_list ap) +{ + for (int index = 1; *fmt; ++fmt) { + switch (*fmt) { + case 'i': + sqlite3_bind_int(stmt, index++, va_arg(ap, int)); + break; + case 's': + sqlite3_bind_text(stmt, index++, va_arg(ap, const char *), -1, SQLITE_STATIC); + break; + case 'z': + sqlite3_bind_int64(stmt, index++, va_arg(ap, size_t)); + break; + default: + break; + } + } +} + +static int +insert(const char *sql, const char *fmt, ...) +{ + assert(sql); + assert(fmt); + + sqlite3_stmt *stmt = NULL; + va_list ap; + int ret = -1; + + if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) + return log_warn("db: %s", sqlite3_errmsg(db)), -1; + + va_start(ap, fmt); + vbind(stmt, fmt, ap); + va_end(ap); + + if (sqlite3_step(stmt) != SQLITE_DONE) + log_warn("db: %s", sqlite3_errmsg(db)); + else + ret = sqlite3_last_insert_rowid(db); + + sqlite3_finalize(stmt); + + return ret; +} + +static int +update(const char *sql, const char *fmt, ...) +{ + assert(sql); + assert(fmt); + + sqlite3_stmt *stmt = NULL; + va_list ap; + int ret = 1; + + if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) + return log_warn("db: %s", sqlite3_errmsg(db)), -1; + + va_start(ap, fmt); + vbind(stmt, fmt, ap); + va_end(ap); + + if (sqlite3_step(stmt) != SQLITE_DONE) + log_warn("db: %s", sqlite3_errmsg(db)); + else + ret = 0; + + sqlite3_finalize(stmt); + + return ret; +} + +static ssize_t +list(struct list *sel, const char *sql, const char *args, ...) +{ + sqlite3_stmt *stmt = NULL; + + va_list ap; + int step; + ssize_t ret = -1; + size_t tot = 0; + + sel->ctx->handle = NULL; + + if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) + return log_warn("db: %s", sqlite3_errmsg(db)), -1; + + va_start(ap, args); + vbind(stmt, args, ap); + va_end(ap); + + sel->ctx->handle = strlist_new(); + + while (tot < sel->datasz && (step = sqlite3_step(stmt)) == SQLITE_ROW) + sel->unpack(stmt, sel->ctx, (unsigned char *)sel->data + (tot++ * sel->elemwidth)); + + if (step == SQLITE_OK || step == SQLITE_DONE || step == SQLITE_ROW) + ret = tot; + else { + memset(sel->data, 0, sel->datasz * sel->elemwidth); + strlist_free(sel->ctx->handle); + sel->ctx->handle = NULL; + } + + sqlite3_finalize(stmt); + + return ret; +} + +int +db_open(const char *path) +{ + assert(path); + + if (sqlite3_open(path, &db) != SQLITE_OK) + return log_warn("db: open error: %s", sqlite3_errmsg(db)), -1; + + /* Wait for 30 seconds to lock the database. */ + sqlite3_busy_timeout(db, 30000); + + if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) + return log_warn("db: initialization error: %s", sqlite3_errmsg(db)), -1; + + return 0; +} + +int +db_project_add(struct project *p) +{ + return (p->id = insert(CHAR(sql_project_add), "ssss", p->name, p->desc, + p->url, p->script)) < 0 ? -1 : 0; +} + +int +db_project_update(const struct project *p) +{ + assert(p); + + return update(CHAR(sql_project_update), "ssssi", p->name, p->desc, + p->url, p->script, p->id); +} + +ssize_t +db_project_list(struct db_ctx *ctx, struct project *projects, size_t projectsz) +{ + struct list sel = { + .unpack = (unpacker)project_unpacker, + .data = projects, + .datasz = projectsz, + .elemwidth = sizeof (*projects), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_project_list), "z", projectsz); +} + +int +db_project_find(struct db_ctx *ctx, struct project *project) +{ + struct list sel = { + .unpack = (unpacker)project_unpacker, + .data = project, + .datasz = 1, + .elemwidth = sizeof (*project), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_project_find), "s", project->name) == 1 ? 0 : -1; +} + +int +db_project_find_id(struct db_ctx *ctx, struct project *project) +{ + struct list sel = { + .unpack = (unpacker)project_unpacker, + .data = project, + .datasz = 1, + .elemwidth = sizeof (*project), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_project_find_id), "i", project->id) == 1 ? 0 : -1; +} + +int +db_worker_add(struct worker *wk) +{ + assert(wk); + + return (wk->id = insert(CHAR(sql_worker_add), "ss", wk->name, wk->desc)) < 0 ? -1 : 0; +} + +ssize_t +db_worker_list(struct db_ctx *ctx, struct worker *wk, size_t wksz) +{ + assert(ctx); + assert(wk); + + struct list sel = { + .unpack = (unpacker)worker_unpacker, + .data = wk, + .datasz = wksz, + .elemwidth = sizeof (*wk), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_worker_list), "z", wksz); +} + +int +db_worker_find(struct db_ctx *ctx, struct worker *wk) +{ + struct list sel = { + .unpack = (unpacker)worker_unpacker, + .data = wk, + .datasz = 1, + .elemwidth = sizeof (*wk), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_worker_find), "s", wk->name) == 1 ? 0 : -1; +} + +int +db_worker_find_id(struct db_ctx *ctx, struct worker *wk) +{ + struct list sel = { + .unpack = (unpacker)worker_unpacker, + .data = wk, + .datasz = 1, + .elemwidth = sizeof (*wk), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_worker_find_id), "i", wk->id) == 1 ? 0 : -1; +} + +int +db_job_add(struct job *job) +{ + assert(job); + + return (job->id = insert(CHAR(sql_job_add), + "si", job->tag, job->project_id)) < 0 ? -1 : 0; +} + +ssize_t +db_job_todo(struct db_ctx *ctx, struct job *jobs, size_t jobsz, int worker_id) +{ + assert(ctx); + assert(jobs); + + struct list sel = { + .unpack = (unpacker)job_unpacker, + .data = jobs, + .datasz = jobsz, + .elemwidth = sizeof (*jobs), + .ctx = ctx + }; + + return list(&sel, CHAR(sql_job_todo), "iiz", worker_id, worker_id, jobsz); +} + +int +db_jobresult_add(struct jobresult *r) +{ + assert(r); + + return (r->id = insert(CHAR(sql_jobresult_add), "iiis", r->job_id, + r->worker_id, r->exitcode, r->log)) < 0 ? -1 : 0; +} + +void +db_finish(void) +{ + if (db) { + sqlite3_close(db); + db = NULL; + } +} + +void +db_ctx_finish(struct db_ctx *ctx) +{ + if (ctx->handle) { + strlist_free(ctx->handle); + ctx->handle = NULL; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/db.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,79 @@ +/* + * db.h -- scid database access + * + * 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. + */ + +#ifndef SCI_DB_H +#define SCI_DB_H + +#include <sys/types.h> +#include <stddef.h> + +struct project; +struct worker; +struct job; +struct jobresult; + +struct db_ctx { + void *handle; +}; + +int +db_open(const char *); + +int +db_job_add(struct job *); + +ssize_t +db_job_todo(struct db_ctx *, struct job *, size_t, int); + +int +db_jobresult_add(struct jobresult *); + +int +db_project_add(struct project *); + +int +db_project_update(const struct project *); + +ssize_t +db_project_list(struct db_ctx *, struct project *, size_t); + +int +db_project_find(struct db_ctx *, struct project *); + +int +db_project_find_id(struct db_ctx *, struct project *); + +int +db_worker_add(struct worker *); + +ssize_t +db_worker_list(struct db_ctx *, struct worker *, size_t); + +int +db_worker_find(struct db_ctx *, struct worker *); + +int +db_worker_find_id(struct db_ctx *, struct worker *); + +void +db_finish(void); + +void +db_ctx_finish(struct db_ctx *); + +#endif /* !SCI_DB_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/http.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,103 @@ +/* + * http.c -- HTTP parsing and rendering + * + * 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/types.h> +#include <assert.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <kcgi.h> + +#include "http.h" +#include "log.h" +#include "page.h" +#include "page-api-jobs.h" +#include "page-api-projects.h" +#include "page-api-workers.h" + +enum page { + PAGE_API, + PAGE_LAST /* Not used. */ +}; + +static void +dispatch_api(struct kreq *req) +{ + static const struct { + const char *prefix; + void (*handler)(struct kreq *); + } apis[] = { + { "v1/jobs", page_api_v1_jobs }, + { "v1/projects", page_api_v1_projects }, + { "v1/workers", page_api_v1_workers }, + { NULL, NULL } + }; + + for (size_t i = 0; apis[i].prefix; ++i) + if (strncmp(req->path, apis[i].prefix, strlen(apis[i].prefix)) == 0) + return apis[i].handler(req); + + page(req, NULL, KHTTP_404, KMIME_TEXT_HTML, "pages/404.html"); +} + +static const char *pages[] = { + [PAGE_API] = "api" +}; + +static void (*handlers[])(struct kreq *req) = { + [PAGE_API] = dispatch_api +}; + +static void +process(struct kreq *req) +{ + assert(req); + + log_debug("http: accessing page '%s'", req->path); + + if (req->page == PAGE_LAST) + page(req, NULL, KHTTP_404, KMIME_TEXT_HTML, "pages/404.html"); + else + handlers[req->page](req); +} + +void +http_fcgi_run(void) +{ + struct kreq req; + struct kfcgi *fcgi; + + if (khttp_fcgi_init(&fcgi, NULL, 0, pages, PAGE_LAST, 0) != KCGI_OK) + return; + + while (khttp_fcgi_parse(fcgi, &req) == KCGI_OK) + process(&req); + + khttp_fcgi_free(fcgi); +} + +void +http_cgi_run(void) +{ + struct kreq req; + + if (khttp_parse(&req, NULL, 0, pages, PAGE_LAST, 0) == KCGI_OK) + process(&req); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/http.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,28 @@ +/* + * http.h -- HTTP parsing and rendering + * + * 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. + */ + +#ifndef SCI_HTTP_H +#define SCI_HTTP_H + +void +http_fcgi_run(void); + +void +http_cgi_run(void); + +#endif /* !SCI_HTTP_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/main.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,84 @@ +/* + * 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 <limits.h> +#include <stdio.h> +#include <stdnoreturn.h> +#include <string.h> +#include <unistd.h> + +#include "db.h" +#include "log.h" +#include "http.h" + +static char dbpath[PATH_MAX] = VARDIR "/db/sci/sci.db"; + +noreturn static void +usage(void) +{ + fprintf(stderr, "usage: %s [-d database] [-s sock]\n", getprogname()); + exit(1); +} + +static void +init(void) +{ + log_open("scid"); + log_info("opening database %s", dbpath); + + if (db_open(dbpath) < 0) + log_die("abort: unable to open database"); +} + +static void +finish(void) +{ + db_finish(); + log_finish(); +} + +int +main(int argc, char **argv) +{ + int ch; + void (*run)(void) = &(http_cgi_run); + + while ((ch = getopt(argc, argv, "d:f")) != -1) { + switch (ch) { + case 'd': + strlcpy(dbpath, optarg, sizeof (dbpath)); + break; + case 'f': + run = &(http_fcgi_run); + break; + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + init(); + + for (;;) + run(); + + finish(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api-jobs.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,133 @@ +/* + * page-api-jobs.c -- /api/v?/jobs route + * + * 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/types.h> +#include <assert.h> +#include <stdarg.h> +#include <stdint.h> +#include <string.h> + +#include <kcgi.h> +#include <jansson.h> + +#include "config.h" +#include "db.h" +#include "log.h" +#include "page-api-jobs.h" +#include "page.h" +#include "types.h" +#include "util.h" + +static void +list(struct kreq *r, const struct job *jobs, size_t jobsz) +{ + json_t *doc; + char *dump; + + doc = job_to(jobs, jobsz); + dump = json_dumps(doc, JSON_COMPACT); + + khttp_puts(r, dump); + free(dump); + json_decref(doc); +} + +static int +save(const char *json) +{ + struct jobresult res = {0}; + int ret = -1; + + json_t *doc; + json_error_t err; + + if (!(doc = json_loads(json, 0, &err))) + log_warn("api/post: invalid JSON input: %s", err.text); + else if (jobresult_from(&res, 1, doc) < 0) + log_warn("api/post: failed to decode parameters"); + else if (db_jobresult_add(&res) < 0) + log_warn("api/post: database save error"); + else + ret = 0; + + json_decref(doc); + + return ret; +} + +static void +get(struct kreq *r) +{ + struct db_ctx ctx; + struct job jobs[SCI_JOB_LIST_MAX]; + ssize_t jobsz; + struct worker wk = { + .name = util_basename(r->path) + }; + + if (db_worker_find(&ctx, &wk) < 0) { + page(r, NULL, KHTTP_404, KMIME_APP_JSON, NULL); + return; + } + + db_ctx_finish(&ctx); + + if ((jobsz = db_job_todo(&ctx, jobs, UTIL_SIZE(jobs), wk.id)) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + list(r, jobs, jobsz); + db_ctx_finish(&ctx); + khttp_free(r); + } +} + +static void +post(struct kreq *r) +{ + if (r->fieldsz < 1) + page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); + else if (save(r->fields[0].val) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_free(r); + } +} + +void +page_api_v1_jobs(struct kreq *r) +{ + assert(r); + + switch (r->method) { + case KMETHOD_GET: + get(r); + break; + case KMETHOD_POST: + post(r); + break; + default: + page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); + break; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api-jobs.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,27 @@ +/* + * page-api-jobs.h -- /api/v?/jobs route + * + * 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. + */ + +#ifndef SCI_PAGE_API_JOBS_H +#define SCI_PAGE_API_JOBS_H + +struct kreq; + +void +page_api_v1_jobs(struct kreq *); + +#endif /* !SCI_PAGE_API_JOBS_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api-projects.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,182 @@ +/* + * page-api-projects.c -- /api/v?/projects route + * + * 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 <assert.h> + +#include "config.h" +#include "db.h" +#include "log.h" +#include "page-api-projects.h" +#include "page.h" +#include "types.h" +#include "util.h" + +static void +list(struct kreq *r, const struct project *projects, size_t projectsz) +{ + struct json_t *doc; + char *dump; + + doc = project_to(projects, projectsz); + dump = json_dumps(doc, JSON_COMPACT); + + khttp_puts(r, dump); + free(dump); + json_decref(doc); +} + +static int +save(const char *json) +{ + struct project res = {0}; + int ret = -1; + + json_t *doc; + json_error_t err; + + if (!(doc = json_loads(json, 0, &err))) + log_warn("api/post: invalid JSON input: %s", err.text); + else if (project_from(&res, 1, doc) < 0) + log_warn("api/post: failed to decode parameters"); + else if (db_project_add(&res) < 0) + log_warn("api/post: database save error"); + else + ret = 0; + + json_decref(doc); + + return ret; +} + +static void +push(struct kreq *r, const struct project *p) +{ + struct json_t *json; + char *dump; + + json = project_to(p, 1); + dump = json_dumps(json, JSON_COMPACT); + + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_puts(r, dump); + khttp_free(r); + + free(dump); + json_decref(json); +} + +static void +get_one(struct kreq *r, const char *name) +{ + struct db_ctx ctx; + struct project project = { + .name = name + }; + + if (db_project_find(&ctx, &project) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + push(r, &project); + db_ctx_finish(&ctx); + } +} + +static void +get_one_id(struct kreq *r, int id) +{ + struct db_ctx ctx; + struct project project = { + .id = id + }; + + if (db_project_find_id(&ctx, &project) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + push(r, &project); + db_ctx_finish(&ctx); + } +} + +static void +get_all(struct kreq *r) +{ + struct db_ctx ctx; + struct project projects[SCI_PROJECT_MAX]; + ssize_t projectsz; + + if ((projectsz = db_project_list(&ctx, projects, UTIL_SIZE(projects))) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + list(r, projects, projectsz); + db_ctx_finish(&ctx); + khttp_free(r); + } +} + +static void +get(struct kreq *r) +{ + char name[128]; + int id; + + if (sscanf(r->path, "v1/projects/%d", &id) == 1) + get_one_id(r, id); + else if (sscanf(r->path, "v1/projects/%127s", name) == 1) + get_one(r, name); + else + get_all(r); +} + +static void +post(struct kreq *r) +{ + if (r->fieldsz < 1) + page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); + else if (save(r->fields[0].val) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_free(r); + } +} + +void +page_api_v1_projects(struct kreq *r) +{ + assert(r); + + switch (r->method) { + case KMETHOD_GET: + get(r); + break; + case KMETHOD_POST: + post(r); + break; + default: + page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); + break; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api-projects.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,27 @@ +/* + * page-api-projects.h -- /api/v?/projects route + * + * 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. + */ + +#ifndef SCI_PAGE_API_PROJECTS_H +#define SCI_PAGE_API_PROJECTS_H + +struct kreq; + +void +page_api_v1_projects(struct kreq *); + +#endif /* !SCI_PAGE_API_PROJECTS_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api-workers.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,163 @@ +#include <assert.h> + +#include "config.h" +#include "db.h" +#include "log.h" +#include "page-api-workers.h" +#include "page.h" +#include "types.h" +#include "util.h" + +static void +list(struct kreq *r, const struct worker *workers, size_t workersz) +{ + struct json_t *doc; + char *dump; + + doc = worker_to(workers, workersz); + dump = json_dumps(doc, JSON_COMPACT); + + khttp_puts(r, dump); + free(dump); + json_decref(doc); +} + +static int +save(const char *json) +{ + struct worker res = {0}; + int ret = -1; + + json_t *doc; + json_error_t err; + + if (!(doc = json_loads(json, 0, &err))) + log_warn("api/post: invalid JSON input: %s", err.text); + else if (worker_from(&res, 1, doc) < 0) + log_warn("api/post: failed to decode parameters"); + else if (db_worker_add(&res) < 0) + log_warn("api/post: database save error"); + else + ret = 0; + + json_decref(doc); + + return ret; +} + +static void +push(struct kreq *r, const struct worker *p) +{ + struct json_t *json; + char *dump; + + json = worker_to(p, 1); + dump = json_dumps(json, JSON_COMPACT); + + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_puts(r, dump); + khttp_free(r); + + free(dump); + json_decref(json); +} + +static void +get_one(struct kreq *r, const char *name) +{ + struct db_ctx ctx; + struct worker worker = { + .name = name + }; + + if (db_worker_find(&ctx, &worker) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + push(r, &worker); + db_ctx_finish(&ctx); + } +} + +static void +get_one_id(struct kreq *r, int id) +{ + struct db_ctx ctx; + struct worker worker = { + .id = id + }; + + if (db_worker_find_id(&ctx, &worker) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + push(r, &worker); + db_ctx_finish(&ctx); + } +} + +static void +get_all(struct kreq *r) +{ + struct db_ctx ctx; + struct worker workers[SCI_PROJECT_MAX]; + ssize_t workersz; + + if ((workersz = db_worker_list(&ctx, workers, UTIL_SIZE(workers))) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + list(r, workers, workersz); + db_ctx_finish(&ctx); + khttp_free(r); + } +} + +static void +get(struct kreq *r) +{ + char name[128]; + int id; + + if (sscanf(r->path, "v1/workers/%d", &id) == 1) + get_one_id(r, id); + else if (sscanf(r->path, "v1/workers/%127s", name) == 1) + get_one(r, name); + else + get_all(r); +} + +static void +post(struct kreq *r) +{ + if (r->fieldsz < 1) + page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); + else if (save(r->fields[0].val) < 0) + page(r, NULL, KHTTP_500, KMIME_APP_JSON, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_free(r); + } +} + +void +page_api_v1_workers(struct kreq *r) +{ + assert(r); + + switch (r->method) { + case KMETHOD_GET: + get(r); + break; + case KMETHOD_POST: + post(r); + break; + default: + page(r, NULL, KHTTP_400, KMIME_APP_JSON, NULL); + break; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api-workers.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,9 @@ +#ifndef SCI_PAGE_API_WORKERS_H +#define SCI_PAGE_API_WORKERS_H + +struct kreq; + +void +page_api_v1_workers(struct kreq *); + +#endif /* !SCI_PAGE_API_WORKERS_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-index.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,105 @@ +/* + * page-index.c -- page / + * + * Copyright (c) 2020-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/types.h> +#include <assert.h> +#include <stdarg.h> +#include <stdint.h> + +#include <kcgi.h> + +#include "database.h" +#include "fragment-paste.h" +#include "page-index.h" +#include "page.h" +#include "paste.h" +#include "util.h" + +struct template { + struct kreq *req; + const struct paste *pastes; + size_t pastesz; +}; + +static const char *keywords[] = { + "pastes" +}; + +static int +template(size_t keyword, void *arg) +{ + struct template *tp = arg; + + switch (keyword) { + case 0: + for (size_t i = 0; i < tp->pastesz; ++i) + fragment_paste(tp->req, &tp->pastes[i]); + break; + default: + break; + } + + return 1; +} + +static void +get(struct kreq *r) +{ + struct paste pastes[10] = {0}; + size_t pastesz = NELEM(pastes); + + if (!database_recents(pastes, &pastesz)) + page(r, NULL, KHTTP_500, "pages/500.html"); + else + page_index_render(r, pastes, pastesz); + + for (size_t i = 0; i < pastesz; ++i) + paste_finish(&pastes[i]); +} + +void +page_index_render(struct kreq *r, const struct paste *pastes, size_t pastesz) +{ + struct template data = { + .req = r, + .pastes = pastes, + .pastesz = pastesz + }; + + struct ktemplate kt = { + .key = keywords, + .keysz = NELEM(keywords), + .arg = &data, + .cb = template + }; + + page(r, &kt, KHTTP_200, "pages/index.html"); +} + +void +page_index(struct kreq *r) +{ + switch (r->method) { + case KMETHOD_GET: + get(r); + break; + default: + page(r, NULL, KHTTP_400, "400.html"); + break; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,40 @@ +/* + * page.c -- page renderer + * + * 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 "page.h" +#include "util.h" + +void +page(struct kreq *req, + const struct ktemplate *tmpl, + enum khttp status, + enum kmime mime, + const char *file) +{ + khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]); + khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]); + khttp_body(req); + + if (file) { + khttp_template(req, NULL, util_path("fragments/header.html")); + khttp_template(req, tmpl, util_path(file)); + khttp_template(req, NULL, util_path("fragments/footer.html")); + } + + khttp_free(req); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,30 @@ +/* + * page.h -- page renderer + * + * 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. + */ + +#ifndef SCI_PAGE_H +#define SCI_PAGE_H + +#include <sys/types.h> +#include <stdarg.h> +#include <stdint.h> +#include <kcgi.h> + +void +page(struct kreq *, const struct ktemplate *, enum khttp, enum kmime, const char *); + +#endif /* !SCI_PAGE_H */
--- a/sciwebd.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/* - * sciwebd.c -- main sciwebd(8) CGI handler 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 <stdio.h> -#include <stdlib.h> -#include <stdnoreturn.h> -#include <unistd.h> - -#include "http.h" -#include "log.h" - -const char *sock = VARDIR "/run/sci.sock"; - -noreturn static void -usage(void) -{ - fprintf(stderr, "usage: %s [-f] [-s sock]\n", getprogname()); - exit(1); -} - -static void -init(void) -{ - log_open(getprogname()); -} - -static void -finish(void) -{ - log_finish(); -} - -int -main(int argc, char **argv) -{ - int ch; - void (*run)(void) = &(http_cgi_run); - - setprogname("sciwebd"); - - while ((ch = getopt(argc, argv, "fs:")) != -1) { - switch (ch) { - case 'f': - run = &(http_fcgi_run); - break; - case 's': - sock = optarg; - break; - default: - usage(); - break; - } - } - - init(); - run(); - finish(); -}
--- a/sciworkerd.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,676 +0,0 @@ -/* - * sciworkerd.c -- main sciworkerd(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/queue.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <assert.h> -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <poll.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdnoreturn.h> -#include <string.h> -#include <unistd.h> - -#include <curl/curl.h> -#include <jansson.h> - -#include "config.h" -#include "log.h" -#include "types.h" -#include "util.h" - -#define TAG_MAX 256 - -enum taskst { - TASKST_PENDING, /* not started yet. */ - TASKST_RUNNING, /* currently running. */ - TASKST_COMPLETED, /* completed but not synced yet. */ - TASKST_SYNCING /* was unable to send result to host. */ -}; - -struct task { - enum taskst status; - pid_t child; - int pipe[2]; - int exitcode; - int job_id; - int project_id; - char job_tag[TAG_MAX]; - char out[SCI_CONSOLE_MAX]; - char script[PATH_MAX]; - int scriptfd; - TAILQ_ENTRY(task) link; -}; - -TAILQ_HEAD(tasks, task); - -struct fds { - struct pollfd *list; - size_t listsz; -}; - -struct fetch { - char buf[SCI_MSG_MAX]; - FILE *bufp; -}; - -static struct { - char *url; - char *worker; - int maxbuilds; -} config = { - .url = "http://localhost", - .worker = "default", - .maxbuilds = 4 -}; - -static struct tasks tasks = TAILQ_HEAD_INITIALIZER(tasks); -static struct worker worker; -static int alive = 1; - -noreturn static void -usage(void) -{ - fprintf(stderr, "usage: %s [-m maxbuild] [-u url] [-w worker]\n", getprogname()); - exit(1); -} - -static inline struct task * -find_by_fd(int fd) -{ - struct task *tk; - - TAILQ_FOREACH(tk, &tasks, link) - if (tk->pipe[0] == fd) - return tk; - - return NULL; -} - -static inline struct task * -find_by_pid(pid_t pid) -{ - struct task *t; - - TAILQ_FOREACH(t, &tasks, link) - if (t->child == pid) - return t; - - return NULL; -} - -static void -destroy(struct task *tk) -{ - log_debug("destroying task %d", tk->job_id); - unlink(tk->script); - - if (tk->pipe[0]) - close(tk->pipe[0]); - if (tk->pipe[1]) - close(tk->pipe[1]); - if (tk->scriptfd) - close(tk->scriptfd); - - TAILQ_REMOVE(&tasks, tk, link); - memset(tk, 0, sizeof (*tk)); - free(tk); -} - -static int -spawn(struct task *tk) -{ - if (pipe(tk->pipe) < 0) - goto cleanup; - - switch ((tk->child = fork())) { - case -1: - log_warn("spawn: %s", strerror(errno)); - goto cleanup; - case 0: - /* Child. */ - dup2(tk->pipe[1], STDOUT_FILENO); - dup2(tk->pipe[1], STDERR_FILENO); - close(tk->pipe[0]); - close(tk->pipe[1]); - log_debug("spawn: running process (%lld) %s", - (long long int)tk->child, tk->script); - - if (execl(tk->script, tk->script, tk->job_tag, NULL) < 0) { - tk->status = TASKST_PENDING; - log_warn("exec %s: %s", tk->script, strerror(errno)); - exit(0); - } - break; - default: - /* Parent */ - tk->status = TASKST_RUNNING; - break; - } - - return 0; - -cleanup: - destroy(tk); - - return -1; -} - -static const char * -makeurl(const char *fmt, ...) -{ - assert(fmt); - - static char url[256]; - char page[128] = {0}; - va_list ap; - - va_start(ap, fmt); - vsnprintf(page, sizeof (page), fmt, ap); - va_end(ap); - - snprintf(url, sizeof (url), "%s/%s", config.url, page); - - return url; -} - -static void -complete(int signum, siginfo_t *sinfo, void *ctx) -{ - (void)ctx; - (void)signum; - - struct task *tk; - - if (waitpid(sinfo->si_pid, NULL, 0) < 0) - log_warn("waitpid: %s", strerror(errno)); - - if ((tk = find_by_pid(sinfo->si_pid))) { - log_debug("process %d terminated (exitcode=%d)", - (int)sinfo->si_pid, sinfo->si_status); - - close(tk->pipe[1]); - tk->status = TASKST_COMPLETED; - tk->exitcode = sinfo->si_status; - tk->pipe[1] = 0; - } -} - -static void -stop(int signum) -{ - log_warn("exiting on signal %d", signum); - alive = 0; -} - -static char * -uploadenc(const struct task *tk) -{ - json_t *doc; - - struct jobresult res = {0}; - char *dump; - - res.job_id = tk->job_id; - res.exitcode = tk->exitcode; - res.log = tk->out; - res.worker_id = worker.id; - - doc = jobresult_to(&res, 1); - dump = json_dumps(doc, JSON_COMPACT); - - json_decref(doc); - - return dump; -} - -static size_t -getcb(char *in, size_t n, size_t w, FILE *fp) -{ - if (fwrite(in, n, w, fp) != w) - return log_warn("get: %s", strerror(errno)), 0; - - return w; -} - -static json_t * -get(const char *topic, const char *url) -{ - CURL *curl; - CURLcode code; - - json_t *doc; - json_error_t error; - - char buf[SCI_MSG_MAX]; - long status; - FILE *fp; - - curl = curl_easy_init(); - - if (!(fp = fmemopen(buf, sizeof (buf), "w"))) - err(1, "fmemopen"); - - curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, getcb); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); - - if ((code = curl_easy_perform(curl)) != CURLE_OK) - log_warn("%s: %s", topic, curl_easy_strerror(code)); - - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); - curl_easy_cleanup(curl); - - fclose(fp); - - if (code != CURLE_OK) - return log_warn("%s: %s", topic, curl_easy_strerror(code)), NULL; - if (status != 200) - return log_warn("%s: unexpected status code %ld", topic, status), NULL; - if (!(doc = json_loads(buf, 0, &error))) - return log_warn("%s: %s", topic, error.text), NULL; - - return doc; -} - -static size_t -silent(char *in, size_t n, size_t w, void *data) -{ - (void)in; - (void)n; - (void)data; - - return w; -} - -static void -upload(struct task *tk) -{ - CURL *curl; - CURLcode code; - struct curl_slist *headers = NULL; - long status; - char *dump; - - curl = curl_easy_init(); - headers = curl_slist_append(headers, "Content-Type: application/json"); - curl_easy_setopt(curl, CURLOPT_URL, makeurl("api/v1/jobs")); - //curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:4000"); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, silent); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (dump = uploadenc(tk))); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(dump)); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - code = curl_easy_perform(curl); - curl_slist_free_all(headers); - - /* - * If we fail to upload data, we put the result into syncing mode so - * that we retry later without redoing the job over and over - */ - tk->status = TASKST_SYNCING; - - if (code != CURLE_OK) - log_warn("upload: %s", curl_easy_strerror(code)); - else { - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); - - if (status != 200) - log_warn("upload: unexpected return code: %ld", status); - else - destroy(tk); - } - - free(dump); - curl_easy_cleanup(curl); -} - -static inline int -pending(int id) -{ - struct task *t; - - TAILQ_FOREACH(t, &tasks, link) - if (t->job_id == id) - return 1; - - return 0; -} - -static void -queue(int id, int project_id, const char *tag) -{ - struct task *tk; - - log_info("queued job build (%d) for tag %s\n", id, tag); - - tk = util_calloc(1, sizeof (*tk)); - tk->job_id = id; - tk->project_id = project_id; - strlcpy(tk->job_tag, tag, sizeof (tk->job_tag)); - - TAILQ_INSERT_TAIL(&tasks, tk, link); -} - -static void -merge(json_t *doc) -{ - struct job jobs[SCI_JOB_LIST_MAX]; - ssize_t jobsz; - - if ((jobsz = job_from(jobs, UTIL_SIZE(jobs), doc)) < 0) - log_warn("fetchjobs: %s", strerror(errno)); - else { - for (ssize_t i = 0; i < jobsz; ++i) { - if (!pending(jobs[i].id)) - queue(jobs[i].id, jobs[i].project_id, jobs[i].tag); - } - } - - json_decref(doc); -} - -static void -fetchjobs(void) -{ - json_t *doc; - - if (!(doc = get("fetch", makeurl("api/v1/jobs/%s", config.worker)))) - log_warn("unable to retrieve jobs"); - else - merge(doc); -} - -/* - * This function reads stdout/stderr pipe from child and optionally remove them - * if they have completed. - */ -static void -readall(struct fds *fds) -{ - struct task *tk; - char buf[BUFSIZ]; - ssize_t nr; - - for (size_t i = 0; i < fds->listsz; ++i) { - if (fds->list[i].revents == 0) - continue; - if (!(tk = find_by_fd(fds->list[i].fd))) - continue; - - /* Read stdout/stderr from children pipe. */ - if ((nr = read(fds->list[i].fd, buf, sizeof (buf) - 1)) <= 0) - tk->status = TASKST_SYNCING; - else { - buf[nr] = 0; - strlcat(tk->out, buf, sizeof (tk->out)); - } - } -} - -/* - * Retrieve status code from spawned process complete or upload again if they - * failed to sync. - */ -static void -flushall(void) -{ - struct task *tk, *tmp; - - TAILQ_FOREACH_SAFE(tk, &tasks, link, tmp) - if (tk->status == TASKST_SYNCING) - upload(tk); -} - -static int -extract(struct task *tk, json_t *doc) -{ - struct project proj; - size_t len; - - if (project_from(&proj, 1, doc) < 0) { - json_decref(doc); - log_warn("fetchproject: %s", strerror(errno)); - return -1; - } - - len = strlen(proj.script); - - if ((size_t)write(tk->scriptfd, proj.script, len) != len) { - json_decref(doc); - log_warn("fetchproject: %s", strerror(errno)); - return -1; - } - - /* Close so we can finally spawn it. */ - close(tk->scriptfd); - tk->scriptfd = 0; - - return 0; -} - -static int -fetchproject(struct task *tk) -{ - json_t *doc; - - if (!(doc = get("fetchproject", makeurl("api/v1/projects/%d", tk->project_id)))) - return -1; - - return extract(tk, doc); -} - -/* - * Create a task to run the script. This will retrieve the project script code - * at this moment and put it in a temporary file. - */ -static void -createtask(struct task *tk) -{ - if (tk->status != TASKST_PENDING) - return; - - log_debug("creating task (id=%d, tag=%s)", tk->job_id, tk->job_tag); - snprintf(tk->script, sizeof (tk->script), "/tmp/sciworkerd-%d-XXXXXX", tk->job_id); - - if ((tk->scriptfd = mkstemp(tk->script)) < 0 || - fchmod(tk->scriptfd, S_IRUSR | S_IWUSR | S_IXUSR) < 0) { - unlink(tk->script); - log_warn("%s", strerror(errno)); - return; - } - - if (fetchproject(tk) < 0) { - unlink(tk->script); - close(tk->scriptfd); - tk->scriptfd = 0; - } else - spawn(tk); -} - -/* - * Start all pending tasks if the limit of running tasks is not reached. - */ -static void -startall(void) -{ - size_t nrunning = 0; - struct task *tk; - - TAILQ_FOREACH(tk, &tasks, link) - if (tk->status == TASKST_RUNNING) - ++nrunning; - - if (nrunning >= (size_t)config.maxbuilds) - log_debug("not spawning new process because limit is reached"); - else { - tk = TAILQ_FIRST(&tasks); - - while (tk && nrunning++ < (size_t)config.maxbuilds) { - createtask(tk); - tk = TAILQ_NEXT(tk, link); - } - } -} - -static void -fetchworker(void) -{ - json_t *doc; - - if (!(doc = get("fetchworker", makeurl("api/v1/workers/%s", config.worker))) || - worker_from(&worker, 1, doc) < 0) - errx(1, "unable to retrieve worker id"); - - log_info("worker id: %d", worker.id); - log_info("worker name: %s", worker.name); - log_info("worker description: %s", worker.desc); - - json_decref(doc); -} - -static void -init(void) -{ - struct sigaction sa; - - sa.sa_flags = SA_SIGINFO | SA_RESTART; - sa.sa_sigaction = complete; - sigemptyset(&sa.sa_mask); - - if (sigaction(SIGCHLD, &sa, NULL) < 0) - err(1, "sigaction"); - - sa.sa_flags = SA_RESTART; - sa.sa_handler = stop; - sigemptyset(&sa.sa_mask); - - if (sigaction(SIGTERM, &sa, NULL) < 0 || sigaction(SIGINT, &sa, NULL) < 0) - err(1, "sigaction"); - - log_open("sciworkerd"); - fetchworker(); -} - -static struct fds -prepare(void) -{ - struct fds fds = {0}; - struct task *tk; - size_t i = 0; - - TAILQ_FOREACH(tk, &tasks, link) - if (tk->status == TASKST_RUNNING || tk->status == TASKST_COMPLETED) - fds.listsz++; - - fds.list = util_calloc(fds.listsz, sizeof (*fds.list)); - - TAILQ_FOREACH(tk, &tasks, link) { - if (tk->status == TASKST_RUNNING || tk->status == TASKST_COMPLETED) { - fds.list[i].fd = tk->pipe[0]; - fds.list[i++].events = POLLIN | POLLPRI; - } - } - - return fds; -} - -static void -run(void) -{ - struct fds fds; - - fds = prepare(); - - if (poll(fds.list, fds.listsz, 5000) < 0 && errno != EINTR) - err(1, "poll"); - - fetchjobs(); - readall(&fds); - startall(); - flushall(); -} - -static void -finish(void) -{ - size_t tot = 0; - struct task *tk, *tmp; - - TAILQ_FOREACH(tk, &tasks, link) - tot++; - - signal(SIGCHLD, SIG_IGN); - log_debug("killing remaining %zu tasks", tot); - - TAILQ_FOREACH_SAFE(tk, &tasks, link, tmp) { - if (tk->status == TASKST_RUNNING) { - kill(tk->child, SIGTERM); - waitpid(tk->child, NULL, 0); - } - - destroy(tk); - } -} - -int -main(int argc, char **argv) -{ - int ch; - const char *errstr; - - setprogname("sciworkerd"); - - while ((ch = getopt(argc, argv, "m:u:w:")) != -1) { - switch (ch) { - case 'm': - config.maxbuilds = strtonum(optarg, 0, INT_MAX, &errstr); - - if (errstr) - errx(1, "%s: %s", optarg, errstr); - - break; - case 'u': - config.url = optarg; - break; - case 'w': - config.worker = optarg; - break; - default: - usage(); - break; - } - } - - init(); - - while (alive) - run(); - - finish(); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciworkerd/main.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,734 @@ +/* + * sciworkerd.c -- main sciworkerd(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. + */ + +#if 0 + +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <unistd.h> + +#include <curl/curl.h> +#include <jansson.h> + +#include "config.h" +#include "log.h" +#include "types.h" +#include "util.h" + +#define TAG_MAX 256 + +struct task { + enum taskst status; + pid_t child; + int pipe[2]; + int exitcode; + int job_id; + int project_id; + char job_tag[TAG_MAX]; + char out[SCI_CONSOLE_MAX]; + char script[PATH_MAX]; + int scriptfd; + TAILQ_ENTRY(task) link; +}; + +TAILQ_HEAD(tasks, task); + +struct fds { + struct pollfd *list; + size_t listsz; +}; + +struct fetch { + char buf[SCI_MSG_MAX]; + FILE *bufp; +}; + +static struct { + char *url; + char *worker; + int maxbuilds; +} config = { + .url = "http://localhost", + .worker = "default", + .maxbuilds = 4 +}; + +static struct tasks tasks = TAILQ_HEAD_INITIALIZER(tasks); +static struct worker worker; +static int alive = 1; + +/* + * Show usage and exit with code 1. + */ +noreturn static void +usage(void) +{ + fprintf(stderr, "usage: %s [-m maxbuild] [-u url] [-w worker]\n", getprogname()); + exit(1); +} + +/* + * Find a task by its id. + */ +static inline struct task * +find_by_fd(int fd) +{ + struct task *tk; + + TAILQ_FOREACH(tk, &tasks, link) + if (tk->pipe[0] == fd) + return tk; + + return NULL; +} + +/* + * Find a task by its pid number. + */ +static inline struct task * +find_by_pid(pid_t pid) +{ + struct task *t; + + TAILQ_FOREACH(t, &tasks, link) + if (t->child == pid) + return t; + + return NULL; +} + +/* + * Destroy a task entirely. + */ +static void +destroy(struct task *tk) +{ + log_debug("destroying task %d", tk->job_id); + unlink(tk->script); + + if (tk->pipe[0]) + close(tk->pipe[0]); + if (tk->pipe[1]) + close(tk->pipe[1]); + if (tk->scriptfd) + close(tk->scriptfd); + + TAILQ_REMOVE(&tasks, tk, link); + memset(tk, 0, sizeof (*tk)); + free(tk); +} + +static const char * +makeurl(const char *fmt, ...) +{ + assert(fmt); + + static char url[256]; + char page[128] = {0}; + va_list ap; + + va_start(ap, fmt); + vsnprintf(page, sizeof (page), fmt, ap); + va_end(ap); + + snprintf(url, sizeof (url), "%s/%s", config.url, page); + + return url; +} + +static void +complete(int signum, siginfo_t *sinfo, void *ctx) +{ + (void)ctx; + (void)signum; + + struct task *tk; + + if (waitpid(sinfo->si_pid, NULL, 0) < 0) + log_warn("waitpid: %s", strerror(errno)); + + if ((tk = find_by_pid(sinfo->si_pid))) { + log_debug("process %d terminated (exitcode=%d)", + (int)sinfo->si_pid, sinfo->si_status); + + close(tk->pipe[1]); + tk->status = TASKST_COMPLETED; + tk->exitcode = sinfo->si_status; + tk->pipe[1] = 0; + } +} + +static void +stop(int signum) +{ + log_warn("exiting on signal %d", signum); + alive = 0; +} + +static char * +uploadenc(const struct task *tk) +{ + json_t *doc; + + struct jobresult res = {0}; + char *dump; + + res.job_id = tk->job_id; + res.exitcode = tk->exitcode; + res.log = tk->out; + res.worker_id = worker.id; + + doc = jobresult_to(&res, 1); + dump = json_dumps(doc, JSON_COMPACT); + + json_decref(doc); + + return dump; +} + +static size_t +getcb(char *in, size_t n, size_t w, FILE *fp) +{ + if (fwrite(in, n, w, fp) != w) + return log_warn("get: %s", strerror(errno)), 0; + + return w; +} + +static json_t * +get(const char *topic, const char *url) +{ + CURL *curl; + CURLcode code; + + json_t *doc; + json_error_t error; + + char buf[SCI_MSG_MAX]; + long status; + FILE *fp; + + curl = curl_easy_init(); + + if (!(fp = fmemopen(buf, sizeof (buf), "w"))) + err(1, "fmemopen"); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, getcb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + + if ((code = curl_easy_perform(curl)) != CURLE_OK) + log_warn("%s: %s", topic, curl_easy_strerror(code)); + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + curl_easy_cleanup(curl); + + fclose(fp); + + if (code != CURLE_OK) + return log_warn("%s: %s", topic, curl_easy_strerror(code)), NULL; + if (status != 200) + return log_warn("%s: unexpected status code %ld", topic, status), NULL; + if (!(doc = json_loads(buf, 0, &error))) + return log_warn("%s: %s", topic, error.text), NULL; + + return doc; +} + +static size_t +silent(char *in, size_t n, size_t w, void *data) +{ + (void)in; + (void)n; + (void)data; + + return w; +} + +static void +upload(struct task *tk) +{ + CURL *curl; + CURLcode code; + struct curl_slist *headers = NULL; + long status; + char *dump; + + curl = curl_easy_init(); + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_URL, makeurl("api/v1/jobs")); + //curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:4000"); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, silent); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (dump = uploadenc(tk))); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(dump)); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + code = curl_easy_perform(curl); + curl_slist_free_all(headers); + + /* + * If we fail to upload data, we put the result into syncing mode so + * that we retry later without redoing the job over and over + */ + tk->status = TASKST_SYNCING; + + if (code != CURLE_OK) + log_warn("upload: %s", curl_easy_strerror(code)); + else { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + + if (status != 200) + log_warn("upload: unexpected return code: %ld", status); + else + destroy(tk); + } + + free(dump); + curl_easy_cleanup(curl); +} + +static inline int +pending(int id) +{ + struct task *t; + + TAILQ_FOREACH(t, &tasks, link) + if (t->job_id == id) + return 1; + + return 0; +} + +static void +queue(int id, int project_id, const char *tag) +{ + struct task *tk; + + log_info("queued job build (%d) for tag %s\n", id, tag); + + tk = util_calloc(1, sizeof (*tk)); + tk->job_id = id; + tk->project_id = project_id; + strlcpy(tk->job_tag, tag, sizeof (tk->job_tag)); + + TAILQ_INSERT_TAIL(&tasks, tk, link); +} + +static void +merge(json_t *doc) +{ + struct job jobs[SCI_JOB_LIST_MAX]; + ssize_t jobsz; + + if ((jobsz = job_from(jobs, UTIL_SIZE(jobs), doc)) < 0) + log_warn("fetchjobs: %s", strerror(errno)); + else { + for (ssize_t i = 0; i < jobsz; ++i) { + if (!pending(jobs[i].id)) + queue(jobs[i].id, jobs[i].project_id, jobs[i].tag); + } + } + + json_decref(doc); +} + +static void +fetchjobs(void) +{ + json_t *doc; + + if (!(doc = get("fetch", makeurl("api/v1/jobs/%s", config.worker)))) + log_warn("unable to retrieve jobs"); + else + merge(doc); +} + +/* + * This function reads stdout/stderr pipe from child and optionally remove them + * if they have completed. + */ +static void +readall(struct fds *fds) +{ + struct task *tk; + char buf[BUFSIZ]; + ssize_t nr; + + for (size_t i = 0; i < fds->listsz; ++i) { + if (fds->list[i].revents == 0) + continue; + if (!(tk = find_by_fd(fds->list[i].fd))) + continue; + + /* Read stdout/stderr from children pipe. */ + if ((nr = read(fds->list[i].fd, buf, sizeof (buf) - 1)) <= 0) + tk->status = TASKST_SYNCING; + else { + buf[nr] = 0; + strlcat(tk->out, buf, sizeof (tk->out)); + } + } +} + +/* + * Retrieve status code from spawned process complete or upload again if they + * failed to sync. + */ +static void +flushall(void) +{ + struct task *tk, *tmp; + + TAILQ_FOREACH_SAFE(tk, &tasks, link, tmp) + if (tk->status == TASKST_SYNCING) + upload(tk); +} + +static int +extract(struct task *tk, json_t *doc) +{ + struct project proj; + size_t len; + + if (project_from(&proj, 1, doc) < 0) { + json_decref(doc); + log_warn("fetchproject: %s", strerror(errno)); + return -1; + } + + len = strlen(proj.script); + + if ((size_t)write(tk->scriptfd, proj.script, len) != len) { + json_decref(doc); + log_warn("fetchproject: %s", strerror(errno)); + return -1; + } + + /* Close so we can finally spawn it. */ + close(tk->scriptfd); + tk->scriptfd = 0; + + return 0; +} + +static int +fetchproject(struct task *tk) +{ + json_t *doc; + + if (!(doc = get("fetchproject", makeurl("api/v1/projects/%d", tk->project_id)))) + return -1; + + return extract(tk, doc); +} + +/* + * Create a task to run the script. This will retrieve the project script code + * at this moment and put it in a temporary file. + */ +static void +createtask(struct task *tk) +{ + if (tk->status != TASKST_PENDING) + return; + + log_debug("creating task (id=%d, tag=%s)", tk->job_id, tk->job_tag); + snprintf(tk->script, sizeof (tk->script), "/tmp/sciworkerd-%d-XXXXXX", tk->job_id); + + if ((tk->scriptfd = mkstemp(tk->script)) < 0 || + fchmod(tk->scriptfd, S_IRUSR | S_IWUSR | S_IXUSR) < 0) { + unlink(tk->script); + log_warn("%s", strerror(errno)); + return; + } + + if (fetchproject(tk) < 0) { + unlink(tk->script); + close(tk->scriptfd); + tk->scriptfd = 0; + } else + spawn(tk); +} + +/* + * Start all pending tasks if the limit of running tasks is not reached. + */ +static void +startall(void) +{ + size_t nrunning = 0; + struct task *tk; + + TAILQ_FOREACH(tk, &tasks, link) + if (tk->status == TASKST_RUNNING) + ++nrunning; + + if (nrunning >= (size_t)config.maxbuilds) + log_debug("not spawning new process because limit is reached"); + else { + tk = TAILQ_FIRST(&tasks); + + while (tk && nrunning++ < (size_t)config.maxbuilds) { + createtask(tk); + tk = TAILQ_NEXT(tk, link); + } + } +} + +static void +fetchworker(void) +{ + json_t *doc; + + if (!(doc = get("fetchworker", makeurl("api/v1/workers/%s", config.worker))) || + worker_from(&worker, 1, doc) < 0) + errx(1, "unable to retrieve worker id"); + + log_info("worker id: %d", worker.id); + log_info("worker name: %s", worker.name); + log_info("worker description: %s", worker.desc); + + json_decref(doc); +} + +static void +init(void) +{ + struct sigaction sa; + + sa.sa_flags = SA_SIGINFO | SA_RESTART; + sa.sa_sigaction = complete; + sigemptyset(&sa.sa_mask); + + if (sigaction(SIGCHLD, &sa, NULL) < 0) + err(1, "sigaction"); + + sa.sa_flags = SA_RESTART; + sa.sa_handler = stop; + sigemptyset(&sa.sa_mask); + + if (sigaction(SIGTERM, &sa, NULL) < 0 || sigaction(SIGINT, &sa, NULL) < 0) + err(1, "sigaction"); + + log_open("sciworkerd"); + fetchworker(); +} + +static struct fds +prepare(void) +{ + struct fds fds = {0}; + struct task *tk; + size_t i = 0; + + TAILQ_FOREACH(tk, &tasks, link) + if (tk->status == TASKST_RUNNING || tk->status == TASKST_COMPLETED) + fds.listsz++; + + fds.list = util_calloc(fds.listsz, sizeof (*fds.list)); + + TAILQ_FOREACH(tk, &tasks, link) { + if (tk->status == TASKST_RUNNING || tk->status == TASKST_COMPLETED) { + fds.list[i].fd = tk->pipe[0]; + fds.list[i++].events = POLLIN | POLLPRI; + } + } + + return fds; +} + +static void +run(void) +{ + struct fds fds; + + fds = prepare(); + + if (poll(fds.list, fds.listsz, 5000) < 0 && errno != EINTR) + err(1, "poll"); + + fetchjobs(); + readall(&fds); + startall(); + flushall(); +} + +static void +finish(void) +{ + size_t tot = 0; + struct task *tk, *tmp; + + TAILQ_FOREACH(tk, &tasks, link) + tot++; + + signal(SIGCHLD, SIG_IGN); + log_debug("killing remaining %zu tasks", tot); + + TAILQ_FOREACH_SAFE(tk, &tasks, link, tmp) { + if (tk->status == TASKST_RUNNING) { + kill(tk->child, SIGTERM); + waitpid(tk->child, NULL, 0); + } + + destroy(tk); + } +} + +int +main(int argc, char **argv) +{ + int ch; + const char *errstr; + + setprogname("sciworkerd"); + + while ((ch = getopt(argc, argv, "m:u:w:")) != -1) { + switch (ch) { + case 'm': + config.maxbuilds = strtonum(optarg, 0, INT_MAX, &errstr); + + if (errstr) + errx(1, "%s: %s", optarg, errstr); + + break; + case 'u': + config.url = optarg; + break; + case 'w': + config.worker = optarg; + break; + default: + usage(); + break; + } + } + + init(); + + while (alive) + run(); + + finish(); +} +#endif + + + + + + + + + + +#include <err.h> +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "types.h" +#include "task.h" + +#define SCRIPT \ + "#!/bin/sh\n" \ + "echo yes\n" \ + "sleep 10\n" \ + "echo no 1>&2\n" \ + "sleep 1\n" \ + "exit 1" + +int +main(void) +{ + struct job job = { + .project_id = 10, + .id = 10, + .tag = "1234" + }; + struct sigaction sa = {0}; + struct pollfd fd; + struct task *t; + int run = 1; + + t = task_new(&job); + + if (task_setup(t, SCRIPT) < 0) + err(1, "task_set_script"); + if (task_start(t) < 0) + err(1, "task_start"); + + while (run) { + if (difftime(time(NULL), task_uptime(t)) >= 3) { + printf("task timeout !\n"); + task_kill(t); + task_wait(t); + break; + } + + task_prepare(t, &fd); + + if (poll(&fd, 1, 250) < 0 && errno != EINTR) + err(1, "poll"); + + switch (task_sync(t, &fd)) { + case -1: + err(1, "task_sync"); + case 0: + run = 0; + task_wait(t); + break; + default: + /* Keep going... */ + break; + } + } + + switch (task_status(t)) { + case TASKSTATUS_EXITED: + printf("process exited with code: %d\n", task_code(t).exitcode); + break; + case TASKSTATUS_KILLED: + printf("process killed with signal %d\n", task_code(t).sigcode); + break; + default: + break; + } + + printf("== console ==\n%s==\n", task_console(t)); + task_free(t); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciworkerd/task.c Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,248 @@ +#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 "types.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; + int job_id; + int project_id; + char *job_tag; + FILE *fp; + char *console; + size_t consolesz; + int scriptfd; + char scriptpath[PATH_MAX]; + time_t startup; +}; + +struct task * +task_new(const struct job *job) +{ + struct task *task; + + task = util_calloc(1, sizeof (*task)); + task->project_id = job->project_id; + task->job_id = job->id; + task->job_tag = util_strdup(job->tag); + task->pipe[0] = task->pipe[1] = -1; + task->child = -1; + + if (!(task->fp = open_memstream(&task->console, &task->consolesz))) { + task_free(task); + return NULL; + } + + return task; +} + +int +task_setup(struct task *self, const char *script) +{ + assert(self); + assert(script); + + const size_t len = strlen(script); + + snprintf(self->scriptpath, sizeof (self->scriptpath), + "/tmp/sciworkerd-%d-XXXXXX", self->job_id); + + if ((self->scriptfd = mkstemp(self->scriptpath)) < 0) + goto failed; + if (fchmod(self->scriptfd, MODE) < 0) + goto failed; + if (write(self->scriptfd, script, len) != len) + goto failed; + + return 0; + +failed: + log_warn("%s", strerror(errno)); + unlink(self->scriptpath); + + return -1; +} + +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 0; +} + +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; +} + +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) != 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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciworkerd/task.h Tue Jul 12 20:20:51 2022 +0200 @@ -0,0 +1,59 @@ +#ifndef SCIWORKERD_TASK_H +#define SCIWORKERD_TASK_H + +#include <sys/types.h> + +struct job; +struct pollfd; +struct task; + +enum taskstatus { + TASKSTATUS_PENDING, /* not started yet. */ + TASKSTATUS_RUNNING, /* currently running. */ + TASKSTATUS_EXITED, /* process exited normally. */ + TASKSTATUS_KILLED, /* process killed killed. */ + TASKSTATUS_SYNCING /* was unable to send result to host. */ +}; + +struct taskcode { + int exitcode; + int sigcode; +}; + +struct task * +task_new(const struct job *job); + +int +task_setup(struct task *self, const char *script); + +pid_t +task_start(struct task *self); + +int +task_wait(struct task *self); + +int +task_kill(struct task *self); + +void +task_prepare(struct task *self, struct pollfd *fd); + +int +task_sync(struct task *self, const struct pollfd *fd); + +time_t +task_uptime(const struct task *self); + +const char * +task_console(const struct task *self); + +enum taskstatus +task_status(const struct task *self); + +struct taskcode +task_code(const struct task *self); + +void +task_free(struct task *self); + +#endif /* !SCIWORKERD_TASK_H */
--- a/types.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,222 +0,0 @@ -/* - * types.c -- type definitions and conversions - * - * 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 <assert.h> -#include <errno.h> - -#include "types.h" - -typedef json_t * (*packer)(const void *); -typedef int (*unpacker)(void *, json_t *); - -static inline json_t * -job_packer(const struct job *job) -{ - return json_pack("{si si ss}", - "id", job->id, - "project_id", job->project_id, - "tag", job->tag - ); -} - -static inline int -job_unpacker(struct job *job, json_t *doc) -{ - return json_unpack(doc, "{si si ss}", - "id", &job->id, - "project_id", &job->project_id, - "tag", &job->tag - ); -} - -static inline json_t * -jobresult_packer(const struct jobresult *res) -{ - return json_pack("{si si si si ss}", - "id", res->id, - "job_id", res->job_id, - "worker_id", res->worker_id, - "exitcode", res->exitcode, - "log", res->log - ); -} - -static inline int -jobresult_unpacker(struct jobresult *res, json_t *doc) -{ - return json_unpack(doc, "{si si si si ss}", - "id", &res->id, - "job_id", &res->job_id, - "worker_id", &res->worker_id, - "exitcode", &res->exitcode, - "log", &res->log - ); -} - -static inline json_t * -worker_packer(const struct worker *w) -{ - return json_pack("{si ss ss}", - "id", w->id, - "name", w->name, - "desc", w->desc - ); -} - -static inline int -worker_unpacker(struct worker *w, json_t *doc) -{ - return json_unpack(doc, "{si ss ss}", - "id", &w->id, - "name", &w->name, - "desc", &w->desc - ); -} - -static inline json_t * -project_packer(struct project *p) -{ - return json_pack("{si ss ss ss ss}", - "id", p->id, - "name", p->name, - "desc", p->desc, - "url", p->url, - "script", p->script - ); -} - -static inline int -project_unpacker(struct project *p, json_t *doc) -{ - return json_unpack(doc, "{si ss ss ss ss}", - "id", &p->id, - "name", &p->name, - "desc", &p->desc, - "url", &p->url, - "script", &p->script - ); -} - -static json_t * -to(const void *array, size_t arraysz, size_t width, packer fn) -{ - json_t *doc; - - if (arraysz == 1) - doc = fn(array); - else { - doc = json_array(); - - for (size_t i = 0; i < arraysz; ++i) - json_array_append(doc, fn((char *)array + (i * width))); - } - - return doc; -} - -static ssize_t -from(void *array, size_t arraysz, size_t width, json_t *doc, unpacker fn) -{ - json_t *val; - size_t i, tot = 0; - - if (json_is_array(doc)) { - json_array_foreach(doc, i, val) { - if (tot >= arraysz) - return errno = ERANGE, -1; - if (fn((char *)array + (tot++ * width), val) < 0) - return errno = EILSEQ, -1; - } - } else if (json_is_object(doc)) { - tot = 1; - - if (fn(array, doc) < 0) - return errno = EILSEQ, -1; - } else - return errno = EINVAL, -1; - - return tot; -} - -json_t * -job_to(const struct job *jobs, size_t jobsz) -{ - assert(jobs); - - return to(jobs, jobsz, sizeof (*jobs), (packer)job_packer); -} - -ssize_t -job_from(struct job *jobs, size_t jobsz, json_t *doc) -{ - assert(jobs); - assert(doc); - - return from(jobs, jobsz, sizeof (*jobs), doc, (unpacker)job_unpacker); -} - -json_t * -jobresult_to(const struct jobresult *res, size_t resz) -{ - assert(res); - - return to(res, resz, sizeof (*res), (packer)jobresult_packer); -} - -ssize_t -jobresult_from(struct jobresult *res, size_t resz, json_t *doc) -{ - assert(res); - assert(doc); - - return from(res, resz, sizeof (*res), doc, (unpacker)jobresult_unpacker); -} - -json_t * -worker_to(const struct worker *w, size_t wsz) -{ - assert(w); - - return to(w, wsz, sizeof (*w), (packer)worker_packer); -} - -ssize_t -worker_from(struct worker *w, size_t wsz, json_t *doc) -{ - assert(w); - assert(doc); - - return from(w, wsz, sizeof (*w), doc, (unpacker)worker_unpacker); -} - -json_t * -project_to(const struct project *proj, size_t projsz) -{ - assert(proj); - - return to(proj, projsz, sizeof (*proj), (packer)project_packer); -} - -ssize_t -project_from(struct project *proj, size_t projsz, json_t *doc) -{ - assert(proj); - assert(doc); - - return from(proj, projsz, sizeof (*proj), doc, (unpacker)project_unpacker); -}
--- a/types.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/* - * types.h -- type definitions and conversions - * - * 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. - */ - -#ifndef SCI_TYPES_H -#define SCI_TYPES_H - -#include <sys/types.h> -#include <stddef.h> - -#include <jansson.h> - -struct job { - int id; - int project_id; - const char *tag; -}; - -struct jobresult { - int id; - int job_id; - int worker_id; - int exitcode; - const char *log; -}; - -struct worker { - int id; - const char *name; - const char *desc; -}; - -struct project { - int id; - const char *name; - const char *desc; - const char *url; - const char *script; -}; - -json_t * -job_to(const struct job *, size_t); - -ssize_t -job_from(struct job *, size_t, json_t *); - -json_t * -jobresult_to(const struct jobresult *, size_t); - -ssize_t -jobresult_from(struct jobresult *, size_t, json_t *); - -json_t * -worker_to(const struct worker *, size_t); - -ssize_t -worker_from(struct worker *, size_t, json_t *); - -json_t * -project_to(const struct project *, size_t); - -ssize_t -project_from(struct project *, size_t, json_t *); - -#endif /* !SCI_TYPES_H */
--- a/util.c Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -/* - * util.c -- miscellaneous utilities - * - * 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/stat.h> -#include <assert.h> -#include <err.h> -#include <fcntl.h> -#include <libgen.h> -#include <limits.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "util.h" - -void * -util_malloc(size_t size) -{ - void *ret; - - if (!(ret = malloc(size))) - err(1, "malloc"); - - return ret; -} - -void * -util_calloc(size_t n, size_t size) -{ - void *ret; - - if (!(ret = calloc(n, size))) - err(1, "calloc"); - - return ret; -} - -void * -util_realloc(void *ptr, size_t size) -{ - void *ret; - - if (!(ret = realloc(ptr, size)) && size) - err(1, "realloc"); - - return ret; -} - -void * -util_memdup(const void *ptr, size_t size) -{ - void *ret; - - if (!(ret = malloc(size))) - err(1, "malloc"); - - return memcpy(ret, ptr, size); -} - -char * -util_strdup(const char *src) -{ - char *ret; - - if (!(ret = strdup(src))) - err(1, "strdup"); - - return ret; -} - -char * -util_strndup(const char *src, size_t n) -{ - assert(src); - - char *ret; - - if (!(ret = strndup(src, n))) - err(1, "strndup"); - - return ret; -} - -char * -util_basename(const char *str) -{ - static char ret[PATH_MAX]; - char tmp[PATH_MAX]; - - strlcpy(tmp, str, sizeof (tmp)); - strlcpy(ret, basename(tmp), sizeof (ret)); - - return ret; -} - -char * -util_dirname(const char *str) -{ - static char ret[PATH_MAX]; - char tmp[PATH_MAX]; - - strlcpy(tmp, str, sizeof (tmp)); - strlcpy(ret, dirname(tmp), sizeof (ret)); - - return ret; -} - -FILE * -util_fmemopen(void *buf, size_t size, const char *mode) -{ - FILE *fp; - - if (!(fp = fmemopen(buf, size, mode))) - err(1, "fmemopen"); - - return fp; -} - -char * -util_printf(char *buf, size_t bufsz, const char *fmt, ...) -{ - assert(buf); - assert(bufsz); - assert(fmt); - - va_list ap; - - va_start(ap, fmt); - vsnprintf(buf, bufsz, fmt, ap); - va_end(ap); - - return buf; -} - -char * -util_read(const char *path) -{ - int fd; - struct stat st; - char *ret; - - if ((fd = open(path, O_RDONLY)) < 0) - return NULL; - if (fstat(fd, &st) < 0) - return close(fd), NULL; - - ret = util_calloc(1, st.st_size + 1); - - if (read(fd, ret, st.st_size) != st.st_size) { - free(ret); - ret = NULL; - } - - close(fd); - - return ret; -} - -const char * -util_path(const char *filename) -{ - assert(filename); - - /* Build path to the template file. */ - static char path[PATH_MAX]; - - //snprintf(path, sizeof (path), "%s/%s", config.themedir, filename); - - return path; -}
--- a/util.h Wed Oct 06 16:22:23 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/* - * util.h -- miscellaneous utilities - * - * 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. - */ - -#ifndef SCI_UTIL_H -#define SCI_UTIL_H - -#include <stddef.h> -#include <stdio.h> - -#define UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) - -void * -util_malloc(size_t); - -void * -util_calloc(size_t, size_t); - -void * -util_realloc(void *, size_t); - -void * -util_reallocarray(void *, size_t, size_t); - -void * -util_memdup(const void *, size_t); - -char * -util_strdup(const char *); - -char * -util_strndup(const char *, size_t); - -char * -util_basename(const char *); - -char * -util_dirname(const char *); - -FILE * -util_fmemopen(void *, size_t, const char *); - -char * -util_printf(char *, size_t, const char *, ...); - -char * -util_read(const char *); - -const char * -util_path(const char *); - -#endif /* !SCI_UTIL_H */