# HG changeset patch # User David Demelier # Date 1657876308 -7200 # Node ID de4bf839b56500f0a0c0b9093b6591ba39470247 # Parent 600204c31bf089e9a9c2115275a8cb0a5363151b misc: revamp SQL diff -r 600204c31bf0 -r de4bf839b565 .hgignore --- a/.hgignore Tue Jul 12 20:20:51 2022 +0200 +++ b/.hgignore Fri Jul 15 11:11:48 2022 +0200 @@ -17,9 +17,12 @@ # macOS specific. \.DS_Store$ +\.dSYM.* # executables. ^bcc$ +^scid/scid$ +^sciworkerd/sciworkerd$ # tests. ^tests/test-db$ diff -r 600204c31bf0 -r de4bf839b565 Makefile --- a/Makefile Tue Jul 12 20:20:51 2022 +0200 +++ b/Makefile Fri Jul 15 11:11:48 2022 +0200 @@ -20,7 +20,10 @@ include config.mk -LIBSCI_SRCS= lib/log.c \ +LIBSCI_SRCS= extern/libsqlite/sqlite3.c \ + lib/apic.c \ + lib/db.c \ + lib/log.c \ lib/types.c \ lib/util.c LIBSCI_OBJS= ${LIBSCI_SRCS:.c=.o} @@ -40,9 +43,7 @@ sql/worker-find-id.h \ sql/worker-list.h -SCID_SRCS= extern/libsqlite/sqlite3.c \ - scid/db.c \ - scid/http.c \ +SCID_SRCS= scid/http.c \ scid/main.c \ scid/page-api-jobs.c \ scid/page-api-projects.c \ @@ -51,7 +52,9 @@ SCID_OBJS= ${SCID_SRCS:.c=.o} SCID_DEPS= ${SCID_SRCS:.c=.d} -SCIWORKERD_SRCS= sciworkerd/main.c sciworkerd/task.c +SCIWORKERD_SRCS= sciworkerd/main.c \ + sciworkerd/sciworkerd.c \ + sciworkerd/task.c SCIWORKERD_OBJS= ${SCIWORKERD_SRCS:.c=.o} SCIWORKERD_DEPS= ${SCIWORKERD_SRCS:.c=.d} @@ -63,9 +66,6 @@ TESTS_OBJS= ${TESTS:.c=} TESTS_DEPS= ${TESTS:.c=.d} -LIBBSD_INCS= `pkg-config --silence-errors --cflags libbsd-overlay` -LIBBSD_LIBS= `pkg-config --silence-errors --libs libbsd-overlay` - LIBCURL_INCS= `pkg-config --cflags libcurl` LIBCURL_LIBS= `pkg-config --libs libcurl` @@ -76,6 +76,7 @@ KCGI_LIBS= `pkg-config --libs kcgi` INCS= -Iextern/libsqlite \ + -Iextern/libutlist \ -Iextern/libgreatest \ -Ilib \ -I. @@ -93,11 +94,11 @@ # for unit tests. .c: - ${CC} ${INCS} ${DEFS} ${LIBBSD_INCS} ${KCGI_INCS} ${JANSSON_INCS} \ - ${CFLAGS} -MMD $< -o $@ libsci.a ${LIBBSD_LIBS} ${JANSSON_LIBS} + ${CC} ${INCS} ${DEFS} ${KCGI_INCS} ${JANSSON_INCS} \ + ${CFLAGS} -MMD $< -o $@ lib/libsci.a ${JANSSON_LIBS} .c.o: - ${CC} ${INCS} ${DEFS} ${LIBBSD_INCS} ${KCGI_INCS} ${JANSSON_INCS} \ + ${CC} ${INCS} ${DEFS} ${KCGI_INCS} ${JANSSON_INCS} \ ${CFLAGS} -MMD -c $< -o $@ .in: @@ -147,6 +148,8 @@ rm -f sciworkerd/sciworkerd ${SCIWORKERD_OBJS} ${SCIWORKERD_DEPS} rm -f ${TESTS_OBJS} ${TESTS_DEPS} +${TESTS_OBJS}: lib/libsci.a + tests: lib/libsci.a ${TESTS_OBJS} for t in ${TESTS_OBJS}; do $$t -v; done diff -r 600204c31bf0 -r de4bf839b565 config.def.h --- a/config.def.h Tue Jul 12 20:20:51 2022 +0200 +++ b/config.def.h Fri Jul 15 11:11:48 2022 +0200 @@ -19,10 +19,6 @@ #ifndef SCI_CONFIG_H #define SCI_CONFIG_H -/* I/O limits */ -#define SCI_CONSOLE_MAX 4194304 /* Build log max (4MB) */ -#define SCI_MSG_MAX (SCI_CONSOLE_MAX + 1024) /* Network message max. */ - /* Database limits. */ #define SCI_PROJECT_MAX 64 /* Projects allowed in database. */ #define SCI_WORKER_MAX 32 /* Workers allowed in database. */ @@ -30,4 +26,7 @@ /* Usage limits. */ #define SCI_JOB_LIST_MAX 128 /* Jobs max list size. */ +/* Miscellaneous limits. */ +#define SCI_URL_MAX 256 + #endif /* !SCI_CONFIG_H */ diff -r 600204c31bf0 -r de4bf839b565 config.mk --- a/config.mk Tue Jul 12 20:20:51 2022 +0200 +++ b/config.mk Fri Jul 15 11:11:48 2022 +0200 @@ -1,5 +1,5 @@ CC= cc -CFLAGS= -g -O0 -Wno-cpp +CFLAGS= -g -O0 #CFLAGS= -Wall -Wextra -fsanitize=address,undefined -g -O0 #LDFLAGS= -fsanitize=address,undefined diff -r 600204c31bf0 -r de4bf839b565 examples/irccd.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/irccd.sh Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,37 @@ +#!/bin/sh + +set -e + +readonly wrkdir="$(mktemp -d /tmp/irccd-XXXXXX)" +readonly repo="http://hg.malikania.fr/irccd" + +trap "cleanup" INT TERM EXIT + +cleanup() +{ + rm -rf $wrkdir +} + +if [ "$#" -ne 1 ]; then + echo "abort: $(basename $0) revision" 1>&2 + exit 1 +fi + +case $(uname -s) in +Darwin) + extra_args="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl" + ;; +esac + +echo "=> Cloning repository $repo (revision $1) into $wkrdir" +hg clone -r "$1" "$repo" "$wrkdir" +cd "$wrkdir" + +echo "=> Configuring CMake" +cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DIRCCD_WITH_TESTS=On $extra_args + +echo "=> Building" +cmake --build build + +echo "=> Running test suite" +cmake --build build --target test diff -r 600204c31bf0 -r de4bf839b565 extern/LICENSE.libutlist.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/LICENSE.libutlist.txt Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,21 @@ +Copyright (c) 2005-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff -r 600204c31bf0 -r de4bf839b565 extern/VERSION.libutlist.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/VERSION.libutlist.txt Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,1 @@ +2.3.0 diff -r 600204c31bf0 -r de4bf839b565 extern/libutlist/utlist.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/libutlist/utlist.h Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,1073 @@ +/* +Copyright (c) 2007-2021, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTLIST_H +#define UTLIST_H + +#define UTLIST_VERSION 2.3.0 + +#include + +/* + * This file contains macros to manipulate singly and doubly-linked lists. + * + * 1. LL_ macros: singly-linked lists. + * 2. DL_ macros: doubly-linked lists. + * 3. CDL_ macros: circular doubly-linked lists. + * + * To use singly-linked lists, your structure must have a "next" pointer. + * To use doubly-linked lists, your structure must "prev" and "next" pointers. + * Either way, the pointer to the head of the list must be initialized to NULL. + * + * ----------------.EXAMPLE ------------------------- + * struct item { + * int id; + * struct item *prev, *next; + * } + * + * struct item *list = NULL: + * + * int main() { + * struct item *item; + * ... allocate and populate item ... + * DL_APPEND(list, item); + * } + * -------------------------------------------------- + * + * For doubly-linked lists, the append and delete macros are O(1) + * For singly-linked lists, append and delete are O(n) but prepend is O(1) + * The sort macro is O(n log(n)) for all types of single/double/circular lists. + */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(LDECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define LDECLTYPE(x) decltype(x) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define LDECLTYPE(x) __typeof(x) +#endif +#endif + +/* for VS2008 we use some workarounds to get around the lack of decltype, + * namely, we always reassign our tmp variable to the list head if we need + * to dereference its prev/next pointers, and save/restore the real head.*/ +#ifdef NO_DECLTYPE +#define IF_NO_DECLTYPE(x) x +#define LDECLTYPE(x) char* +#define UTLIST_SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); } +#define UTLIST_NEXT(elt,list,next) ((char*)((list)->next)) +#define UTLIST_NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); } +/* #define UTLIST_PREV(elt,list,prev) ((char*)((list)->prev)) */ +#define UTLIST_PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); } +#define UTLIST_RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; } +#define UTLIST_CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); } +#else +#define IF_NO_DECLTYPE(x) +#define UTLIST_SV(elt,list) +#define UTLIST_NEXT(elt,list,next) ((elt)->next) +#define UTLIST_NEXTASGN(elt,list,to,next) ((elt)->next)=(to) +/* #define UTLIST_PREV(elt,list,prev) ((elt)->prev) */ +#define UTLIST_PREVASGN(elt,list,to,prev) ((elt)->prev)=(to) +#define UTLIST_RS(list) +#define UTLIST_CASTASGN(a,b) (a)=(b) +#endif + +/****************************************************************************** + * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort * + * Unwieldy variable names used here to avoid shadowing passed-in variables. * + *****************************************************************************/ +#define LL_SORT(list, cmp) \ + LL_SORT2(list, cmp, next) + +#define LL_SORT2(list, cmp, next) \ +do { \ + LDECLTYPE(list) _ls_p; \ + LDECLTYPE(list) _ls_q; \ + LDECLTYPE(list) _ls_e; \ + LDECLTYPE(list) _ls_tail; \ + IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;) \ + int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ + if (list) { \ + _ls_insize = 1; \ + _ls_looping = 1; \ + while (_ls_looping) { \ + UTLIST_CASTASGN(_ls_p,list); \ + (list) = NULL; \ + _ls_tail = NULL; \ + _ls_nmerges = 0; \ + while (_ls_p) { \ + _ls_nmerges++; \ + _ls_q = _ls_p; \ + _ls_psize = 0; \ + for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ + _ls_psize++; \ + UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); \ + if (!_ls_q) break; \ + } \ + _ls_qsize = _ls_insize; \ + while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ + if (_ls_psize == 0) { \ + _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \ + UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \ + } else if (_ls_qsize == 0 || !_ls_q) { \ + _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \ + UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \ + } else if (cmp(_ls_p,_ls_q) <= 0) { \ + _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \ + UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \ + } else { \ + _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \ + UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \ + } \ + if (_ls_tail) { \ + UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \ + } else { \ + UTLIST_CASTASGN(list,_ls_e); \ + } \ + _ls_tail = _ls_e; \ + } \ + _ls_p = _ls_q; \ + } \ + if (_ls_tail) { \ + UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list); \ + } \ + if (_ls_nmerges <= 1) { \ + _ls_looping=0; \ + } \ + _ls_insize *= 2; \ + } \ + } \ +} while (0) + + +#define DL_SORT(list, cmp) \ + DL_SORT2(list, cmp, prev, next) + +#define DL_SORT2(list, cmp, prev, next) \ +do { \ + LDECLTYPE(list) _ls_p; \ + LDECLTYPE(list) _ls_q; \ + LDECLTYPE(list) _ls_e; \ + LDECLTYPE(list) _ls_tail; \ + IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;) \ + int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ + if (list) { \ + _ls_insize = 1; \ + _ls_looping = 1; \ + while (_ls_looping) { \ + UTLIST_CASTASGN(_ls_p,list); \ + (list) = NULL; \ + _ls_tail = NULL; \ + _ls_nmerges = 0; \ + while (_ls_p) { \ + _ls_nmerges++; \ + _ls_q = _ls_p; \ + _ls_psize = 0; \ + for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ + _ls_psize++; \ + UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); \ + if (!_ls_q) break; \ + } \ + _ls_qsize = _ls_insize; \ + while ((_ls_psize > 0) || ((_ls_qsize > 0) && _ls_q)) { \ + if (_ls_psize == 0) { \ + _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \ + UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \ + } else if ((_ls_qsize == 0) || (!_ls_q)) { \ + _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \ + UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \ + } else if (cmp(_ls_p,_ls_q) <= 0) { \ + _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \ + UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \ + } else { \ + _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \ + UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \ + } \ + if (_ls_tail) { \ + UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \ + } else { \ + UTLIST_CASTASGN(list,_ls_e); \ + } \ + UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list); \ + _ls_tail = _ls_e; \ + } \ + _ls_p = _ls_q; \ + } \ + UTLIST_CASTASGN((list)->prev, _ls_tail); \ + UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list); \ + if (_ls_nmerges <= 1) { \ + _ls_looping=0; \ + } \ + _ls_insize *= 2; \ + } \ + } \ +} while (0) + +#define CDL_SORT(list, cmp) \ + CDL_SORT2(list, cmp, prev, next) + +#define CDL_SORT2(list, cmp, prev, next) \ +do { \ + LDECLTYPE(list) _ls_p; \ + LDECLTYPE(list) _ls_q; \ + LDECLTYPE(list) _ls_e; \ + LDECLTYPE(list) _ls_tail; \ + LDECLTYPE(list) _ls_oldhead; \ + LDECLTYPE(list) _tmp; \ + int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ + if (list) { \ + _ls_insize = 1; \ + _ls_looping = 1; \ + while (_ls_looping) { \ + UTLIST_CASTASGN(_ls_p,list); \ + UTLIST_CASTASGN(_ls_oldhead,list); \ + (list) = NULL; \ + _ls_tail = NULL; \ + _ls_nmerges = 0; \ + while (_ls_p) { \ + _ls_nmerges++; \ + _ls_q = _ls_p; \ + _ls_psize = 0; \ + for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ + _ls_psize++; \ + UTLIST_SV(_ls_q,list); \ + if (UTLIST_NEXT(_ls_q,list,next) == _ls_oldhead) { \ + _ls_q = NULL; \ + } else { \ + _ls_q = UTLIST_NEXT(_ls_q,list,next); \ + } \ + UTLIST_RS(list); \ + if (!_ls_q) break; \ + } \ + _ls_qsize = _ls_insize; \ + while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ + if (_ls_psize == 0) { \ + _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \ + UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \ + if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \ + } else if (_ls_qsize == 0 || !_ls_q) { \ + _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \ + UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \ + if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \ + } else if (cmp(_ls_p,_ls_q) <= 0) { \ + _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \ + UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \ + if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \ + } else { \ + _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \ + UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \ + if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \ + } \ + if (_ls_tail) { \ + UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \ + } else { \ + UTLIST_CASTASGN(list,_ls_e); \ + } \ + UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list); \ + _ls_tail = _ls_e; \ + } \ + _ls_p = _ls_q; \ + } \ + UTLIST_CASTASGN((list)->prev,_ls_tail); \ + UTLIST_CASTASGN(_tmp,list); \ + UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_tmp,next); UTLIST_RS(list); \ + if (_ls_nmerges <= 1) { \ + _ls_looping=0; \ + } \ + _ls_insize *= 2; \ + } \ + } \ +} while (0) + +/****************************************************************************** + * singly linked list macros (non-circular) * + *****************************************************************************/ +#define LL_PREPEND(head,add) \ + LL_PREPEND2(head,add,next) + +#define LL_PREPEND2(head,add,next) \ +do { \ + (add)->next = (head); \ + (head) = (add); \ +} while (0) + +#define LL_CONCAT(head1,head2) \ + LL_CONCAT2(head1,head2,next) + +#define LL_CONCAT2(head1,head2,next) \ +do { \ + LDECLTYPE(head1) _tmp; \ + if (head1) { \ + _tmp = (head1); \ + while (_tmp->next) { _tmp = _tmp->next; } \ + _tmp->next=(head2); \ + } else { \ + (head1)=(head2); \ + } \ +} while (0) + +#define LL_APPEND(head,add) \ + LL_APPEND2(head,add,next) + +#define LL_APPEND2(head,add,next) \ +do { \ + LDECLTYPE(head) _tmp; \ + (add)->next=NULL; \ + if (head) { \ + _tmp = (head); \ + while (_tmp->next) { _tmp = _tmp->next; } \ + _tmp->next=(add); \ + } else { \ + (head)=(add); \ + } \ +} while (0) + +#define LL_INSERT_INORDER(head,add,cmp) \ + LL_INSERT_INORDER2(head,add,cmp,next) + +#define LL_INSERT_INORDER2(head,add,cmp,next) \ +do { \ + LDECLTYPE(head) _tmp; \ + if (head) { \ + LL_LOWER_BOUND2(head, _tmp, add, cmp, next); \ + LL_APPEND_ELEM2(head, _tmp, add, next); \ + } else { \ + (head) = (add); \ + (head)->next = NULL; \ + } \ +} while (0) + +#define LL_LOWER_BOUND(head,elt,like,cmp) \ + LL_LOWER_BOUND2(head,elt,like,cmp,next) + +#define LL_LOWER_BOUND2(head,elt,like,cmp,next) \ + do { \ + if ((head) == NULL || (cmp(head, like)) >= 0) { \ + (elt) = NULL; \ + } else { \ + for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) { \ + if (cmp((elt)->next, like) >= 0) { \ + break; \ + } \ + } \ + } \ + } while (0) + +#define LL_DELETE(head,del) \ + LL_DELETE2(head,del,next) + +#define LL_DELETE2(head,del,next) \ +do { \ + LDECLTYPE(head) _tmp; \ + if ((head) == (del)) { \ + (head)=(head)->next; \ + } else { \ + _tmp = (head); \ + while (_tmp->next && (_tmp->next != (del))) { \ + _tmp = _tmp->next; \ + } \ + if (_tmp->next) { \ + _tmp->next = (del)->next; \ + } \ + } \ +} while (0) + +#define LL_COUNT(head,el,counter) \ + LL_COUNT2(head,el,counter,next) \ + +#define LL_COUNT2(head,el,counter,next) \ +do { \ + (counter) = 0; \ + LL_FOREACH2(head,el,next) { ++(counter); } \ +} while (0) + +#define LL_FOREACH(head,el) \ + LL_FOREACH2(head,el,next) + +#define LL_FOREACH2(head,el,next) \ + for ((el) = (head); el; (el) = (el)->next) + +#define LL_FOREACH_SAFE(head,el,tmp) \ + LL_FOREACH_SAFE2(head,el,tmp,next) + +#define LL_FOREACH_SAFE2(head,el,tmp,next) \ + for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp)) + +#define LL_SEARCH_SCALAR(head,out,field,val) \ + LL_SEARCH_SCALAR2(head,out,field,val,next) + +#define LL_SEARCH_SCALAR2(head,out,field,val,next) \ +do { \ + LL_FOREACH2(head,out,next) { \ + if ((out)->field == (val)) break; \ + } \ +} while (0) + +#define LL_SEARCH(head,out,elt,cmp) \ + LL_SEARCH2(head,out,elt,cmp,next) + +#define LL_SEARCH2(head,out,elt,cmp,next) \ +do { \ + LL_FOREACH2(head,out,next) { \ + if ((cmp(out,elt))==0) break; \ + } \ +} while (0) + +#define LL_REPLACE_ELEM2(head, el, add, next) \ +do { \ + LDECLTYPE(head) _tmp; \ + assert((head) != NULL); \ + assert((el) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el)->next; \ + if ((head) == (el)) { \ + (head) = (add); \ + } else { \ + _tmp = (head); \ + while (_tmp->next && (_tmp->next != (el))) { \ + _tmp = _tmp->next; \ + } \ + if (_tmp->next) { \ + _tmp->next = (add); \ + } \ + } \ +} while (0) + +#define LL_REPLACE_ELEM(head, el, add) \ + LL_REPLACE_ELEM2(head, el, add, next) + +#define LL_PREPEND_ELEM2(head, el, add, next) \ +do { \ + if (el) { \ + LDECLTYPE(head) _tmp; \ + assert((head) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el); \ + if ((head) == (el)) { \ + (head) = (add); \ + } else { \ + _tmp = (head); \ + while (_tmp->next && (_tmp->next != (el))) { \ + _tmp = _tmp->next; \ + } \ + if (_tmp->next) { \ + _tmp->next = (add); \ + } \ + } \ + } else { \ + LL_APPEND2(head, add, next); \ + } \ +} while (0) \ + +#define LL_PREPEND_ELEM(head, el, add) \ + LL_PREPEND_ELEM2(head, el, add, next) + +#define LL_APPEND_ELEM2(head, el, add, next) \ +do { \ + if (el) { \ + assert((head) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el)->next; \ + (el)->next = (add); \ + } else { \ + LL_PREPEND2(head, add, next); \ + } \ +} while (0) \ + +#define LL_APPEND_ELEM(head, el, add) \ + LL_APPEND_ELEM2(head, el, add, next) + +#ifdef NO_DECLTYPE +/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */ + +#undef LL_CONCAT2 +#define LL_CONCAT2(head1,head2,next) \ +do { \ + char *_tmp; \ + if (head1) { \ + _tmp = (char*)(head1); \ + while ((head1)->next) { (head1) = (head1)->next; } \ + (head1)->next = (head2); \ + UTLIST_RS(head1); \ + } else { \ + (head1)=(head2); \ + } \ +} while (0) + +#undef LL_APPEND2 +#define LL_APPEND2(head,add,next) \ +do { \ + if (head) { \ + (add)->next = head; /* use add->next as a temp variable */ \ + while ((add)->next->next) { (add)->next = (add)->next->next; } \ + (add)->next->next=(add); \ + } else { \ + (head)=(add); \ + } \ + (add)->next=NULL; \ +} while (0) + +#undef LL_INSERT_INORDER2 +#define LL_INSERT_INORDER2(head,add,cmp,next) \ +do { \ + if ((head) == NULL || (cmp(head, add)) >= 0) { \ + (add)->next = (head); \ + (head) = (add); \ + } else { \ + char *_tmp = (char*)(head); \ + while ((head)->next != NULL && (cmp((head)->next, add)) < 0) { \ + (head) = (head)->next; \ + } \ + (add)->next = (head)->next; \ + (head)->next = (add); \ + UTLIST_RS(head); \ + } \ +} while (0) + +#undef LL_DELETE2 +#define LL_DELETE2(head,del,next) \ +do { \ + if ((head) == (del)) { \ + (head)=(head)->next; \ + } else { \ + char *_tmp = (char*)(head); \ + while ((head)->next && ((head)->next != (del))) { \ + (head) = (head)->next; \ + } \ + if ((head)->next) { \ + (head)->next = ((del)->next); \ + } \ + UTLIST_RS(head); \ + } \ +} while (0) + +#undef LL_REPLACE_ELEM2 +#define LL_REPLACE_ELEM2(head, el, add, next) \ +do { \ + assert((head) != NULL); \ + assert((el) != NULL); \ + assert((add) != NULL); \ + if ((head) == (el)) { \ + (head) = (add); \ + } else { \ + (add)->next = head; \ + while ((add)->next->next && ((add)->next->next != (el))) { \ + (add)->next = (add)->next->next; \ + } \ + if ((add)->next->next) { \ + (add)->next->next = (add); \ + } \ + } \ + (add)->next = (el)->next; \ +} while (0) + +#undef LL_PREPEND_ELEM2 +#define LL_PREPEND_ELEM2(head, el, add, next) \ +do { \ + if (el) { \ + assert((head) != NULL); \ + assert((add) != NULL); \ + if ((head) == (el)) { \ + (head) = (add); \ + } else { \ + (add)->next = (head); \ + while ((add)->next->next && ((add)->next->next != (el))) { \ + (add)->next = (add)->next->next; \ + } \ + if ((add)->next->next) { \ + (add)->next->next = (add); \ + } \ + } \ + (add)->next = (el); \ + } else { \ + LL_APPEND2(head, add, next); \ + } \ +} while (0) \ + +#endif /* NO_DECLTYPE */ + +/****************************************************************************** + * doubly linked list macros (non-circular) * + *****************************************************************************/ +#define DL_PREPEND(head,add) \ + DL_PREPEND2(head,add,prev,next) + +#define DL_PREPEND2(head,add,prev,next) \ +do { \ + (add)->next = (head); \ + if (head) { \ + (add)->prev = (head)->prev; \ + (head)->prev = (add); \ + } else { \ + (add)->prev = (add); \ + } \ + (head) = (add); \ +} while (0) + +#define DL_APPEND(head,add) \ + DL_APPEND2(head,add,prev,next) + +#define DL_APPEND2(head,add,prev,next) \ +do { \ + if (head) { \ + (add)->prev = (head)->prev; \ + (head)->prev->next = (add); \ + (head)->prev = (add); \ + (add)->next = NULL; \ + } else { \ + (head)=(add); \ + (head)->prev = (head); \ + (head)->next = NULL; \ + } \ +} while (0) + +#define DL_INSERT_INORDER(head,add,cmp) \ + DL_INSERT_INORDER2(head,add,cmp,prev,next) + +#define DL_INSERT_INORDER2(head,add,cmp,prev,next) \ +do { \ + LDECLTYPE(head) _tmp; \ + if (head) { \ + DL_LOWER_BOUND2(head, _tmp, add, cmp, next); \ + DL_APPEND_ELEM2(head, _tmp, add, prev, next); \ + } else { \ + (head) = (add); \ + (head)->prev = (head); \ + (head)->next = NULL; \ + } \ +} while (0) + +#define DL_LOWER_BOUND(head,elt,like,cmp) \ + DL_LOWER_BOUND2(head,elt,like,cmp,next) + +#define DL_LOWER_BOUND2(head,elt,like,cmp,next) \ +do { \ + if ((head) == NULL || (cmp(head, like)) >= 0) { \ + (elt) = NULL; \ + } else { \ + for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) { \ + if ((cmp((elt)->next, like)) >= 0) { \ + break; \ + } \ + } \ + } \ +} while (0) + +#define DL_CONCAT(head1,head2) \ + DL_CONCAT2(head1,head2,prev,next) + +#define DL_CONCAT2(head1,head2,prev,next) \ +do { \ + LDECLTYPE(head1) _tmp; \ + if (head2) { \ + if (head1) { \ + UTLIST_CASTASGN(_tmp, (head2)->prev); \ + (head2)->prev = (head1)->prev; \ + (head1)->prev->next = (head2); \ + UTLIST_CASTASGN((head1)->prev, _tmp); \ + } else { \ + (head1)=(head2); \ + } \ + } \ +} while (0) + +#define DL_DELETE(head,del) \ + DL_DELETE2(head,del,prev,next) + +#define DL_DELETE2(head,del,prev,next) \ +do { \ + assert((head) != NULL); \ + assert((del)->prev != NULL); \ + if ((del)->prev == (del)) { \ + (head)=NULL; \ + } else if ((del)==(head)) { \ + (del)->next->prev = (del)->prev; \ + (head) = (del)->next; \ + } else { \ + (del)->prev->next = (del)->next; \ + if ((del)->next) { \ + (del)->next->prev = (del)->prev; \ + } else { \ + (head)->prev = (del)->prev; \ + } \ + } \ +} while (0) + +#define DL_COUNT(head,el,counter) \ + DL_COUNT2(head,el,counter,next) \ + +#define DL_COUNT2(head,el,counter,next) \ +do { \ + (counter) = 0; \ + DL_FOREACH2(head,el,next) { ++(counter); } \ +} while (0) + +#define DL_FOREACH(head,el) \ + DL_FOREACH2(head,el,next) + +#define DL_FOREACH2(head,el,next) \ + for ((el) = (head); el; (el) = (el)->next) + +/* this version is safe for deleting the elements during iteration */ +#define DL_FOREACH_SAFE(head,el,tmp) \ + DL_FOREACH_SAFE2(head,el,tmp,next) + +#define DL_FOREACH_SAFE2(head,el,tmp,next) \ + for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp)) + +/* these are identical to their singly-linked list counterparts */ +#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR +#define DL_SEARCH LL_SEARCH +#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2 +#define DL_SEARCH2 LL_SEARCH2 + +#define DL_REPLACE_ELEM2(head, el, add, prev, next) \ +do { \ + assert((head) != NULL); \ + assert((el) != NULL); \ + assert((add) != NULL); \ + if ((head) == (el)) { \ + (head) = (add); \ + (add)->next = (el)->next; \ + if ((el)->next == NULL) { \ + (add)->prev = (add); \ + } else { \ + (add)->prev = (el)->prev; \ + (add)->next->prev = (add); \ + } \ + } else { \ + (add)->next = (el)->next; \ + (add)->prev = (el)->prev; \ + (add)->prev->next = (add); \ + if ((el)->next == NULL) { \ + (head)->prev = (add); \ + } else { \ + (add)->next->prev = (add); \ + } \ + } \ +} while (0) + +#define DL_REPLACE_ELEM(head, el, add) \ + DL_REPLACE_ELEM2(head, el, add, prev, next) + +#define DL_PREPEND_ELEM2(head, el, add, prev, next) \ +do { \ + if (el) { \ + assert((head) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el); \ + (add)->prev = (el)->prev; \ + (el)->prev = (add); \ + if ((head) == (el)) { \ + (head) = (add); \ + } else { \ + (add)->prev->next = (add); \ + } \ + } else { \ + DL_APPEND2(head, add, prev, next); \ + } \ +} while (0) \ + +#define DL_PREPEND_ELEM(head, el, add) \ + DL_PREPEND_ELEM2(head, el, add, prev, next) + +#define DL_APPEND_ELEM2(head, el, add, prev, next) \ +do { \ + if (el) { \ + assert((head) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el)->next; \ + (add)->prev = (el); \ + (el)->next = (add); \ + if ((add)->next) { \ + (add)->next->prev = (add); \ + } else { \ + (head)->prev = (add); \ + } \ + } else { \ + DL_PREPEND2(head, add, prev, next); \ + } \ +} while (0) \ + +#define DL_APPEND_ELEM(head, el, add) \ + DL_APPEND_ELEM2(head, el, add, prev, next) + +#ifdef NO_DECLTYPE +/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */ + +#undef DL_INSERT_INORDER2 +#define DL_INSERT_INORDER2(head,add,cmp,prev,next) \ +do { \ + if ((head) == NULL) { \ + (add)->prev = (add); \ + (add)->next = NULL; \ + (head) = (add); \ + } else if ((cmp(head, add)) >= 0) { \ + (add)->prev = (head)->prev; \ + (add)->next = (head); \ + (head)->prev = (add); \ + (head) = (add); \ + } else { \ + char *_tmp = (char*)(head); \ + while ((head)->next && (cmp((head)->next, add)) < 0) { \ + (head) = (head)->next; \ + } \ + (add)->prev = (head); \ + (add)->next = (head)->next; \ + (head)->next = (add); \ + UTLIST_RS(head); \ + if ((add)->next) { \ + (add)->next->prev = (add); \ + } else { \ + (head)->prev = (add); \ + } \ + } \ +} while (0) +#endif /* NO_DECLTYPE */ + +/****************************************************************************** + * circular doubly linked list macros * + *****************************************************************************/ +#define CDL_APPEND(head,add) \ + CDL_APPEND2(head,add,prev,next) + +#define CDL_APPEND2(head,add,prev,next) \ +do { \ + if (head) { \ + (add)->prev = (head)->prev; \ + (add)->next = (head); \ + (head)->prev = (add); \ + (add)->prev->next = (add); \ + } else { \ + (add)->prev = (add); \ + (add)->next = (add); \ + (head) = (add); \ + } \ +} while (0) + +#define CDL_PREPEND(head,add) \ + CDL_PREPEND2(head,add,prev,next) + +#define CDL_PREPEND2(head,add,prev,next) \ +do { \ + if (head) { \ + (add)->prev = (head)->prev; \ + (add)->next = (head); \ + (head)->prev = (add); \ + (add)->prev->next = (add); \ + } else { \ + (add)->prev = (add); \ + (add)->next = (add); \ + } \ + (head) = (add); \ +} while (0) + +#define CDL_INSERT_INORDER(head,add,cmp) \ + CDL_INSERT_INORDER2(head,add,cmp,prev,next) + +#define CDL_INSERT_INORDER2(head,add,cmp,prev,next) \ +do { \ + LDECLTYPE(head) _tmp; \ + if (head) { \ + CDL_LOWER_BOUND2(head, _tmp, add, cmp, next); \ + CDL_APPEND_ELEM2(head, _tmp, add, prev, next); \ + } else { \ + (head) = (add); \ + (head)->next = (head); \ + (head)->prev = (head); \ + } \ +} while (0) + +#define CDL_LOWER_BOUND(head,elt,like,cmp) \ + CDL_LOWER_BOUND2(head,elt,like,cmp,next) + +#define CDL_LOWER_BOUND2(head,elt,like,cmp,next) \ +do { \ + if ((head) == NULL || (cmp(head, like)) >= 0) { \ + (elt) = NULL; \ + } else { \ + for ((elt) = (head); (elt)->next != (head); (elt) = (elt)->next) { \ + if ((cmp((elt)->next, like)) >= 0) { \ + break; \ + } \ + } \ + } \ +} while (0) + +#define CDL_DELETE(head,del) \ + CDL_DELETE2(head,del,prev,next) + +#define CDL_DELETE2(head,del,prev,next) \ +do { \ + if (((head)==(del)) && ((head)->next == (head))) { \ + (head) = NULL; \ + } else { \ + (del)->next->prev = (del)->prev; \ + (del)->prev->next = (del)->next; \ + if ((del) == (head)) (head)=(del)->next; \ + } \ +} while (0) + +#define CDL_COUNT(head,el,counter) \ + CDL_COUNT2(head,el,counter,next) \ + +#define CDL_COUNT2(head, el, counter,next) \ +do { \ + (counter) = 0; \ + CDL_FOREACH2(head,el,next) { ++(counter); } \ +} while (0) + +#define CDL_FOREACH(head,el) \ + CDL_FOREACH2(head,el,next) + +#define CDL_FOREACH2(head,el,next) \ + for ((el)=(head);el;(el)=(((el)->next==(head)) ? NULL : (el)->next)) + +#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \ + CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) + +#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) \ + for ((el) = (head), (tmp1) = (head) ? (head)->prev : NULL; \ + (el) && ((tmp2) = (el)->next, 1); \ + (el) = ((el) == (tmp1) ? NULL : (tmp2))) + +#define CDL_SEARCH_SCALAR(head,out,field,val) \ + CDL_SEARCH_SCALAR2(head,out,field,val,next) + +#define CDL_SEARCH_SCALAR2(head,out,field,val,next) \ +do { \ + CDL_FOREACH2(head,out,next) { \ + if ((out)->field == (val)) break; \ + } \ +} while (0) + +#define CDL_SEARCH(head,out,elt,cmp) \ + CDL_SEARCH2(head,out,elt,cmp,next) + +#define CDL_SEARCH2(head,out,elt,cmp,next) \ +do { \ + CDL_FOREACH2(head,out,next) { \ + if ((cmp(out,elt))==0) break; \ + } \ +} while (0) + +#define CDL_REPLACE_ELEM2(head, el, add, prev, next) \ +do { \ + assert((head) != NULL); \ + assert((el) != NULL); \ + assert((add) != NULL); \ + if ((el)->next == (el)) { \ + (add)->next = (add); \ + (add)->prev = (add); \ + (head) = (add); \ + } else { \ + (add)->next = (el)->next; \ + (add)->prev = (el)->prev; \ + (add)->next->prev = (add); \ + (add)->prev->next = (add); \ + if ((head) == (el)) { \ + (head) = (add); \ + } \ + } \ +} while (0) + +#define CDL_REPLACE_ELEM(head, el, add) \ + CDL_REPLACE_ELEM2(head, el, add, prev, next) + +#define CDL_PREPEND_ELEM2(head, el, add, prev, next) \ +do { \ + if (el) { \ + assert((head) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el); \ + (add)->prev = (el)->prev; \ + (el)->prev = (add); \ + (add)->prev->next = (add); \ + if ((head) == (el)) { \ + (head) = (add); \ + } \ + } else { \ + CDL_APPEND2(head, add, prev, next); \ + } \ +} while (0) + +#define CDL_PREPEND_ELEM(head, el, add) \ + CDL_PREPEND_ELEM2(head, el, add, prev, next) + +#define CDL_APPEND_ELEM2(head, el, add, prev, next) \ +do { \ + if (el) { \ + assert((head) != NULL); \ + assert((add) != NULL); \ + (add)->next = (el)->next; \ + (add)->prev = (el); \ + (el)->next = (add); \ + (add)->next->prev = (add); \ + } else { \ + CDL_PREPEND2(head, add, prev, next); \ + } \ +} while (0) + +#define CDL_APPEND_ELEM(head, el, add) \ + CDL_APPEND_ELEM2(head, el, add, prev, next) + +#ifdef NO_DECLTYPE +/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */ + +#undef CDL_INSERT_INORDER2 +#define CDL_INSERT_INORDER2(head,add,cmp,prev,next) \ +do { \ + if ((head) == NULL) { \ + (add)->prev = (add); \ + (add)->next = (add); \ + (head) = (add); \ + } else if ((cmp(head, add)) >= 0) { \ + (add)->prev = (head)->prev; \ + (add)->next = (head); \ + (add)->prev->next = (add); \ + (head)->prev = (add); \ + (head) = (add); \ + } else { \ + char *_tmp = (char*)(head); \ + while ((char*)(head)->next != _tmp && (cmp((head)->next, add)) < 0) { \ + (head) = (head)->next; \ + } \ + (add)->prev = (head); \ + (add)->next = (head)->next; \ + (add)->next->prev = (add); \ + (head)->next = (add); \ + UTLIST_RS(head); \ + } \ +} while (0) +#endif /* NO_DECLTYPE */ + +#endif /* UTLIST_H */ diff -r 600204c31bf0 -r de4bf839b565 lib/apic.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/apic.c Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include + +#include + +#include "apic.h" +#include "util.h" + +struct curlpack { + CURL *curl; + CURLcode code; + struct curl_slist *headers; +}; + +static size_t +writer(char *in, size_t n, size_t w, FILE *fp) +{ + if (fwrite(in, n, w, fp) != w) + return 0; + + return w; +} + +static inline char * +create_url(const char *fmt, va_list args) +{ + static _Thread_local char ret[256]; + va_list ap; + + ret[0] = 0; + va_copy(ap, args); + vsnprintf(ret, sizeof (ret), fmt, ap); + va_end(ap); + + return ret; +} + +static inline FILE * +create_file(char **buf, size_t *bufsz) +{ + FILE *fp; + + *buf = NULL; + *bufsz = 0; + + if (!(fp = open_memstream(buf, bufsz))) + util_die("open_memstream: %s\n", strerror(errno)); + + return fp; +} + +static struct curlpack +create_curl(FILE *fp, const char *body, const char *url) +{ + struct curlpack pack = {0}; + + pack.headers = curl_slist_append(pack.headers, "Content-Type: application/json"); + pack.curl = curl_easy_init(); + curl_easy_setopt(pack.curl, CURLOPT_HTTPHEADER, pack.headers); + curl_easy_setopt(pack.curl, CURLOPT_URL, url); + curl_easy_setopt(pack.curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(pack.curl, CURLOPT_TIMEOUT, 3L); + curl_easy_setopt(pack.curl, CURLOPT_WRITEFUNCTION, writer); + curl_easy_setopt(pack.curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(pack.curl, CURLOPT_NOSIGNAL, 1L); + + /* Assume POST request if there is a body. */ + if (body) { + curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDS, body); + curl_easy_setopt(pack.curl, CURLOPT_POSTFIELDSIZE, strlen(body)); + } + + pack.code = curl_easy_perform(pack.curl); + + return pack; +} + +static int +perform(struct apicreq *req, const char *body, const char *fmt, va_list ap) +{ + FILE *fp; + char *response, *url; + size_t responsesz; + json_error_t error; + int ret = -1; + struct curlpack curl; + + memset(req, 0, sizeof (*req)); + + url = create_url(fmt, ap); + fp = create_file(&response, &responsesz); + curl = create_curl(fp, body, url); + + /* Perform that request now. */ + fclose(fp); + + if (curl.code != CURLE_OK) + snprintf(req->error, sizeof (req->error), "%s", curl_easy_strerror(curl.code)); + else { + curl_easy_getinfo(curl.curl, CURLINFO_RESPONSE_CODE, &req->status); + + if (req->status != 200) + snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status); + if (response[0] && !(req->doc = json_loads(response, 0, &error))) + snprintf(req->error, sizeof (req->error), "JSON parse error: %s", error.text); + else + ret = 0; + } + + curl_easy_cleanup(curl.curl); + curl_slist_free_all(curl.headers); + + free(response); + + return ret; +} + +int +apic_get(struct apicreq *req, const char *fmt, ...) +{ + assert(req); + assert(fmt); + + va_list ap; + int ret; + + va_start(ap, fmt); + ret = perform(req, NULL, fmt, ap); + va_end(ap); + + return ret; +} + +int +apic_post(struct apicreq *req, const json_t *doc, const char *fmt, ...) +{ + assert(req); + assert(fmt); + + va_list ap; + int ret; + char *body; + + if (!(body = json_dumps(doc, JSON_COMPACT))) + util_die("%s", strerror(ENOMEM)); + + va_start(ap, fmt); + ret = perform(req, body, fmt, ap); + va_end(ap); + + free(body); + + return ret; +} diff -r 600204c31bf0 -r de4bf839b565 lib/apic.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/apic.h Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,20 @@ +#ifndef SCI_APIC_H +#define SCI_APIC_H + +#include + +#define APIC_ERR_MAX 128 + +struct apicreq { + json_t *doc; + char error[APIC_ERR_MAX]; + long status; +}; + +int +apic_get(struct apicreq *req, const char *url, ...); + +int +apic_post(struct apicreq *req, const json_t *body, const char *url, ...); + +#endif /* !SCI_APIC_H */ diff -r 600204c31bf0 -r de4bf839b565 lib/db.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/db.c Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,404 @@ +/* + * db.c -- scid database access + * + * Copyright (c) 2021 David Demelier + * + * 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 +#include +#include + +#include + +#include + +#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; + struct str *next; +}; + +struct list { + unpacker unpack; + void *data; + size_t datasz; + size_t elemwidth; + struct db_ctx *ctx; +}; + +static const char * +strlist_add(struct db_ctx *ctx, const char *text) +{ + struct str *s, *list = ctx->handle; + + s = util_calloc(1, sizeof (*s)); + s->str = util_strdup(text); + LL_APPEND(list, s); + + return s->str; +} + +static void +strlist_free(struct db_ctx *ctx) +{ + struct str *s, *tmp, *list = ctx->handle; + + LL_FOREACH_SAFE(list, s, tmp) { + free(s->str); + free(s); + } + + ctx->handle = NULL; +} + +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, CHAR(sqlite3_column_text(stmt, 1))); + project->desc = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 2))); + project->url = strlist_add(ctx, CHAR(sqlite3_column_text(stmt, 3))); + project->script = strlist_add(ctx, 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, CHAR(sqlite3_column_text(stmt, 1))); + w->desc = strlist_add(ctx, 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, 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); + + 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; + } +} diff -r 600204c31bf0 -r de4bf839b565 lib/db.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/db.h Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,79 @@ +/* + * db.h -- scid database access + * + * Copyright (c) 2021 David Demelier + * + * 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 +#include + +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 */ diff -r 600204c31bf0 -r de4bf839b565 lib/types.h --- a/lib/types.h Tue Jul 12 20:20:51 2022 +0200 +++ b/lib/types.h Fri Jul 15 11:11:48 2022 +0200 @@ -21,17 +21,18 @@ #include #include +#include #include struct job { - int id; + intmax_t id; int project_id; const char *tag; }; struct jobresult { - int id; + intmax_t id; int job_id; int worker_id; int exitcode; @@ -39,13 +40,13 @@ }; struct worker { - int id; + intmax_t id; const char *name; const char *desc; }; struct project { - int id; + intmax_t id; const char *name; const char *desc; const char *url; diff -r 600204c31bf0 -r de4bf839b565 lib/util.c --- a/lib/util.c Tue Jul 12 20:20:51 2022 +0200 +++ b/lib/util.c Fri Jul 15 11:11:48 2022 +0200 @@ -185,3 +185,16 @@ return path; } + +void +util_die(const char *fmt, ...) +{ + assert(fmt); + + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(1); +} diff -r 600204c31bf0 -r de4bf839b565 lib/util.h --- a/lib/util.h Tue Jul 12 20:20:51 2022 +0200 +++ b/lib/util.h Fri Jul 15 11:11:48 2022 +0200 @@ -63,4 +63,7 @@ const char * util_path(const char *); +void +util_die(const char *, ...); + #endif /* !SCI_UTIL_H */ diff -r 600204c31bf0 -r de4bf839b565 scictl.c --- a/scictl.c Tue Jul 12 20:20:51 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,424 +0,0 @@ -/* - * scictl.c -- main scictl(8) utility file - * - * Copyright (c) 2021 David Demelier - * - * 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 -#include -#include -#include -#include -#include - -#include "config.h" -#include "req.h" -#include "types.h" -#include "util.h" - -noreturn static void -usage(void) -{ - fprintf(stderr, "usage: %s [-s sock] command [args...]\n", getprogname()); - exit(1); -} - -noreturn static void -help(void) -{ - fprintf(stderr, "usage: %s job-add project tag\n", getprogname()); - fprintf(stderr, " %s job-todo worker\n", getprogname()); - fprintf(stderr, " %s jobresult-add id worker exitcode console\n", getprogname()); - fprintf(stderr, " %s project-add name desc url script\n", getprogname()); - fprintf(stderr, " %s project-info name\n", getprogname()); - fprintf(stderr, " %s project-list\n", getprogname()); - fprintf(stderr, " %s project-update name key value\n", getprogname()); - fprintf(stderr, " %s worker-add name desc\n", getprogname()); - fprintf(stderr, " %s worker-list\n", getprogname()); - exit(0); -} - -static char * -readfile(const char *path) -{ - FILE *fp, *str; - char buf[BUFSIZ], *console; - size_t nr; - - if (strcmp(path, "-") == 0) - fp = stdin; - else if (!(fp = fopen(path, "r"))) - err(1, "%s", path); - - console = util_calloc(1, SCI_MSG_MAX); - - if (!(str = fmemopen(console, SCI_MSG_MAX, "w"))) - err(1, "fmemopen"); - - while ((nr = fread(buf, 1, sizeof (buf), fp)) > 0) - fwrite(buf, 1, nr, str); - - if ((ferror(fp) && !feof(fp)) || (ferror(str) && !feof(str))) { - free(console); - console = NULL; - } - - fclose(str); - fclose(fp); - - 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}; - - if (argc < 2) - usage(); - - project.name = argv[0]; - - if (_project_find(&project, argv[0])).status) - return rp; - - job.project_id = project.id; - job.tag = argv[1]; - - rj = req_job_add(&job); - req_finish(&rp); - - return rj; -} - -static struct req -cmd_job_todo(int argc, char **argv) -{ - struct project projects[SCI_PROJECT_MAX] = {0}; - struct job jobs[SCI_JOB_LIST_MAX] = {0}; - struct req rp, rj; - size_t projectsz = UTIL_SIZE(projects), jobsz = UTIL_SIZE(jobs); - - if (argc < 1) - usage(); - - /* First retrieve projects for a better listing. */ - if ((rp = req_project_list(projects, &projectsz)).status) - return rp; - - if ((rj = req_job_todo(jobs, &jobsz, argv[0])).status) { - req_finish(&rp); - return rj; - } - - for (size_t i = 0; i < jobsz; ++i) { - const char *project = "unknown"; - - /* Find project if exists (it should). */ - for (size_t p = 0; p < projectsz; ++p) { - if (projects[p].id == jobs[i].project_id) { - project = projects[p].name; - break; - } - } - - printf("%-16s%d\n", "id:", jobs[i].id); - printf("%-16s%s\n", "tag:", jobs[i].tag); - printf("%-16s%s\n", "project:", project); - - if (i + 1 < jobsz) - printf("\n"); - } - - req_finish(&rp); - - return rj; -} - -static struct req -cmd_jobresult_add(int argc, char **argv) -{ - struct jobresult res = {0}; - struct worker wk = {0}; - struct req rw, rj; - char *log; - - if (argc < 5) - usage(); - - /* Find worker id. */ - if ((rw = req_worker_find(&wk, argv[1])).status) - return rw; - - res.job_id = strtoll(argv[0], NULL, 10); - res.exitcode = strtoll(argv[2], NULL, 10); - res.worker_id = wk.id; - res.log = log = readfile(argv[3]); - rj = req_jobresult_add(&res); - - free(log); - req_finish(&rw); - - return rj; -} - -static struct req -cmd_project_add(int argc, char **argv) -{ - struct project pc = {0}; - struct req res; - char *script; - - if (argc < 4) - usage(); - - pc.name = argv[0]; - pc.desc = argv[1]; - pc.url = argv[2]; - pc.script = script = readfile(argv[3]); - res = req_project_add(&pc); - - free(script); - - return res; -} - -static struct req -cmd_project_update(int argc, char **argv) -{ - struct project pc; - struct req rget, rsend; - char *script = NULL; - - if (argc < 3) - help(); - - if ((rget = req_project_find(&pc, argv[0])).status) - return rget; - - if (strcmp(argv[1], "name") == 0) - pc.name = argv[2]; - else if (strcmp(argv[1], "desc") == 0) - pc.desc = argv[2]; - else if (strcmp(argv[1], "url") == 0) - pc.url = argv[2]; - else if (strcmp(argv[1], "script") == 0) - pc.script = script = readfile(argv[2]); - - rsend = req_project_update(&pc); - - req_finish(&rget); - free(script); - - return rsend; -} - -static struct req -cmd_project_info(int argc, char **argv) -{ - struct project project = {0}; - struct req req; - - if (argc < 1) - usage(); - if ((req = req_project_find(&project, argv[0])).status) - return req; - - printf("%-16s%d\n", "id:", project.id); - printf("%-16s%s\n", "name:", project.name); - printf("%-16s%s\n", "desc:", project.desc); - printf("%-16s%s\n", "url:", project.url); - printf("\n"); - printf("%s", project.script); - - return req; -} - -static struct req -cmd_project_list(int argc, char **argv) -{ - (void)argc; - (void)argv; - - struct project projects[SCI_PROJECT_MAX] = {0}; - struct req req; - size_t projectsz = UTIL_SIZE(projects); - - if ((req = req_project_list(projects, &projectsz)).status) - return req; - - for (size_t i = 0; i < projectsz; ++i) { - printf("%-16s%d\n", "id:", projects[i].id); - printf("%-16s%s\n", "name:", projects[i].name); - printf("%-16s%s\n", "desc:", projects[i].desc); - printf("%-16s%s\n", "url:", projects[i].url); - - if (i + 1 < projectsz) - printf("\n"); - } - - return req; -} - -static struct req -cmd_worker_add(int argc, char **argv) -{ - struct worker wk = {0}; - - if (argc < 2) - usage(); - - wk.name = argv[0]; - wk.desc = argv[1]; - - return req_worker_add(&wk); -} - -static struct req -cmd_worker_list(int argc, char **argv) -{ - (void)argc; - (void)argv; - - struct worker wk[SCI_WORKER_MAX]; - struct req req; - size_t wksz = UTIL_SIZE(wk); - - if ((req = req_worker_list(wk, &wksz)).status) - return req; - - for (size_t i = 0; i < wksz; ++i) { - printf("%-16s%d\n", "id:", wk[i].id); - printf("%-16s%s\n", "name:", wk[i].name); - printf("%-16s%s\n", "desc:", wk[i].desc); - - if (i + 1 < wksz) - printf("\n"); - } - - return req; -} - -static struct { - const char *name; - struct req (*exec)(int, char **); -} commands[] = { - { "job-add", cmd_job_add }, - { "job-todo", cmd_job_todo }, - { "jobresult-add", cmd_jobresult_add }, - { "project-add", cmd_project_add }, - { "project-info", cmd_project_info }, - { "project-list", cmd_project_list }, - { "project-update", cmd_project_update }, - { "worker-add", cmd_worker_add }, - { "worker-list", cmd_worker_list }, - { NULL, NULL } -}; - -int -main(int argc, char **argv) -{ - int ch, cmdfound = 0; - - setprogname("scictl"); - - while ((ch = getopt(argc, argv, "s:")) != -1) { - switch (ch) { - case 's': - req_set_path(optarg); - break; - default: - break; - } - } - - argc -= optind; - argv += optind; - - if (argc <= 0) - usage(); - if (strcmp(argv[0], "help") == 0) - help(); - - for (size_t i = 0; commands[i].name; ++i) { - struct req res; - - if (strcmp(commands[i].name, argv[0]) == 0) { - res = commands[i].exec(--argc, ++argv); - cmdfound = 1; - - if (res.status) - warnx("%s", json_string_value(json_object_get(res.msg, "error"))); - - req_finish(&res); - break; - } - } - - if (!cmdfound) - errx(1, "abort: command %s not found", argv[0]); -} diff -r 600204c31bf0 -r de4bf839b565 scictl/scictl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scictl/scictl.c Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,424 @@ +/* + * scictl.c -- main scictl(8) utility file + * + * Copyright (c) 2021 David Demelier + * + * 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 +#include +#include +#include +#include +#include + +#include "config.h" +#include "req.h" +#include "types.h" +#include "util.h" + +noreturn static void +usage(void) +{ + fprintf(stderr, "usage: %s [-s sock] command [args...]\n", getprogname()); + exit(1); +} + +noreturn static void +help(void) +{ + fprintf(stderr, "usage: %s job-add project tag\n", getprogname()); + fprintf(stderr, " %s job-todo worker\n", getprogname()); + fprintf(stderr, " %s jobresult-add id worker exitcode console\n", getprogname()); + fprintf(stderr, " %s project-add name desc url script\n", getprogname()); + fprintf(stderr, " %s project-info name\n", getprogname()); + fprintf(stderr, " %s project-list\n", getprogname()); + fprintf(stderr, " %s project-update name key value\n", getprogname()); + fprintf(stderr, " %s worker-add name desc\n", getprogname()); + fprintf(stderr, " %s worker-list\n", getprogname()); + exit(0); +} + +static char * +readfile(const char *path) +{ + FILE *fp, *str; + char buf[BUFSIZ], *console; + size_t nr; + + if (strcmp(path, "-") == 0) + fp = stdin; + else if (!(fp = fopen(path, "r"))) + err(1, "%s", path); + + console = util_calloc(1, SCI_MSG_MAX); + + if (!(str = fmemopen(console, SCI_MSG_MAX, "w"))) + err(1, "fmemopen"); + + while ((nr = fread(buf, 1, sizeof (buf), fp)) > 0) + fwrite(buf, 1, nr, str); + + if ((ferror(fp) && !feof(fp)) || (ferror(str) && !feof(str))) { + free(console); + console = NULL; + } + + fclose(str); + fclose(fp); + + 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}; + + if (argc < 2) + usage(); + + project.name = argv[0]; + + if (_project_find(&project, argv[0])).status) + return rp; + + job.project_id = project.id; + job.tag = argv[1]; + + rj = req_job_add(&job); + req_finish(&rp); + + return rj; +} + +static struct req +cmd_job_todo(int argc, char **argv) +{ + struct project projects[SCI_PROJECT_MAX] = {0}; + struct job jobs[SCI_JOB_LIST_MAX] = {0}; + struct req rp, rj; + size_t projectsz = UTIL_SIZE(projects), jobsz = UTIL_SIZE(jobs); + + if (argc < 1) + usage(); + + /* First retrieve projects for a better listing. */ + if ((rp = req_project_list(projects, &projectsz)).status) + return rp; + + if ((rj = req_job_todo(jobs, &jobsz, argv[0])).status) { + req_finish(&rp); + return rj; + } + + for (size_t i = 0; i < jobsz; ++i) { + const char *project = "unknown"; + + /* Find project if exists (it should). */ + for (size_t p = 0; p < projectsz; ++p) { + if (projects[p].id == jobs[i].project_id) { + project = projects[p].name; + break; + } + } + + printf("%-16s%d\n", "id:", jobs[i].id); + printf("%-16s%s\n", "tag:", jobs[i].tag); + printf("%-16s%s\n", "project:", project); + + if (i + 1 < jobsz) + printf("\n"); + } + + req_finish(&rp); + + return rj; +} + +static struct req +cmd_jobresult_add(int argc, char **argv) +{ + struct jobresult res = {0}; + struct worker wk = {0}; + struct req rw, rj; + char *log; + + if (argc < 5) + usage(); + + /* Find worker id. */ + if ((rw = req_worker_find(&wk, argv[1])).status) + return rw; + + res.job_id = strtoll(argv[0], NULL, 10); + res.exitcode = strtoll(argv[2], NULL, 10); + res.worker_id = wk.id; + res.log = log = readfile(argv[3]); + rj = req_jobresult_add(&res); + + free(log); + req_finish(&rw); + + return rj; +} + +static struct req +cmd_project_add(int argc, char **argv) +{ + struct project pc = {0}; + struct req res; + char *script; + + if (argc < 4) + usage(); + + pc.name = argv[0]; + pc.desc = argv[1]; + pc.url = argv[2]; + pc.script = script = readfile(argv[3]); + res = req_project_add(&pc); + + free(script); + + return res; +} + +static struct req +cmd_project_update(int argc, char **argv) +{ + struct project pc; + struct req rget, rsend; + char *script = NULL; + + if (argc < 3) + help(); + + if ((rget = req_project_find(&pc, argv[0])).status) + return rget; + + if (strcmp(argv[1], "name") == 0) + pc.name = argv[2]; + else if (strcmp(argv[1], "desc") == 0) + pc.desc = argv[2]; + else if (strcmp(argv[1], "url") == 0) + pc.url = argv[2]; + else if (strcmp(argv[1], "script") == 0) + pc.script = script = readfile(argv[2]); + + rsend = req_project_update(&pc); + + req_finish(&rget); + free(script); + + return rsend; +} + +static struct req +cmd_project_info(int argc, char **argv) +{ + struct project project = {0}; + struct req req; + + if (argc < 1) + usage(); + if ((req = req_project_find(&project, argv[0])).status) + return req; + + printf("%-16s%d\n", "id:", project.id); + printf("%-16s%s\n", "name:", project.name); + printf("%-16s%s\n", "desc:", project.desc); + printf("%-16s%s\n", "url:", project.url); + printf("\n"); + printf("%s", project.script); + + return req; +} + +static struct req +cmd_project_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + struct project projects[SCI_PROJECT_MAX] = {0}; + struct req req; + size_t projectsz = UTIL_SIZE(projects); + + if ((req = req_project_list(projects, &projectsz)).status) + return req; + + for (size_t i = 0; i < projectsz; ++i) { + printf("%-16s%d\n", "id:", projects[i].id); + printf("%-16s%s\n", "name:", projects[i].name); + printf("%-16s%s\n", "desc:", projects[i].desc); + printf("%-16s%s\n", "url:", projects[i].url); + + if (i + 1 < projectsz) + printf("\n"); + } + + return req; +} + +static struct req +cmd_worker_add(int argc, char **argv) +{ + struct worker wk = {0}; + + if (argc < 2) + usage(); + + wk.name = argv[0]; + wk.desc = argv[1]; + + return req_worker_add(&wk); +} + +static struct req +cmd_worker_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + struct worker wk[SCI_WORKER_MAX]; + struct req req; + size_t wksz = UTIL_SIZE(wk); + + if ((req = req_worker_list(wk, &wksz)).status) + return req; + + for (size_t i = 0; i < wksz; ++i) { + printf("%-16s%d\n", "id:", wk[i].id); + printf("%-16s%s\n", "name:", wk[i].name); + printf("%-16s%s\n", "desc:", wk[i].desc); + + if (i + 1 < wksz) + printf("\n"); + } + + return req; +} + +static struct { + const char *name; + struct req (*exec)(int, char **); +} commands[] = { + { "job-add", cmd_job_add }, + { "job-todo", cmd_job_todo }, + { "jobresult-add", cmd_jobresult_add }, + { "project-add", cmd_project_add }, + { "project-info", cmd_project_info }, + { "project-list", cmd_project_list }, + { "project-update", cmd_project_update }, + { "worker-add", cmd_worker_add }, + { "worker-list", cmd_worker_list }, + { NULL, NULL } +}; + +int +main(int argc, char **argv) +{ + int ch, cmdfound = 0; + + setprogname("scictl"); + + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + req_set_path(optarg); + break; + default: + break; + } + } + + argc -= optind; + argv += optind; + + if (argc <= 0) + usage(); + if (strcmp(argv[0], "help") == 0) + help(); + + for (size_t i = 0; commands[i].name; ++i) { + struct req res; + + if (strcmp(commands[i].name, argv[0]) == 0) { + res = commands[i].exec(--argc, ++argv); + cmdfound = 1; + + if (res.status) + warnx("%s", json_string_value(json_object_get(res.msg, "error"))); + + req_finish(&res); + break; + } + } + + if (!cmdfound) + errx(1, "abort: command %s not found", argv[0]); +} diff -r 600204c31bf0 -r de4bf839b565 scid/db.c --- a/scid/db.c Tue Jul 12 20:20:51 2022 +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 - * - * 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 -#include -#include -#include - -#include - -#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; - } -} diff -r 600204c31bf0 -r de4bf839b565 scid/db.h --- a/scid/db.h Tue Jul 12 20:20:51 2022 +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 - * - * 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 -#include - -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 */ diff -r 600204c31bf0 -r de4bf839b565 sciworkerd/main.c --- a/sciworkerd/main.c Tue Jul 12 20:20:51 2022 +0200 +++ b/sciworkerd/main.c Fri Jul 15 11:11:48 2022 +0200 @@ -16,719 +16,49 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#if 0 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include #include -#include -#include - -#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; -} +#include "sciworkerd.h" 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) +env(void) { - 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; + const char *env; - 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); - } + if ((env = getenv("SCI_URL"))) + snprintf(sciworkerd.url, sizeof (sciworkerd.url), "%s", optarg); + if ((env = getenv("SCI_WORKER"))) + snprintf(sciworkerd.name, sizeof (sciworkerd.name), "%s", optarg); } int main(int argc, char **argv) { - int ch; - const char *errstr; + int ch, val; - setprogname("sciworkerd"); + env(); + opterr = 0; - while ((ch = getopt(argc, argv, "m:u:w:")) != -1) { + while ((ch = getopt(argc, argv, "j:t:u:w:")) != -1) { switch (ch) { - case 'm': - config.maxbuilds = strtonum(optarg, 0, INT_MAX, &errstr); - - if (errstr) - errx(1, "%s: %s", optarg, errstr); - + case 'j': + if ((val = atoi(optarg)) > 0) + sciworkerd.maxjobs = val; + break; + case 't': + if ((val = atoi(optarg)) > 0) + sciworkerd.timeout = val; break; case 'u': - config.url = optarg; + snprintf(sciworkerd.url, sizeof (sciworkerd.url), "%s", optarg); break; case 'w': - config.worker = optarg; + snprintf(sciworkerd.name, sizeof (sciworkerd.name), "%s", optarg); break; default: - usage(); break; } } - - init(); - - while (alive) - run(); - - finish(); } -#endif - - - - - - - - - - -#include -#include -#include -#include -#include -#include -#include - -#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); -} diff -r 600204c31bf0 -r de4bf839b565 sciworkerd/sciworkerd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciworkerd/sciworkerd.c Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,393 @@ +#include +#include +#include +#include +#include + +#include + +#include "apic.h" +#include "log.h" +#include "sciworkerd.h" +#include "task.h" +#include "types.h" +#include "util.h" + +#define TAG "sigworkerd: " + +struct taskentry { + struct task *task; + struct job job; + struct taskentry *next; +}; + +static struct taskentry *taskpending; +static struct taskentry *tasks; +static struct taskentry *taskfinished; +static struct worker worker; +static int run = 1; + +struct sciworkerd sciworkerd = { + .fetchinterval = 300, + .maxjobs = 4, + .timeout = 600 +}; + +static inline void +taskentry_free(struct taskentry *entry) +{ + if (task_status(entry->task) == TASKSTATUS_RUNNING) { + if (task_kill(entry->task) == 0) + task_wait(entry->task); + } + + task_free(entry->task); + free(entry); +} + +static void +stop(int sign) +{ + log_info(TAG "exiting on signal %d\n", sign); + run = 0; +} + +static inline int +pending(int id) +{ + const struct taskentry *iter; + + LL_FOREACH(taskpending, iter) + if (iter->job.id == id) + return 1; + + return 0; +} + +static inline void +queue(const struct job *job) +{ + struct taskentry *tk; + + log_info(TAG "queued job build (%d) for tag %s\n", job->id, job->tag); + + tk = util_calloc(1, sizeof (*tk)); + tk->task = task_new(job->tag); + memcpy(&tk->job, job, sizeof (*job)); + LL_APPEND(taskpending, tk); +} + +static void +merge(json_t *doc) +{ + struct job jobs[SCI_JOB_LIST_MAX]; + ssize_t jobsz; + size_t total = 0; + + if ((jobsz = job_from(jobs, UTIL_SIZE(jobs), doc)) < 0) + log_warn(TAG "error while parsing jobs: %s", strerror(errno)); + else { + for (ssize_t i = 0; i < jobsz; ++i) { + if (!pending(jobs[i].id)) { + queue(&jobs[i]); + total++; + } + } + + log_info(TAG "added %zu new pending tasks", total); + } +} + +/* + * Fetch jobs periodically, depending on the user setting. + */ +static void +fetch_jobs(void) +{ + static time_t startup; + time_t now; + struct apicreq req; + + if (!startup) + startup = time(NULL); + + if (difftime((now = time(NULL)), startup) >= sciworkerd.fetchinterval) { + startup = now; + + if (apic_get(&req, "%s/api/v1/%s", sciworkerd.url, sciworkerd.name) < 0) + log_warn(TAG "unable to fetch jobs: %s", req.error); + if (req.doc) { + merge(req.doc); + json_decref(req.doc); + } + } +} + +/* + * Fetch information about myself. + */ +static void +fetch_worker(void) +{ + struct apicreq req; + + if (apic_get(&req, "%s/api/v1/workers/%s", sciworkerd.url, sciworkerd.name) < 0) + log_warn(TAG "unable to fetch worker info: %s", req.error); + if (!req.doc) + log_die(TAG "empty worker response"); + if (worker_from(&worker, 1, req.doc) < 0) + log_die(TAG "unable to parse worker", strerror(errno)); + + log_info("worker id: %d", worker.id); + log_info("worker name: %s", worker.name); + log_info("worker description: %s", worker.desc); + + json_decref(req.doc); +} + +/* + * Fetch information about a project. + */ +static int +fetch_project(struct project *project, int id) +{ + struct apicreq req; + + if (apic_get(&req, "%s/api/v1/projects/%d", id) < 0) + return log_warn(TAG "unable to fetch project info: %s", req.error), -1; + if (!req.doc) + return log_warn(TAG "empty project response"), -1; + if (project_from(project, 1, req.doc) < 0) + return log_warn(TAG "unable to parse project: %s", strerror(errno)), -1; + + return 0; +} + +static inline size_t +count(const struct taskentry *head) +{ + const struct taskentry *iter; + size_t tot = 0; + + LL_FOREACH(head, iter) + tot++; + + return tot; +} + +/* + * Start a task. We fetch its script code and then create the task with that + * script. + */ +static int +start(struct taskentry *entry) +{ + struct project project; + pid_t pid; + + if (fetch_project(&project, entry->job.project_id) < 0) + return log_warn(TAG "unable to fetch project, dropping task"), -1; + if (task_setup(entry->task, project.script) < 0) + return log_warn(TAG "unable to setup script code: %s, dropping task", strerror(errno)), -1; + if ((pid = task_start(entry->task)) < 0) + return log_warn(TAG "unable to spawn task process: %s", strerror(errno)), -1; + + log_info(TAG "task %lld spawned", (long long int)pid); + + return 0; +} + +static inline void +delete(struct taskentry *entry) +{ + LL_DELETE(taskpending, entry); + task_free(entry->task); + free(entry); +} + +static void +start_all(void) +{ + size_t running = count(tasks); + struct taskentry *entry; + + while (running-- > 0 && (entry = taskpending)) { + if (start(entry) < 0) + delete(entry); + else { + LL_DELETE(taskpending, entry); + LL_APPEND(tasks, entry); + } + } +} + +static void +process_all(void) +{ + struct taskentry *iter, *next; + struct taskcode code; + struct pollfd *fds; + size_t fdsz, i = 0; + int ret; + + /* First, read every pipes. */ + if (!(fdsz = count(tasks))) + return; + + fds = util_calloc(fdsz, sizeof (*fds)); + + for (iter = tasks; iter; iter = iter->next) + task_prepare(iter->task, &fds[i++]); + + if (poll(fds, fdsz, 5000) < 0) + log_warn("poll: %s", strerror(errno)); + + for (iter = tasks, i = 0; i < fdsz; ++i) { + next = iter->next; + + /* + * 0: EOF [wait] + * -1: error [kill + wait] + * >0: keep going [nothing] + */ + if ((ret = task_sync(iter->task, &fds[i])) < 0) { + log_warn(TAG "pipe error: %s, killing task", strerror(errno)); + + if (task_kill(iter->task) < 0) + log_warn(TAG "task kill error: %s", strerror(errno)); + } + + /* Now wait for the task to complete. */ + if (ret <= 0) { + if (task_wait(iter->task) < 0) + log_warn(TAG "task wait error: %s", strerror(errno)); + else { + code = task_code(iter->task); + + switch (task_status(iter->task)) { + case TASKSTATUS_EXITED: + log_info(TAG "task %lld exited with code %d", + (long long int)task_pid(iter->task), code.exitcode); + break; + case TASKSTATUS_KILLED: + log_info(TAG "task %lld killed with signal %d", + (long long int)task_pid(iter->task), code.sigcode); + break; + default: + break; + } + } + + /* Remove that task and push to the outgoing queue. */ + next = iter->next; + LL_DELETE(tasks, iter); + LL_APPEND(taskfinished, iter); + } + } + + free(fds); +} + +/* + * Kill all tasks that have been running for too long. + */ +static void +ghost_all(void) +{ + struct taskentry *iter, *tmp; + time_t now; + + LL_FOREACH_SAFE(tasks, iter, tmp) { + if (difftime(time(NULL), task_uptime(iter->task)) < sciworkerd.timeout) + continue; + + /* Do not attempt to wait if kill failed to avoid lock. */ + log_info(TAG "task timeout, killing"); + + if (task_kill(iter->task) == 0) + task_wait(iter->task); + + LL_DELETE(tasks, iter); + LL_APPEND(taskfinished, iter); + } +} + +static int +publish(struct taskentry *iter) +{ + // TODO: add sigcode. + struct taskcode code = task_code(iter->task); + struct jobresult res = { + .job_id = iter->job.id, + .exitcode = code.exitcode, + .log = task_console(iter->task), + .worker_id = worker.id + }; + struct apicreq req; + json_t *doc; + int ret; + + doc = jobresult_to(&res, 1); + ret = apic_post(&req, doc, "%s/api/v1/jobs", sciworkerd.url); + json_decref(doc); + + if (ret) + log_warn(TAG "unable to publish task: %s", req.error); + else + log_info(TAG "task successfully published"); + + return ret; +} + +static void +publish_all(void) +{ + struct taskentry *iter, *tmp; + + LL_FOREACH_SAFE(taskfinished, iter, tmp) { + if (publish(iter) == 0) { + LL_DELETE(taskfinished, iter); + taskentry_free(iter); + } + } +} + +void +sciworkerd_init(void) +{ + struct sigaction sa = {0}; + + log_open("sigworkerd"); + + sigemptyset(&sa.sa_mask); + sa.sa_handler = stop; + + if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) + log_die(TAG "sigaction: %s", strerror(errno)); + + fetch_worker(); +} + +void +sciworkerd_run(void) +{ + while (run) { + fetch_jobs(); + process_all(); + ghost_all(); + publish_all(); + } +} + +void +sciworkerd_finish(void) +{ + struct taskentry *iter, *tmp; + + LL_FOREACH_SAFE(taskpending, iter, tmp) + taskentry_free(iter); + LL_FOREACH_SAFE(tasks, iter, tmp) + taskentry_free(iter); + LL_FOREACH_SAFE(taskfinished, iter, tmp) + taskentry_free(iter); +} diff -r 600204c31bf0 -r de4bf839b565 sciworkerd/sciworkerd.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciworkerd/sciworkerd.h Fri Jul 15 11:11:48 2022 +0200 @@ -0,0 +1,23 @@ +#ifndef SCIWORKERD_H +#define SCIWORKERD_H + +#include "config.h" + +extern struct sciworkerd { + char url[SCI_URL_MAX]; + char name[SCI_WORKER_MAX]; + unsigned int fetchinterval; + unsigned int maxjobs; + unsigned int timeout; +} sciworkerd; + +void +sciworkerd_init(void); + +void +sciworkerd_run(void); + +void +sciworkerd_finish(void); + +#endif /* !SCIWORKERD_H */ diff -r 600204c31bf0 -r de4bf839b565 sciworkerd/task.c --- a/sciworkerd/task.c Tue Jul 12 20:20:51 2022 +0200 +++ b/sciworkerd/task.c Fri Jul 15 11:11:48 2022 +0200 @@ -24,8 +24,6 @@ int pipe[2]; int exitcode; int sigcode; - int job_id; - int project_id; char *job_tag; FILE *fp; char *console; @@ -36,14 +34,12 @@ }; struct task * -task_new(const struct job *job) +task_new(const char *tag) { 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->job_tag = util_strdup(tag); task->pipe[0] = task->pipe[1] = -1; task->child = -1; @@ -63,8 +59,7 @@ const size_t len = strlen(script); - snprintf(self->scriptpath, sizeof (self->scriptpath), - "/tmp/sciworkerd-%d-XXXXXX", self->job_id); + snprintf(self->scriptpath, sizeof (self->scriptpath), "/tmp/sciworkerd-XXXXXX"); if ((self->scriptfd = mkstemp(self->scriptpath)) < 0) goto failed; @@ -166,6 +161,15 @@ return self->startup; } +pid_t +task_pid(const struct task *self) +{ + assert(self); + assert(self->status == TASKSTATUS_RUNNING); + + return self->child; +} + const char * task_console(const struct task *self) { diff -r 600204c31bf0 -r de4bf839b565 sciworkerd/task.h --- a/sciworkerd/task.h Tue Jul 12 20:20:51 2022 +0200 +++ b/sciworkerd/task.h Fri Jul 15 11:11:48 2022 +0200 @@ -3,7 +3,6 @@ #include -struct job; struct pollfd; struct task; @@ -11,8 +10,7 @@ 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. */ + TASKSTATUS_KILLED /* process killed killed. */ }; struct taskcode { @@ -21,7 +19,7 @@ }; struct task * -task_new(const struct job *job); +task_new(const char *tag); int task_setup(struct task *self, const char *script); @@ -44,6 +42,9 @@ time_t task_uptime(const struct task *self); +pid_t +task_pid(const struct task *self); + const char * task_console(const struct task *self); diff -r 600204c31bf0 -r de4bf839b565 sql/init.sql --- a/sql/init.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/init.sql Fri Jul 15 11:11:48 2022 +0200 @@ -17,38 +17,34 @@ -- CREATE TABLE IF NOT EXISTS project( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE, - desc TEXT NOT NULL, - url TEXT NOT NULL, - script TEXT NOT NULL, - date INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + `name` TEXT NOT NULL UNIQUE, + `desc` TEXT NOT NULL, + `url` TEXT NOT NULL, + `script` TEXT NOT NULL, + `date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); CREATE TABLE IF NOT EXISTS worker( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE, - desc TEXT NOT NULL, - date INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + `name` TEXT NOT NULL UNIQUE, + `desc` TEXT NOT NULL, + `date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); CREATE TABLE IF NOT EXISTS job( - id INTEGER PRIMARY KEY AUTOINCREMENT, - tag TEXT NOT NULL UNIQUE, - project_id INTEGER NOT NULL REFERENCES project (id), - date INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + `tag` TEXT NOT NULL UNIQUE, + `project_id` INTEGER NOT NULL REFERENCES project (id), + `date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); CREATE TABLE IF NOT EXISTS jobresult( - id INTEGER PRIMARY KEY AUTOINCREMENT, - job_id INTEGER NOT NULL REFERENCES job (id), - worker_id INTEGER NOT NULL REFERENCES worker (id), - exitcode INTEGER DEFAULT 0, - console TEXT DEFAULT NULL, - date INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + `job_id` INTEGER NOT NULL REFERENCES job (id), + `worker_id` INTEGER NOT NULL REFERENCES worker (id), + `exitcode` INTEGER DEFAULT 0, + `console` TEXT DEFAULT NULL, + `date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); CREATE TABLE IF NOT EXISTS property( - key TEXT PRIMARY KEY NOT NULL, - value TEXT + `key` TEXT PRIMARY KEY NOT NULL, + `value` TEXT ); diff -r 600204c31bf0 -r de4bf839b565 sql/job-add.sql --- a/sql/job-add.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/job-add.sql Fri Jul 15 11:11:48 2022 +0200 @@ -17,6 +17,6 @@ -- INSERT INTO job( - tag, - project_id + `tag`, + `project_id` ) VALUES (?, ?) diff -r 600204c31bf0 -r de4bf839b565 sql/job-todo.sql --- a/sql/job-todo.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/job-todo.sql Fri Jul 15 11:11:48 2022 +0200 @@ -21,19 +21,19 @@ -- otherwise when adding a new worker it would need to run potentially a very -- high number of jobs -- -SELECT job.id - , job.tag - , job.project_id - FROM job - WHERE job.id +SELECT `job`.`rowid` + , `job`.`tag` + , `job`.`project_id` + FROM `job` + WHERE `job`.`rowid` NOT IN ( - SELECT jobresult.job_id - FROM jobresult - WHERE jobresult.worker_id = ? + SELECT `jobresult`.`job_id` + FROM `jobresult` + WHERE `jobresult`.`worker_id` = ? ) - AND job.date >= ( - SELECT worker.date - FROM worker - WHERE worker.id = ? + AND `job`.`date` >= ( + SELECT `worker`.`date` + FROM `worker` + WHERE `worker`.`rowid` = ? ) LIMIT ? diff -r 600204c31bf0 -r de4bf839b565 sql/jobresult-add.sql --- a/sql/jobresult-add.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/jobresult-add.sql Fri Jul 15 11:11:48 2022 +0200 @@ -17,8 +17,8 @@ -- INSERT INTO jobresult( - job_id, - worker_id, - exitcode, - console + `job_id`, + `worker_id`, + `exitcode`, + `console` ) VALUES (?, ?, ?, ?) diff -r 600204c31bf0 -r de4bf839b565 sql/project-add.sql --- a/sql/project-add.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/project-add.sql Fri Jul 15 11:11:48 2022 +0200 @@ -17,8 +17,8 @@ -- INSERT INTO project( - name, - desc, - url, - script + `name`, + `desc`, + `url`, + `script` ) VALUES (?, ?, ?, ?) diff -r 600204c31bf0 -r de4bf839b565 sql/project-find-id.sql --- a/sql/project-find-id.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/project-find-id.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,7 +16,8 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -SELECT * - FROM project - WHERE id = ? +SELECT rowid + , * + FROM `project` + WHERE `rowid` = ? LIMIT 1 diff -r 600204c31bf0 -r de4bf839b565 sql/project-find.sql --- a/sql/project-find.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/project-find.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,7 +16,8 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -SELECT * - FROM project - WHERE name = ? +SELECT rowid + , * + FROM `project` + WHERE `name` = ? LIMIT 1 diff -r 600204c31bf0 -r de4bf839b565 sql/project-list.sql --- a/sql/project-list.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/project-list.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,6 +16,7 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -SELECT * - FROM project +SELECT rowid + , * + FROM `project` LIMIT ? diff -r 600204c31bf0 -r de4bf839b565 sql/project-update.sql --- a/sql/project-update.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/project-update.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,9 +16,9 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -UPDATE project - SET name = ? - , desc = ? - , url = ? - , script = ? - WHERE id = ? +UPDATE `project` + SET `name` = ? + , `desc` = ? + , `url` = ? + , `script` = ? + WHERE `rowid` = ? diff -r 600204c31bf0 -r de4bf839b565 sql/worker-add.sql --- a/sql/worker-add.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/worker-add.sql Fri Jul 15 11:11:48 2022 +0200 @@ -17,6 +17,6 @@ -- INSERT INTO worker( - name, - desc + `name`, + `desc` ) VALUES (?, ?) diff -r 600204c31bf0 -r de4bf839b565 sql/worker-find-id.sql --- a/sql/worker-find-id.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/worker-find-id.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,7 +16,8 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -SELECT * - FROM worker - WHERE id = ? +SELECT rowid + , * + FROM `worker` + WHERE `rowid` = ? LIMIT 1 diff -r 600204c31bf0 -r de4bf839b565 sql/worker-find.sql --- a/sql/worker-find.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/worker-find.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,7 +16,8 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -SELECT * - FROM worker - WHERE name = ? +SELECT rowid + , * + FROM `worker` + WHERE `name` = ? LIMIT 1 diff -r 600204c31bf0 -r de4bf839b565 sql/worker-list.sql --- a/sql/worker-list.sql Tue Jul 12 20:20:51 2022 +0200 +++ b/sql/worker-list.sql Fri Jul 15 11:11:48 2022 +0200 @@ -16,6 +16,7 @@ -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- -SELECT * - FROM worker +SELECT rowid + , * + FROM `worker` LIMIT ?