summaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2021-03-28 01:50:36 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2021-03-28 01:50:36 +0000
commite2959c22c7309836d6f5dba2f0db7b293b860ad8 (patch)
treea26cefd2a36f6682e302bed8840642da0c6c64a8 /src/server
parentba3bcbb86ea0177349bcd021559347248d6ab10a (diff)
downloads6-rc-e2959c22c7309836d6f5dba2f0db7b293b860ad8.tar.xz
A few pieces of the future s6-rcd server
Diffstat (limited to 'src/server')
-rw-r--r--src/server/client.c161
-rw-r--r--src/server/client.h50
-rw-r--r--src/server/clientrules.c118
-rw-r--r--src/server/clientrules.h15
-rw-r--r--src/server/command.c43
-rw-r--r--src/server/command.h14
-rw-r--r--src/server/db.h11
-rw-r--r--src/server/ep.c128
-rw-r--r--src/server/ep.h21
-rw-r--r--src/server/ev.c47
-rw-r--r--src/server/ev.h19
-rw-r--r--src/server/livedir.h11
-rw-r--r--src/server/livedir_init.c124
-rw-r--r--src/server/livesubdir_create.c35
-rw-r--r--src/server/main.h18
-rw-r--r--src/server/s6-rcd.c139
-rw-r--r--src/server/s6-rcd.h17
-rw-r--r--src/server/signals.c89
-rw-r--r--src/server/signals.h11
-rw-r--r--src/server/transition.h22
20 files changed, 1093 insertions, 0 deletions
diff --git a/src/server/client.c b/src/server/client.c
new file mode 100644
index 0000000..3984553
--- /dev/null
+++ b/src/server/client.c
@@ -0,0 +1,161 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <skalibs/alloc.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/textmessage.h>
+
+#include <s6-rc/connection-common.h>
+#include "command.h"
+#include "main.h"
+#include "client.h"
+
+ /*
+ We can't use gensetdyn for client storage, because
+ c->connection.in has a pointer to c->connection.inbuf
+ and gensetdyn can relocate the whole thing on realloc;
+ also, having a sentinel is too costly (client_t is big).
+ So we use an OG linked list, with all the boundary value
+ problems it brings.
+ */
+
+tain_t client_answer_tto = TAIN_INFINITE ;
+client_t *client_head = 0 ;
+uint32_t client_connections = 0 ;
+uint32_t client_monitors = 0 ;
+
+static inline int ismonitored (client_t *c)
+{
+ return textmessage_sender_fd(&c->monitor) >= 0 ;
+}
+
+static inline void client_free (client_t *c)
+{
+ monitor_finish(c) ;
+ fd_close(textmessage_sender_fd(&c->connection.out)) ;
+ textmessage_sender_free(&c->connection.out) ;
+ textmessage_receiver_free(&c->connection.in) ;
+ alloc_free(c) ;
+}
+
+void client_yoink (client_t **c)
+{
+ client_t *prev = (*c)->prev ;
+ if (prev) prev->next = (*c)->next ; else client_head = (*c)->next ;
+ if ((*c)->next) (*c)->next->prev = prev ;
+ if (ismonitored(*c)) nmonitors-- ;
+ client_free(*c) ;
+ *c = prev ;
+ client_connections-- ;
+}
+
+void client_setdeadline (client_t *c)
+{
+ tain_t blah ;
+ tain_half(&blah, &tain_infinite_relative) ;
+ tain_add_g(&blah, &blah) ;
+ if (tain_less(&blah, &c->deadline))
+ tain_add_g(&c->deadline, &client_answer_tto) ;
+}
+
+int client_prepare_iopause (client_t *c, tain_t *deadline, iopause_fd *x, uint32_t *j)
+{
+ if (tain_less(&c->deadline, deadline)) *deadline = c->deadline ;
+ if (!textmessage_sender_isempty(&c->connection.out) || !textmessage_receiver_isempty(&c->connection.in) || (!flags.lameduck && !textmessage_receiver_isfull(&c->connection.in)))
+ {
+ x[*j].fd = textmessage_sender_fd(&c->connection.out) ;
+ x[*j].events = (!textmessage_receiver_isempty(&c->connection.in) || (!flags.lameduck && !textmessage_receiver_isfull(&c->connection.in)) ? IOPAUSE_READ : 0) | (!textmessage_sender_isempty(&c->connection.out) ? IOPAUSE_WRITE : 0) ;
+ c->xindex[0] = (*j)++ ;
+ }
+ else c->xindex[0] = 0 ;
+ if (ismonitored(c) && !textmessage_sender_isempty(&c->monitor))
+ {
+ x[*j].fd = textmessage_sender_fd(&c->monitor) ;
+ x[*j].events = IOPAUSE_WRITE ;
+ c->xindex[1] = (*j)++ ;
+ }
+ else c->xindex[1] = 0 ;
+ return !!c->xindex[0] || !!c->xindex[1] ;
+}
+
+int client_add (int fd, uint8_t perms)
+{
+ client_t *c = alloc(sizeof(client_t)) ;
+ if (!c) return 0 ;
+ c->xindex[0] = c->xindex[1] = 0 ;
+ tain_add_g(&c->deadline, &client_answer_tto) ;
+ c->perms = perms ;
+ c->monitor = textmessage_sender_zero ;
+ textmessage_sender_init(&c->connection.out, fd) ;
+ textmessage_receiver_init(&c->connection.in, fd, &c->connection.inbuf, S6RC_CONNECTION_BUFSIZE) ;
+ c->prev = 0 ;
+ c->next = client_head ;
+ if (c->next) c->next->prev = c ;
+ client_head = c ;
+ client_connections++ ;
+ return 1 ;
+}
+
+int client_flush (client_t *c, iopause_fd const *x)
+{
+ int ok = 1 ;
+ int done = 1 ;
+ if (c->xindex[0] && (x[c->xindex[0]].revents & IOPAUSE_WRITE))
+ {
+ if (!textmessage_sender_flush(&c->connection.out))
+ {
+ done = 0 ;
+ if (!error_isagain(errno)) ok = 0 ;
+ }
+ }
+ if (ismonitored(c) && c->xindex[1] && (x[c->xindex[1]].revents & IOPAUSE_WRITE))
+ {
+ if (!textmessage_sender_flush(&c->monitor))
+ {
+ done = 0 ;
+ if (!error_isagain(errno)) ok = 0 ;
+ }
+ }
+ if (done) tain_add_g(&c->deadline, &tain_infinite_relative) ;
+ return ok ;
+}
+
+int client_read (client_t *c, iopause_fd const *x)
+{
+ return !textmessage_receiver_isempty(&c->connection.in) || (c->xindex[0] && (x[c->xindex[0]].revents & IOPAUSE_READ)) ?
+ textmessage_handle(&c->connection.in, command_handle, c) > 0 : 1 ;
+}
+
+int monitor_init (client_t *c, int fd, unsigned int v)
+{
+ if (ismonitored(c)) return (errno = EINVAL, 0) ;
+ textmessage_sender_init(&c->monitor, fd) ;
+ c->monitor_verbosity = v ;
+ client_monitors++ ;
+ return 1 ;
+}
+
+void monitor_finish (client_t *c)
+{
+ if (!ismonitored(c)) return ;
+ fd_close(textmessage_sender_fd(&c->monitor)) ;
+ textmessage_sender_free(&c->monitor) ;
+ c->monitor = textmessage_sender_zero ;
+ client_monitors-- ;
+}
+
+void monitor_put (unsigned int v, char const *s, size_t len)
+{
+ if (!client_monitors) return ;
+ for (client_t *c = client_head ; c ; c = c->next)
+ if (ismonitored(c) && v >= c->monitor_verbosity && !textmessage_put(&c->monitor, s, len))
+ strerr_diefu1sys("queue message to monitor") ;
+}
diff --git a/src/server/client.h b/src/server/client.h
new file mode 100644
index 0000000..4a6ad6c
--- /dev/null
+++ b/src/server/client.h
@@ -0,0 +1,50 @@
+/* ISC license. */
+
+#ifndef S6RCD_CLIENT_H
+#define S6RCD_CLIENT_H
+
+#include <stdint.h>
+
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/textmessage.h>
+
+#include <s6-rc/connection.h>
+
+
+ /* Client connection */
+
+typedef struct client_s client_t, *client_t_ref ;
+struct client_s
+{
+ client_t *prev ;
+ client_t *next ;
+ uint32_t xindex[2] ;
+ tain_t deadline ;
+ s6rc_connection_t connection ;
+ textmessage_sender_t monitor ;
+ unsigned int monitor_verbosity ;
+ uint8_t perms ;
+} ;
+#define CLIENT_ZERO { .next = 0, .xindex = 0, .deadline = TAIN_ZERO, .connection = S6RC_CONNECTION_ZERO, .monitor = TEXTMESSAGE_SENDER_ZERO, .monitor_verbosity = 0, .perms = 0 }
+
+extern tain_t client_answer_tto ;
+extern client_t *client_head ;
+extern uint32_t client_connections ;
+
+extern void client_yoink (client_t **) ;
+extern int client_prepare_iopause (client_t *, tain_t *, iopause_fd *, uint32_t *) ;
+extern int client_flush (client_t *, iopause_fd const *) ;
+extern int client_add (int, uint8_t) ;
+
+extern void client_setdeadline (client_t *) ;
+
+
+ /* Monitors */
+
+extern uint32_t client_monitors ;
+extern int monitor_init (client_t *, int) ;
+extern int monitor_finish (client_t *) ;
+extern int monitor_put (unsigned int, char const *, size_t) ;
+
+#endif
diff --git a/src/server/clientrules.c b/src/server/clientrules.c
new file mode 100644
index 0000000..4050b2f
--- /dev/null
+++ b/src/server/clientrules.c
@@ -0,0 +1,118 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <skalibs/posixplz.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/types.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/cdb.h>
+
+#include <s6/accessrules.h>
+
+#include "clientrules.h"
+
+static unsigned int rulestype = 0 ;
+static char const *rules = 0 ;
+static int cdbfd = -1 ;
+static struct cdb cdbmap = CDB_ZERO ;
+
+void clientrules_init (unsigned int type, char const *s)
+{
+ rulestype = type ;
+ rules = s ;
+ if (rulestype == 2)
+ {
+ cdbfd = open_readb(rules) ;
+ if (cdbfd < 0) strerr_diefu3sys(111, "open ", rules, " for reading") ;
+ if (cdb_init(&cdbmap, cdbfd) < 0)
+ strerr_diefu2sys(111, "cdb_init ", rules) ;
+ }
+}
+
+void clientrules_reload ()
+{
+ int fd ;
+ struct cdb c = CDB_ZERO ;
+ if (rulestype != 2) break ;
+ fd = open_readb(rules) ;
+ if (fd < 0) break ;
+ if (cdb_init(&c, fd) < 0)
+ {
+ fd_close(fd) ;
+ break ;
+ }
+ cdb_free(&cdbmap) ;
+ fd_close(cdbfd) ;
+ cdbfd = fd ;
+ cdbmap = c ;
+}
+
+static inline uint8_t parse_env (char const *const *envp)
+{
+ uint8_t perms = 0 ;
+ for (; *envp ; envp++)
+ {
+ if (str_start(*envp, "query=")) perms |= 1 ;
+ if (str_start(*envp, "monitor=")) perms |= 2 ;
+ if (str_start(*envp, "change=")) perms |= 4 ;
+ if (str_start(*envp, "event=")) perms |= 8 ;
+ if (str_start(*envp, "admin=")) perms |= 16 ;
+ }
+ return perms ;
+}
+
+int clientrules_check (int fd, uint8_t *perms)
+{
+ s6_accessrules_params_t params = S6_ACCESSRULES_PARAMS_ZERO ;
+ s6_accessrules_result_t result = S6_ACCESSRULES_ERROR ;
+ uid_t uid ;
+ gid_t gid ;
+
+ if (getpeereid(fd, &uid, &gid) < 0)
+ {
+ if (verbosity) strerr_warnwu1sys("getpeereid") ;
+ return 0 ;
+ }
+
+ switch (rulestype)
+ {
+ case 1 :
+ result = s6_accessrules_uidgid_fs(uid, gid, rules, &params) ; break ;
+ case 2 :
+ result = s6_accessrules_uidgid_cdb(uid, gid, &cdbmap, &params) ; break ;
+ default : break ;
+ }
+ if (result != S6_ACCESSRULES_ALLOW)
+ {
+ if (verbosity && (result == S6_ACCESSRULES_ERROR))
+ strerr_warnw1sys("error while checking rules") ;
+ return 0 ;
+ }
+ if (params.exec.len && verbosity)
+ {
+ char fmtuid[UID_FMT] ;
+ char fmtgid[GID_FMT] ;
+ fmtuid[uid_fmt(fmtuid, uid)] = 0 ;
+ fmtgid[gid_fmt(fmtgid, gid)] = 0 ;
+ strerr_warnw4x("unused exec string in rules for uid ", fmtuid, " gid ", fmtgid) ;
+ }
+ if (params.env.s)
+ {
+ size_t n = byte_count(params.env.s, params.env.len, '\0') ;
+ char const *envp[n+1] ;
+ if (!env_make(envp, n, params.env.s, params.env.len))
+ {
+ if (verbosity) strerr_warnwu1sys("env_make") ;
+ s6_accessrules_params_free(&params) ;
+ return 0 ;
+ }
+ envp[n] = 0 ;
+ *perms = parse_env(envp, perms) ;
+ }
+ s6_accessrules_params_free(&params) ;
+ return !!perms ;
+}
diff --git a/src/server/clientrules.h b/src/server/clientrules.h
new file mode 100644
index 0000000..1080d7a
--- /dev/null
+++ b/src/server/clientrules.h
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#ifndef S6RCD_CLIENTRULES_H
+#define S6RCD_CLIENTRULES_H
+
+#include <stdint.h>
+
+
+ /* Client rules */
+
+extern void clientrules_init (unsigned int, char const *) ;
+extern void clientrules_reload (void) ;
+extern int clientrules_check (int, uint8_t *) ;
+
+#endif
diff --git a/src/server/command.c b/src/server/command.c
new file mode 100644
index 0000000..c52dc8b
--- /dev/null
+++ b/src/server/command.c
@@ -0,0 +1,43 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <sys/uio.h>
+
+#include <skalibs/textmessage.h>
+#include <skalibs/posixishard.h>
+
+#include "client.h"
+#include "command.h"
+
+static int answer (client_t *c, char e)
+{
+ if (!textmessage_put(&c->connection.out, &e, 1)) return 0 ;
+ client_setdeadline(c) ;
+ return 1 ;
+}
+
+static int do_query (client_t *c, char const *s, size_t len)
+{
+ if (!len--) return (errno = EPROTO, 0) ;
+}
+
+static int do_monitor (client_t *c, char const *s, size_t len)
+{
+}
+
+int command_handle (struct iovec const *v, void *aux)
+{
+ client_t *c = aux ;
+ char const *s = v->iov_base ;
+ size_t len = v->iov_len ;
+ if (!len--) return (errno = EPROTO, 0) ;
+ switch (*s++)
+ {
+ case 'Q' : return do_query(c, s, len) ;
+ case 'M' : return do_monitor(c, s, len) ;
+ case '?' : return do_change(c, s, len) ;
+ case '!' : return do_event(c, s, len) ;
+ case '#' : return do_admin(c, s, len) ;
+ default : return do_error(c) ;
+ }
+}
diff --git a/src/server/command.h b/src/server/command.h
new file mode 100644
index 0000000..4edfb09
--- /dev/null
+++ b/src/server/command.h
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#ifndef S6RCD_COMMAND_H
+#define S6RCD_COMMAND_H
+
+#include <sys/uio.h>
+
+#include <skalibs/textmessage.h>
+
+ /* Client commands */
+
+int command_handle (struct iovec const *, void *) ;
+
+#endif
diff --git a/src/server/db.h b/src/server/db.h
new file mode 100644
index 0000000..efe799d
--- /dev/null
+++ b/src/server/db.h
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#ifndef S6RCD_DB_H
+#define S6RCD_DB_H
+
+ /* Service database */
+
+extern int s6rcd_db_load (s6rc_db_t *, char const *) ;
+extern int s6rcd_db_read_sizes (s6rc_db_sizes_t *, char const *) ;
+
+#endif
diff --git a/src/server/ep.c b/src/server/ep.c
new file mode 100644
index 0000000..2fa0c0e
--- /dev/null
+++ b/src/server/ep.c
@@ -0,0 +1,128 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <skalibs/strerr2.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+
+#include <s6-rc/event.h>
+#include "ep.h"
+
+typedef struct epelem_s epelem_t, *epelem_t_ref ;
+struct epelem_s
+{
+ uint32_t owner ;
+ ep_func_t_ref f ;
+ void *aux ;
+} ;
+#define EPELEM_ZERO { .owner = 0, .f = 0, .aux = 0 }
+
+typedef struct eplist_s eplist_t, *eplist_t_ref ;
+struct eplist_s
+{
+ char const *name ;
+ genalloc list ; /* epelem_t */
+} ;
+#define EPLIST_ZERO { .name = 0, .list = GENALLOC_ZERO }
+
+typedef struct epset_s epset_t, *epset_t_ref ;
+struct epset_s
+{
+ gensetdyn data ; /* eplist_t */
+ avltree map ;
+} ;
+#define EPSET_ZERO { .data = GENSETDYN_ZERO, .map = AVLTREE_ZERO }
+
+static void *epset_dtok (uint32_t d, void *x)
+{
+ return (void *)&GENSETDYN_P(eplist_t, (gensetdyn *)x, d)->name ;
+}
+
+static int epset_cmp (void const *a, void const *b, void *x)
+{
+ (void)x ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+static epset_t ep[EVENTTYPE_PHAIL] =
+{
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[0].data) },
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[1].data) },
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[2].data) },
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[3].data) },
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[4].data) },
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[5].data) },
+ { .data = GENSETDYN_ZERO, .map = AVLTREE_INIT(8, 3, 8, &epset_dtok, &epset_cmp, &ep[6].data) },
+} ;
+
+static void eplist_free (void *p)
+{
+ eplist_t *x = p ;
+ genalloc_free(epelem_t, &x->list) ;
+}
+
+void ep_free ()
+{
+ for (s6rc_eventtype_t i = 0 ; i < S6RC_EVENTTYPE_PHAIL ; i++)
+ {
+ avltree_free(&ep[i].map) ;
+ gensetdyn_deepfree(&ep[i].data, &eplist_free) ;
+ avltree_init(&ep[i].map, 8, 3, 8, &epset_dtok, &epset_cmp, &ep[i].data) ;
+ }
+}
+
+int ep_add (uint8_t type, char const *name, uint32_t owner, ep_func_t_ref f, void *aux)
+{
+ epelem_t ee = { .owner = owner, .f = f, .aux = aux } ;
+ uint32_t d ;
+ if (type >= S6RC_EVENTTYPE_PHAIL) return (errno = EINVAL, 0) ;
+ if (!avltree_search(&ep[type].map, name, &d))
+ {
+ eplist_t newlist = { .name = name, .list = GENALLOC_ZERO } ;
+ if (!gensetdyn_new(&ep[type].data, &d)) return 0 ;
+ *GENSETDYN_P(eplist_t, &ep[type].data, d) = newlist ;
+ if (!avltree_insert(&ep[type].map, d))
+ {
+ gensetdyn_delete(&ep[type].data, d) ;
+ return 0 ;
+ }
+ }
+ return genalloc_catb(epelem_t, &GENSETDYN_P(eplist_t, &ep[type].data, d)->list, &ee) ;
+}
+
+void ep_delete (uint8_t type, char const *name, uint32_t owner, ep_func_t_ref f, void *aux)
+{
+ uint32_t d ;
+ if (type >= S6RC_EVENTTYPE_PHAIL) return ;
+ if (!avltree_search(&ep[type].map, name, &d)) return ;
+
+ epelem_t ee = { .owner = owner, .f = f, .aux = aux } ;
+ genalloc *g = &GENSETDYN_P(eplist_t, &ep[type].data, d)->list ;
+ epelem_t *list = genalloc_s(epelem_t, g) ;
+ size_t n = genalloc_len(epelem_t, g) ;
+ size_t i = 0 ;
+ for (; i < n ; i++) if (list == ee) break ;
+ if (i < n) list[i] = list[--n] ;
+ if (!n)
+ {
+ genalloc_free(epelem_t, g) ;
+ avltree_delete(&ep[type].map, name) ;
+ gensetdyn_delete(eplist_t, &ep[type].data, d) ;
+ }
+}
+
+void ep_run (s6rc_event_t const *ev)
+{
+ uint32_t d ;
+ if (!avltree_search(&ep[ev->type].map, ev->name, &d)) return ;
+
+ genalloc *g = &GENSETDYN_P(eplist_t, &ep[ev->type].data, d)->list ;
+ epelem_t const *list = genalloc_s(epelem_t, g) ;
+ size_t n = genalloc_len(epelem_t, g) ;
+ for (size_t i = 0 ; i < n ; i++)
+ (*list[i].f)(ev, list[i].owner, list[i].aux) ;
+}
diff --git a/src/server/ep.h b/src/server/ep.h
new file mode 100644
index 0000000..96e6c59
--- /dev/null
+++ b/src/server/ep.h
@@ -0,0 +1,21 @@
+/* ISC license. */
+
+#ifndef S6RCD_EP_H
+#define S6RCD_EP_H
+
+#include <stdint.h>
+
+#include <s6-rc/event.h>
+
+
+ /* Event processor: the dynamic part */
+
+typedef void ep_func_t (s6rc_event_t const *, uint32_t, void *) ;
+typedef ep_func_t *ep_func_t_ref ;
+
+extern void ep_free (void) ;
+extern int ep_add (uint8_t, char const *, uint32_t, ep_func_t_ref, void *) ;
+extern void ep_delete (uint8_t, char const *, uint32_t, ep_func_t_ref, void *) ;
+extern void ep_run (s6rc_event_t const *) ;
+
+#endif
diff --git a/src/server/ev.c b/src/server/ev.c
new file mode 100644
index 0000000..c201191
--- /dev/null
+++ b/src/server/ev.c
@@ -0,0 +1,47 @@
+/* ISC license. */
+
+#include <skalibs/genqdyn.h>
+
+#include <s6-rc/event.h>
+#include "ev.h"
+
+
+/* Event queue */
+
+static genqdyn evq = GENQDYN_INIT(s6rc_event_t, 0, 1) ; /* only clean when it's empty */
+
+int ev_enqueue (s6rc_event_t const *ev)
+{
+ return genqdyn_push(&evq, ev) ;
+}
+
+int ev_pop (s6rc_event_t *ev)
+{
+ if (!genqdyn_n(&evq)) return 0 ;
+ *ev = *GENQDYN_PEEK(s6rc_event_t, &evq) ;
+ return genqdyn_pop(&evq) ;
+}
+
+
+ /* Event processing (builtins) */
+
+void ev_handle (s6rc_event_t const *ev)
+{
+ switch (ev->type)
+ {
+ case S6RC_EVENTTYPE_WANTED_STATE_DOWN :
+ break ;
+ case S6RC_EVENTTYPE_WANTED_STATE_UP :
+ break ;
+ case S6RC_EVENTTYPE_CURRENT_STATE_DOWN :
+ break ;
+ case S6RC_EVENTTYPE_CURRENT_STATE_UP :
+ break ;
+ case S6RC_EVENTTYPE_TRANSITION_STOP :
+ break ;
+ case S6RC_EVENTTYPE_TRANSITION_START :
+ break ;
+ case S6RC_EVENTTYPE_CUSTOM :
+ break ;
+ }
+}
diff --git a/src/server/ev.h b/src/server/ev.h
new file mode 100644
index 0000000..c267a40
--- /dev/null
+++ b/src/server/ev.h
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#ifndef S6RCD_EV_H
+#define S6RCD_EV_H
+
+#include <s6-rc/event.h>
+
+
+ /* Event queue */
+
+extern int ev_enqueue (s6rc_event_t const *) ;
+extern int ev_pop (s6rc_event_t *) ;
+
+
+ /* Event processor: the static part */
+
+extern void ev_handle (s6rc_event_t const *) ;
+
+#endif
diff --git a/src/server/livedir.h b/src/server/livedir.h
new file mode 100644
index 0000000..1d6d106
--- /dev/null
+++ b/src/server/livedir.h
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#ifndef S6RCD_LIVEDIR_H
+#define S6RCD_LIVEDIR_H
+
+ /* Live directory */
+
+extern int s6rcd_livedir_init (char const *, char const *, char const *, char const *, int *)
+extern int s6rcd_livesubdir_create (char *, char const *, char const *) ;
+
+#endif
diff --git a/src/server/livedir_init.c b/src/server/livedir_init.c
new file mode 100644
index 0000000..f252dba
--- /dev/null
+++ b/src/server/livedir_init.c
@@ -0,0 +1,124 @@
+/* ISC license. */
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/webipc.h>
+
+#include "s6rcd.h"
+
+#ifdef NAME_MAX
+# define S6RC_NAME_MAX NAME_MAX
+#else
+# define S6RC_NAME_MAX 63
+#endif
+
+static inline int mksubdirs (char *s)
+{
+ size_t n = strlen(s) ;
+ size_t i = 0 ;
+ for (; i < n ; i++)
+ {
+ if (s[i] == '/')
+ {
+ int r ;
+ s[i] = 0 ;
+ r = mkdir(s, 0755) ;
+ s[i] = '/' ;
+ if (r < 0 && errno != EEXIST) break ;
+ }
+ }
+ return i >= n ;
+}
+
+int s6rcd_livedir_init (char const *livedir, char const *scandir, char const *prefix, char const *compiled, int *sock)
+{
+ size_t plen = strlen(prefix) ;
+ size_t llen = strlen(livedir) ;
+ struct stat st ;
+ char ltmp[llen + 9] ;
+ if (plen >= S6RC_NAME_MAX)
+ strerr_dief1x(100, "prefix is too long") ;
+
+ memcpy(ltmp, livedir, llen + 1) ;
+ if (!mksubdirs(ltmp, len))
+ strerr_diefu2sys(111, "create subdirectories of ", s) ;
+ if (mkdir(ltmp, 0755) < 0 && errno != EEXIST)
+ strerr_diefu2sys(111, "mkdir ", ltmp) ;
+
+ memcpy(ltmp + llen, "/s", 3) ;
+ *sock = ipc_stream() ;
+ if (*sock < 0)
+ strerr_diefu1sys(111, "create socket") ;
+ if (ipc_bind_reuse(*sock, ltmp) < 0)
+ strerr_diefu2sys(111, "bind to ", ltmp) ;
+
+ memcpy(ltmp + llen + 1, "scandir", 8) ;
+ if (lstat(ltmp, &st) < 0)
+ {
+ if (errno != ENOENT)
+ strerr_diefu2sys(111, "lstat ", ltmp) ;
+ if (symlink(scandir, ltmp) < 0)
+ strerr_diefu4sys(111, "symlink ", ltmp, " to ", scandir) ;
+ }
+ else
+ {
+ size_t slen = strlen(scandir) ;
+ char stmp[slen + 1] ;
+ if (!S_ISLNK(st.st_mode))
+ strerr_dief3x(100, "file ", ltmp, " exists and is not a symbolic link") ;
+ if (readlink(ltmp, stmp, slen + 1) < 0)
+ strerr_diefu2sys(111, "readlink ", ltmp) ;
+ if (strncmp(scandir, stmp, slen + 1))
+ strerr_dief4x(100, "provided scandir ", scandir, " does not match the contents of existing ", ltmp) ;
+ }
+
+ memcpy(ltmp + llen + 1, "prefix", 7) ;
+ if (stat(ltmp, &st) < 0)
+ {
+ if (errno != ENOENT)
+ strerr_diefu2sys(111, "stat ", ltmp) ;
+ if (!openwritenclose_unsafe(ltmp, prefix, strlen(prefix)))
+ strerr_diefu2sys(111, "write prefix to ", ltmp) ;
+ }
+ else
+ {
+ if (!S_IFREG(st.st_mode))
+ strerr_dief3x(100, "file ", ltmp, " exists and is not a regular file") ;
+ if (st.st_size != plen)
+ strerr_dief4x(100, "provided prefix ", prefix, " does not match the contents of existing ", ltmp) ;
+ {
+ char stmp[plen] ;
+ ssize_t r = openreadnclose(ltmp, ptmp, plen) ;
+ if (r != plen)
+ strerr_diefu2sys(111, "read ", ltmp) ;
+ if (memcmp(ptmp, prefix, plen))
+ strerr_dief4x(100, "provided prefix ", prefix, " does not match the contents of existing ", ltmp) ;
+ }
+ }
+
+ memcpy(ltmp + llen + 1, "live", 5) ;
+ if (lstat(ltmp, &st) < 0)
+ {
+ char name[12] ;
+ if (errno != ENOENT) strerr_diefu2sys(111, "lstat ", ltmp) ;
+ if (!s6rcd_livesubdir_create(name, live, compiled))
+ strerr_diefu2sys(111, "create live subdirectory in ", live) ;
+ if (symlink(name, ltmp) < 0)
+ strerr_diefu4sys(111, "symlink ", ltmp, " to ", name) ;
+ return 0 ;
+ }
+ if (!S_ISLNK(st.st_mode))
+ strerr_dief3x(100, "livesubdir ", ltmp, " exists and is not a symbolic link") ;
+ if (stat(ltmp, &st) < 0)
+ strerr_diefu2sys(111, "stat ", ltmp) ;
+ if (!S_ISDIR(st.st_mode))
+ strerr_dief4x(100, "livesubdir ", ltmp, " exists and is not a symbolic link", " to a directory") ;
+ return 1 ;
+}
diff --git a/src/server/livesubdir_create.c b/src/server/livesubdir_create.c
new file mode 100644
index 0000000..ff989aa
--- /dev/null
+++ b/src/server/livesubdir_create.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <skalibs/djbunix.h>
+
+#include "s6rcd.h"
+
+int s6rcd_livesubdir_create (char *name, char const *live, char const *compiled)
+{
+ size_t llen = strlen(live) ;
+ size_t clen = strlen(compiled) ;
+ char cfn[clen + 13] ;
+ char lfn[llen + 25] ;
+ memcpy(lfn, live, llen) ;
+ memcpy(lfn + llen, "/live:XXXXXX", 13) ;
+ if (!mkdtemp(lfn)) return 0 ;
+
+ memcpy(lfn + llen + 12, "/compiled", 10) ;
+ if (symlink(compiled, lfn) < 0) return 0 ;
+ strerr_diefu4sys(111, "symlink ", compiled, " to ", realfn + llen + 1) ;
+
+ memcpy(cfn, compiled, clen) ;
+ memcpy(cfn + clen, "/servicedirs", 13) ;
+ memcpy(lfn + llen + 13, "servicedirs", 12) ;
+ if (!hiercopy(cfn, lfn)) return 0 ;
+
+ lfn[llen + 12] = 0 ;
+ if (chmod(lfn, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) return 0 ;
+ memcpy(name, lfn + llen + 1, 12) ;
+ return 1 ;
+}
diff --git a/src/server/main.h b/src/server/main.h
new file mode 100644
index 0000000..84d6688
--- /dev/null
+++ b/src/server/main.h
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#ifndef S6RCD_MAIN_H
+#define S6RCD_MAIN_H
+
+ /* Exported by the main file, s6-rcd.c */
+
+typedef struct globalflags_s globalflags_t, *globalflags_t_ref ;
+struct globalflags_s
+{
+ uint8_t lameduck : 1 ;
+} ;
+
+extern globalflags_t flags ;
+extern tain_t lameduckdeadline ;
+extern unsigned int verbosity ;
+
+#endif
diff --git a/src/server/s6-rcd.c b/src/server/s6-rcd.c
new file mode 100644
index 0000000..0fcfb77
--- /dev/null
+++ b/src/server/s6-rcd.c
@@ -0,0 +1,139 @@
+/* ISC license. */
+
+#include <fcntl.h>
+
+#include <skalibs/types.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/subgetopt.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+
+#include "s6-rcd.h"
+
+#define USAGE "s6-rcd [ -v verbosity ] [ -1 ] [ -t timeout ] [ -T lameducktimeout ] [ -i rulesdir | -x rulesfile ] [ -l livedir ]"
+#define dieusage() strerr_dieusage(100, USAGE) ;
+
+unsigned int verbosity = 1 ;
+globalflags_t flags =
+{
+ .lameduck = 0 ;
+} ;
+
+tain_t lameduckdeadline = TAIN_INFINITE_RELATIVE ;
+
+int main (int argc, char const *const *argv)
+{
+ int spfd, sock ;
+ PROG = "s6-rcd" ;
+
+ {
+ char const *rules = 0 ;
+ unsigned int rulestype = 0 ;
+ int flag1 = 0 ;
+ unsigned int t = 0, T = 0 ;
+ subgetopt_t l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "v:1c:n:i:x:t:T:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ;
+ case '1' : flag1 = 1 ; break ;
+ case 'i' : rules = l.arg ; rulestype = 1 ; break ;
+ case 'x' : rules = l.arg ; rulestype = 2 ; break ;
+ case 't' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ;
+ case 'T' : if (!uint0_scan(l.arg, &T)) dieusage() ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ if (t) tain_from_millisecs(&client_answer_tto, t) ;
+ if (T) tain_from_millisecs(&lameduckdeadline, T) ;
+ if (!rulestype) strerr_dief1x(100, "no access rights specified!") ;
+ if (flag1)
+ {
+ if (fcntl(1, F_GETFD) < 0)
+ strerr_dief1sys(100, "called with option -1 but stdout said") ;
+ }
+ else close(1) ;
+
+ spfd = signals_init() ;
+ clientrules_init(rulestype, rules) ;
+ sock = livedir_init() ;
+ if (!tain_now_set_stopwatch_g())
+ strerr_diefu1sys(111, "initialize clock") ;
+ if (flag1)
+ {
+ fd_write(1, "\n", 1) ;
+ fd_close(1) ;
+ }
+ }
+
+ for (;;)
+ {
+ iopause_fd x[2 + client_connections + client_monitors] ;
+ tain_t deadline ;
+ uint32_t j = 2 ;
+ int r = 1 ;
+
+ {
+ s6rc_event_t ev ;
+ while (ev_pop(&ev)) { ev_handle(&ev) ; ep_run(&ev) ; }
+ }
+
+ tain_add_g(&deadline, &tain_infinite_relative) ;
+ if (flags.lameduck) deadline = lameduckdeadline ;
+
+ x[0].fd = spfd ;
+ x[0].events = IOPAUSE_READ ;
+ x[1].fd = sock ;
+ x[1].events = !flags.lameduck ? IOPAUSE_READ : 0 ;
+
+ for (client_t *c = client_head ; c ; c = c->next)
+ if (client_prepare_iopause(c, &deadline, x, &j)) r = 0 ;
+
+ if (r) /* clean buffers */
+ {
+ if (flags.lameduck) break ;
+ if (flags.dbupdate && db_update())
+ {
+ flags.dbupdate = 0 ;
+ continue ;
+ }
+ }
+
+ r = iopause_g(x, j, &deadline) ;
+ if (r < 0) strerr_diefu1sys(111, "iopause") ;
+
+ if (!r)
+ {
+ if (flags.lameduck && !tain_future(&lameduckdeadline)) break ;
+ for (client_t *c = client_head ; c ; c = c ? c->next : client_head)
+ if (!tain_future(c->deadline)) client_yoink(&c) ;
+ continue ;
+ }
+
+ if (x[0].revents & IOPAUSE_READ) signals_handle() ;
+
+ for (client_t *c = client_head ; c ; c = c ? c->next : client_head)
+ if (!client_flush(c, x)) client_yoink(&c) ;
+
+ for (client_t *c = client_head ; c ; c = c ? c->next : client_head)
+ if (!client_read(c, x)) client_yoink(&c) ;
+
+ if (x[1].revents & IOPAUSE_READ)
+ {
+ uint8_t perms = 0 ;
+ int dummy ;
+ int fd = ipc_accept_nb(x[1].fd, 0, 0, &dummy) ;
+ if (fd < 0)
+ if (!error_isagain(errno)) strerr_diefu1sys(111, "accept connection") ;
+ else continue ;
+ else if (!clientrules_check(fd, &perms)) fd_close(fd) ;
+ else client_add(fd, perms) ;
+ }
+ }
+
+ return 0 ;
+}
diff --git a/src/server/s6-rcd.h b/src/server/s6-rcd.h
new file mode 100644
index 0000000..2436c54
--- /dev/null
+++ b/src/server/s6-rcd.h
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#ifndef S6RCD_H
+#define S6RCD_H
+
+#include "ep.h"
+#include "ev.h"
+#include "transition.h"
+#include "clientrules.h"
+#include "client.h"
+#include "command.h"
+#include "livedir.h"
+#include "db.h"
+#include "signals.h"
+#include "main.h"
+
+#endif
diff --git a/src/server/signals.c b/src/server/signals.c
new file mode 100644
index 0000000..ce26bdb
--- /dev/null
+++ b/src/server/signals.c
@@ -0,0 +1,89 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include <skalibs/strerr2.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/tai.h>
+
+#include "clientrules.h"
+#include "transition.h"
+#include "main.h"
+#include "signals.h"
+
+static inline void activate_lameduck (void)
+{
+ if (!flags.lameduck)
+ {
+ flags.lameduck = 1 ;
+ tain_add_g(&lameduckdeadline, &lameduckdeadline) ;
+ }
+}
+
+static inline void reload_config (void)
+{
+ clientrules_reload() ;
+}
+
+static inline void wait_children (void)
+{
+ for (;;)
+ {
+ unsigned int j = 0 ;
+ int wstat ;
+ pid_t r = wait_nohang(&wstat) ;
+ if (r < 0)
+ if (errno == ECHILD) break ;
+ else strerr_diefu1sys(111, "wait for children") ;
+ else if (!r) break ;
+ for (; j < ntransitions ; j++) if (transitions[j].pid == r) break ;
+ if (r < ntransitions)
+ {
+ transition_t tr = transitions[j] ;
+ transitions[j] = transitions[--ntransitions] ;
+ if (!WIFSIGNALED(wstat) && !WEXITSTATUS(wstat))
+ transition_success(&tr) ;
+ else
+ transition_failure(&tr, !!WIFSIGNALED(wstat), WIFSIGNALED(wstat) ? WTERMSIG(wstat) : WEXITSTATUS(wstat)) ;
+ }
+ }
+}
+
+void signals_handle ()
+{
+ for (;;)
+ {
+ switch (selfpipe_read())
+ {
+ case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+ case 0 : return ;
+ case SIGTERM :
+ activate_lameduck() ;
+ break ;
+ case SIGHUP :
+ reload_config() ;
+ break ;
+ case SIGCHLD :
+ wait_children() ;
+ break ;
+ }
+ }
+}
+
+int signals_init ()
+{
+ int fd = selfpipe_init() ;
+ if (fd < 0) strerr_diefu1sys(111, "selfpipe_init") ;
+ if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+ {
+ sigset_t set ;
+ sigemptyset(&set) ;
+ sigaddset(&set, SIGTERM) ;
+ sigaddset(&set, SIGHUP) ;
+ if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ;
+ }
+ return fd ;
+}
diff --git a/src/server/signals.h b/src/server/signals.h
new file mode 100644
index 0000000..b9c167f
--- /dev/null
+++ b/src/server/signals.h
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#ifndef S6RCD_SIGNALS_H
+#define S6RCD_SIGNALS_H
+
+ /* Signal management */
+
+extern int signals_init (void) ;
+extern void signals_handle (void) ;
+
+#endif
diff --git a/src/server/transition.h b/src/server/transition.h
new file mode 100644
index 0000000..fcddfa6
--- /dev/null
+++ b/src/server/transition.h
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#ifndef S6RCD_TRANSITION_H
+#define S6RCD_TRANSITION_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+
+ /* Transitions */
+
+typedef struct transition_s transition_t, *transition_t_ref ;
+struct transition_s
+{
+ pid_t pid ;
+} ;
+#define TRANSITION_ZERO { .pid = 0 }
+
+extern uint32_t ntransitions ;
+transitions_t *transitions ;
+
+#endif