s6

Mirror/fork of https://skarnet.org/software/s6/
git clone https://ccx.te2000.cz/git/s6
Log | Files | Refs | README | LICENSE

commit f481d14e9d9c5b2d57995aa0d9094500676e013e
parent 518e74eda99bed17eca188305659874b35ae158e
Author: Laurent Bercot <ska-skaware@skarnet.org>
Date:   Thu,  7 Jan 2021 23:14:17 +0000

 Add s6-usertree-maker

Diffstat:
MNEWS | 2++
Mdoc/index.html | 6++++++
Adoc/s6-usertree-maker.html | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdoc/upgrade.html | 2++
Mpackage/deps.mak | 3+++
Mpackage/modes | 1+
Mpackage/targets.mak | 4++++
Asrc/usertree/deps-exe/s6-usertree-maker | 1+
Asrc/usertree/s6-usertree-maker.c | 302++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 561 insertions(+), 0 deletions(-)

diff --git a/NEWS b/NEWS @@ -14,6 +14,8 @@ SIGQUIT semantics changed to immediately bail. SIGINT is now trapped and forwarded to the service's process group. - New binary: s6-svperms, implementing a split permissions model. (By default, everything is the same as before.) + - New binary: s6-usertree-maker, creating service directories +for supervision trees managed by users. In 2.9.2.0 diff --git a/doc/index.html b/doc/index.html @@ -261,6 +261,12 @@ synchronization</a>. <li><a href="ucspilogd.html">The <tt>ucspilogd</tt> program</a></li> </ul> +<h4> Management of user supervision trees </h4> + +<ul> +<li><a href="s6-usertree-maker.html">The <tt>s6-usertree-maker</tt> program</a></li> +</ul> + <h4> Timed lock acquisition </h4> <ul> diff --git a/doc/s6-usertree-maker.html b/doc/s6-usertree-maker.html @@ -0,0 +1,240 @@ +<html> + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-usertree-maker program</title> + <meta name="Description" content="s6: the s6-usertree-maker program" /> + <meta name="Keywords" content="s6 command s6-usertree-maker user supervision tree s6-svscan" /> + <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="//skarnet.org/software/">Software</a><br /> +<a href="//skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-usertree-maker program </h1> + +<p> +s6-usertree-maker creates a <a href="servicedir.html">service directory</a> +implementing a service that runs a <a href="s6-svscan.html">s6-svscan</a> +instance owned by a given user, on a <a href="scandir.html">scan directory</a> +belonging to that user. It is meant to help admins deploy systems where +each user has their own supervision subtree, rooted in the main supervision +tree owned by root. +</p> + +<p> + Alternatively, s6-usertree-maker can create source definition directories +for the <a href="//skarnet.org/software/s6-rc/">s6-rc</a> service manager. +</p> + +<h2> Interface </h2> + +<pre> + s6-usertree-maker \ + [ -d <em>userscandir</em> ] \ + [ -p <em>path</em> ] \ + [ -E <em>envdir</em> [ -e <em>var</em> -e <em>var</em> ... ] ] \ + [ -r <em>service</em>/<em>logger</em>/<em>pipeline</em> ] \ + [ -l <em>loguser</em> ] \ + [ -t <em>stamptype</em> ] \ + [ -n <em>nfiles</em> ] \ + [ -s <em>filesize</em> ] \ + [ -S <em>maxsize</em> ] \ + user logdir dir +</pre> + +<p> +s6-usertree-maker creates a service directory in <em>dir</em>, that launches +a supervision tree as user <em>user</em> on scan directory <em>userscandir</em>, +with a catch-all logger logging the tree's output via +<a href="s6-log.html">s6-log</a> to the <em>logdir</em> directory. +</p> + +<h2> Exit codes </h2> + +<ul> + <li> 0: success </li> + <li> 100: wrong usage </li> + <li> 111: system call failed </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-d</tt>&nbsp;<em>userscandir</em>&nbsp;: the supervision tree will be run +on the <em>userscandir</em> directory. <em>userscandir</em> is subject to variable +substitution (see below). Default is <strong><tt>${HOME}/service</tt></strong>. </li> <br /> + + <li> <tt>-p</tt>&nbsp;<em>path</em>&nbsp;: the supervision tree will be run with a +PATH environment variable set to <em>path</em>. <em>path</em> is subject to variable +substitution. Default is <strong><tt>/usr/bin:/bin</tt></strong>, or whatever has been +given to the <tt>--with-default-path</tt> option to skalibs' configure script. </li> <br /> + + <li> <tt>-E</tt>&nbsp;<em>envdir</em>&nbsp;: the supervision tree will be run with +the environment variables defined in the directory <em>envdir</em>, which will be +read via <a href="s6-envdir.html">s6-envdir</a> without options. By default, no +envdir is defined and the supervision tree will only be run with the basic +environment variables listed below. </li> <br /> + + <li> <tt>-e</tt>&nbsp;<em>var</em>&nbsp;: Perform variable substitution on <em>var</em>. +This option is repeatable, and only makes sense when the <tt>-E</tt> option is also +given. For every <em>var</em> listed via a <tt>-e</tt> option, the contents of +<em>var</em> will be subjected to variable substitution before the supervision tree +is run. This is only useful if <em>var</em> is defined in <em>envdir</em>, as a +template, that is then instanced for <em>user</em> when the service is run. By +default, only the PATH environment variable, customizable via <tt>-p</tt>, is +subjected to variable substitution. </li> <br /> + + <li> <tt>-r</tt>&nbsp;<em>service</em>/<em>logger</em>/<em>pipeline</em>&nbsp;: +create <a href="//skarnet.org/software/s6-rc">s6-rc</a> source definition directories. +When this option is given, <em>dir</em> is not created as a service directory, but +as a directory containing two services: <em>dir</em>/<em>service</em> and +<em>dir</em>/<em>logger</em>, and <em>dir</em> is suitable as a source argument to +<a href="//skarnet.org/software/s6-rc/s6-rc-compile.html">s6-rc-compile</a>. The +<tt>/</tt><em>pipeline</em> part can be omitted, but if it is present, <em>pipeline</em> +is used as a name for a bundle containing both <em>service</em> and <em>logger</em>. +When this option is not given, <em>dir</em> is a regular service directory for direct +inclusion (or linking) in the parent scan directory (and the catch-all logger for +the user subtree is declared in <em>dir</em><tt>/log</tt>). </li> <br /> + + <li> <tt>-l</tt>&nbsp;<em>loguser</em>&nbsp;: run the catch-all logger of the user +subdirectory as user <em>loguser</em>. Default is <strong><tt>root</tt></strong>. </li> <br /> + + <li> <tt>-t</tt>&nbsp;<em>stamptype</em>&nbsp;: how +logs are timestamped by the catch-all logger. 0 means no +timestamp, 1 means +<a href="http://cr.yp.to/libtai/tai64.html">external TAI64N format</a>, +2 means +<a href="http://www.iso.org/iso/home/standards/iso8601.htm">ISO 8601 format</a>, +and 3 means both. Default is <strong><tt>1</tt></strong>. </li> <br /> + + <li> <tt>-n</tt>&nbsp;<em>nfiles</em>&nbsp;: maximum number of archive files +in <em>logdir</em>. Default is <strong><tt>10</tt></strong>. </li> <br /> + + <li> <tt>-s</tt>&nbsp;<em>filesize</em>&nbsp;: maximum size of the <tt>current</tt> +file (and archive files) in <em>logdir</em>. Default is <strong><tt>1000000</tt></strong>. </li> <br /> + + <li> <tt>-S</tt>&nbsp;<em>maxsize</em>&nbsp;: maximum total size of the +archives in the <em>logdir</em>. Default is <strong><tt>0</tt></strong>, +meaning no limits apart from those enforced by the <tt>-n</tt> and +<tt>-s</tt> options. </li> <br /> +</ul> + +<h2> Operation of the service </h2> + +<p> + When the service is started, its run script will execute the following +operations: +</p> + +<ul> + <li> Clear all its environment variables, except PATH. This prevents +any data leak from the parent supervision tree into the user subtree. </li> + <li> Fill its environment with data related to <em>user</em>: + <ul> + <li> USER is set to <em>user</em> </li> + <li> HOME is set to <em>user</em>'s home directory </li> + <li> UID is set to <em>user</em>'s uid </li> + <li> GID is set to <em>user</em>'s primary gid </li> + <li> GIDLIST is set to <em>user</em>'s supplementary groups list </li> + </ul> </li> + <li> If the service has been created with the <tt>-E</tt> option to s6-usertree-maker: + <ul> + <li> Add all the variables defined in <em>envdir</em> to its environment </li> + <li> For every variable <em>var</em> given via a <tt>-e</tt> option, subject +<em>var</em> to substitution with the USER, HOME, UID, GID and GIDLIST variables +(see below). </li> + </ul> </li> + <li> Set the PATH environment variable to <em>path</em>, subjected to +variable substitution. </li> + <li> Execute into <a href="s6-svscan.html">s6-svscan</a>, running in +<em>userscandir</em> (which is first subjected to variable substitution). </li> +</ul> + +<p> + The service is logged: its stderr and stdout are piped to a +<a href="s6-log.html">s6-log</a> process running as <em>loguser</em> and +writing to the <em>logdir</em> directory. This logger is the catch-all logger +for the supervision tree owned by <em>user</em>; it is recommended to make +<em>loguser</em> distinct from <em>user</em>, and to have <em>logdir</em> +in a place that is <strong>not</strong> under the control of <em>user</em>. +If <em>user</em> wants to keep control of their logs, they can declare a +logger for each of their services. +</p> + +<h2> Variable substitution </h2> + +<p> + When the service starts, the USER, HOME, UID, GID and GIDLIST +environment variables are deduced from <em>user</em>'s identity. +The value of those variables may be used in a few configuration +knobs: +</p> + +<ul> + <li> The value of <em>userscandir</em>: it is likely that the +scan directory belonging to <em>user</em> resides under <em>user</em>'s +home directory. Or under <tt>/run/user/${UID}</tt>, or some similar +scheme. </li> + <li> The PATH environment variable, declared in <em>path</em>: it is +often useful to prepend the default system PATH with a user-specific +directory that hosts that user's binaries. For instance, you may want +the PATH to be set as something like <tt>${HOME}/bin:/usr/bin:/bin</tt>. </li> + <li> Any variable declared in <em>envdir</em> and given as an argument +to a <tt>-e</tt> option to s6-usertree-maker. If <em>envdir</em> is a +template valid for all users, it may contain variables that depends on +user-specific data: for instance, the XDG_CONFIG_HOME variable may be +set to <tt>${HOME}/.config</tt>. </li> +</ul> + +<p> + When the strings <tt>${USER}</tt>, <tt>${HOME}</tt>, <tt>${UID}</tt>, +<tt>${GID}</tt>, or <tt>${GIDLIST}</tt> appear in the value for +<em>userscandir</em>, <em>path</em>, or any of the <em>var</em> +variables, they are substituted with the corresponding value of the USER, +HOME, UID, GID, or GIDLIST environment variable instead. +</p> + +<p> + For instance, if no <tt>-d</tt> option is provided, the default value +for <em>userscandir</em> is <tt>${HOME}/service</tt>. If the provided +<em>user</em> is <tt>ska</tt> and ska's home directory is <tt>/home/ska</tt>, +then <a href="s6-svscan.html">s6-svscan</a> will be run on +<tt>/home/ska/service</tt>. +</p> + +<h2> Example </h2> + +<pre> + s6-usertree-maker -d '/run/user/${UID}/service' -p '${HOME}/bin:/usr/bin:/bin' -E /etc/user-env -e XDG_CONFIG_HOME -l catchlog ska /var/log/usertree/ska usertree-ska +</pre> + +<p> + creates a service directory in <tt>usertree-ska</tt> declaring a service that +starts a supervision tree on <tt>/run/user/1000/service</tt> if ska has uid 1000, +with <tt>/home/ska/bin:/usr/bin/bin</tt> as its PATH if ska's home directory is +<tt>/home/ska</tt>, and with all the environment variables declared in +<tt>/etc/user-env</tt>, among which the XDG_CONFIG_HOME variable is processed +for variable substitution. The supervision tree has a catch-all logger running +as user catchlog, and storing its data in the <tt>/var/log/usertree/ska</tt> +directory. +</p> + +<h2> Notes </h2> + +<ul> + <li> s6-usertree-maker makes use of the fact that +<a href="//skarnet.org/software/execline/">execline</a> scripts are much +easier to generate programmatically and to harden than shell scripts, so it is only +built if s6 is built with <a href="//skarnet.org/software/execline/">execline</a> +support - i.e. the <tt>--disable-execline</tt> switch has <em>not</em> been given +to configure. </li> +</ul> + +</body> +</html> diff --git a/doc/upgrade.html b/doc/upgrade.html @@ -41,6 +41,8 @@ directories. <a href="s6-supervise.html">s6-supervise</a> now always starts it child as a session leader. </li> <li> Split permissions on service control are now officially supported. New binary: <a href="s6-svperms.html">s6-svperms</a>. </li> + <li> New program that creates service directories to run a supervision tree +managed by a user: <a href="s6-usertree-maker.html">s6-usertree-maker</a>. </li> </ul> <h2> in 2.9.2.0 </h2> diff --git a/package/deps.mak b/package/deps.mak @@ -133,6 +133,7 @@ src/supervision/s6-svstat.o src/supervision/s6-svstat.lo: src/supervision/s6-svs src/supervision/s6-svwait.o src/supervision/s6-svwait.lo: src/supervision/s6-svwait.c src/supervision/s6-svlisten.h src/supervision/s6_svlisten_loop.o src/supervision/s6_svlisten_loop.lo: src/supervision/s6_svlisten_loop.c src/supervision/s6-svlisten.h src/include/s6/ftrigr.h src/include/s6/s6-supervise.h src/supervision/s6_svlisten_signal_handler.o src/supervision/s6_svlisten_signal_handler.lo: src/supervision/s6_svlisten_signal_handler.c src/supervision/s6-svlisten.h +src/usertree/s6-usertree-maker.o src/usertree/s6-usertree-maker.lo: src/usertree/s6-usertree-maker.c src/include/s6/config.h s6-accessrules-cdb-from-fs: EXTRA_LIBS := -lskarnet ${SOCKET_LIB} ${SYSCLOCK_LIB} s6-accessrules-cdb-from-fs: src/conn-tools/s6-accessrules-cdb-from-fs.o @@ -253,3 +254,5 @@ s6-svstat: EXTRA_LIBS := -lskarnet ${SYSCLOCK_LIB} s6-svstat: src/supervision/s6-svstat.o ${LIBS6} s6-svwait: EXTRA_LIBS := -lskarnet ${SOCKET_LIB} ${SYSCLOCK_LIB} ${SPAWN_LIB} s6-svwait: src/supervision/s6-svwait.o src/supervision/s6_svlisten_loop.o ${LIBS6} +s6-usertree-maker: EXTRA_LIBS := -lskarnet +s6-usertree-maker: src/usertree/s6-usertree-maker.o diff --git a/package/modes b/package/modes @@ -61,3 +61,4 @@ s6-fdholder-setdump 0755 s6-fdholder-setdumpc 0755 s6-fdholder-transferdump 0755 s6-fdholder-transferdumpc 0755 +s6-usertree-maker 0755 diff --git a/package/targets.mak b/package/targets.mak @@ -58,3 +58,7 @@ s6-setuidgid LIBEXEC_TARGETS := s6lockd-helper LIB_DEFS := S6=s6 + +ifneq ($(EXECLINE_LIB),) +BIN_TARGETS += s6-usertree-maker +endif diff --git a/src/usertree/deps-exe/s6-usertree-maker b/src/usertree/deps-exe/s6-usertree-maker @@ -0,0 +1 @@ +-lskarnet diff --git a/src/usertree/s6-usertree-maker.c b/src/usertree/s6-usertree-maker.c @@ -0,0 +1,302 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <skalibs/config.h> +#include <skalibs/uint64.h> +#include <skalibs/types.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/stralloc.h> +#include <skalibs/djbunix.h> +#include <skalibs/skamisc.h> + +#include <execline/config.h> + +#include <s6/config.h> + +#define USAGE "s6-usertree-maker [ -d userscandir ] [ -p path ] [ -E envdir [ -e var ... ] ] [ -r service/logger[/pipeline] ] [ -l loguser ] [ -t stamptype ] [ -n nfiles ] [ -s filesize ] [ -S maxsize ] user logdir dir" +#define dieusage() strerr_dieusage(100, USAGE) + +#define VARS_MAX 64 + +static mode_t mask ; +static stralloc sa = STRALLOC_ZERO ; + +static inline void write_run (char const *runfile, char const *user, char const *sc, char const *path, char const *userenvdir, char const *const *vars, size_t varlen) +{ + buffer b ; + char buf[2048] ; + int fd = open_trunc(runfile) ; + if (fd < 0) strerr_diefu3sys(111, "open ", runfile, " for writing") ; + buffer_init(&b, &buffer_write, fd, buf, 2048) ; + if (!string_quote(&sa, user, strlen(user))) goto errq ; + if (buffer_puts(&b, "#!" EXECLINE_SHEBANGPREFIX "execlineb -P\n" + EXECLINE_EXTBINPREFIX "fdmove -c 2 1\n" + EXECLINE_EXTBINPREFIX "emptyenv -p\n" + EXECLINE_EXTBINPREFIX "export USER ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_puts(&b, "\n" + S6_EXTBINPREFIX "s6-envuidgid -i -- ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_puts(&b, "\n" + EXECLINE_EXTBINPREFIX "backtick -in HOME { " + EXECLINE_EXTBINPREFIX "homeof ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_put(&b, " }\n", 3) < 0) goto err ; + sa.len = 0 ; + if (userenvdir) + { + if (!string_quote(&sa, userenvdir, strlen(userenvdir))) goto errq ; + if (buffer_puts(&b, S6_EXTBINPREFIX "s6-envdir -i -- ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_put(&b, "\n", 1) < 0) goto err ; + sa.len = 0 ; + } + if (buffer_puts(&b, EXECLINE_EXTBINPREFIX "multisubstitute\n{\n" + " importas -i USER USER\n" + " importas -i HOME HOME\n" + " importas -i UID UID\n" + " importas -i GID GID\n" + " importas -i GIDLIST GIDLIST\n}\n") < 0) goto err ; + if (userenvdir && varlen) + { + if (buffer_puts(&b, EXECLINE_EXTBINPREFIX "multisubstitute\n{\n") < 0) goto err ; + for (size_t i = 0 ; i < varlen ; i++) + { + if (!string_quote(&sa, vars[i], strlen(vars[i]))) goto errq ; + if (buffer_puts(&b, " importas -D \"\" -- ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_put(&b, " ", 1) < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_put(&b, "\n", 1) < 0) goto err ; + sa.len = 0 ; + } + if (buffer_put(&b, "}\n", 2) < 0) goto err ; + for (size_t i = 0 ; i < varlen ; i++) + { + if (!string_quote(&sa, vars[i], strlen(vars[i]))) goto errq ; + if (buffer_puts(&b, EXECLINE_EXTBINPREFIX "export ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_put(&b, " ${", 3) < 0 + || buffer_put(&b, sa.s, sa.len) < 0 + || buffer_put(&b, "}\n", 2) < 0) goto err ; + sa.len = 0 ; + } + } + if (!string_quote(&sa, path, strlen(path))) goto errq ; + if (buffer_puts(&b, EXECLINE_EXTBINPREFIX "export PATH ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0) goto err ; + sa.len = 0 ; + if (!string_quote(&sa, sc, strlen(sc))) goto errq ; + if (buffer_puts(&b, "\n" + S6_EXTBINPREFIX "s6-svscan -d3 -- ") < 0 + || buffer_put(&b, sa.s, sa.len) < 0) goto err ; + sa.len = 0 ; + if (!buffer_putflush(&b, "\n", 1)) goto err ; + fd_close(fd) ; + return ; + err: + strerr_diefu2sys(111, "write to ", runfile) ; + errq: + strerr_diefu1sys(111, "quote string") ; +} + +static inline void write_logrun (char const *runfile, char const *loguser, char const *logdir, unsigned int stamptype, unsigned int nfiles, uint64_t filesize, uint64_t maxsize) +{ + buffer b ; + char buf[1024] ; + char fmt[UINT64_FMT] ; + int fd = open_trunc(runfile) ; + if (fd < 0) strerr_diefu3sys(111, "open ", runfile, " for writing") ; + buffer_init(&b, &buffer_write, fd, buf, 1024) ; + if (buffer_puts(&b, "#!" EXECLINE_SHEBANGPREFIX "execlineb -P\n") < 0) goto err ; + if (loguser) + { + if (buffer_puts(&b, S6_EXTBINPREFIX "s6-setuidgid ") < 0) goto err ; + if (!string_quote(&sa, loguser, strlen(loguser))) strerr_diefu1sys(111, "quote string") ; + if (buffer_put(&b, sa.s, sa.len) < 0 || buffer_put(&b, "\n", 1) < 0) goto err ; + sa.len = 0 ; + } + if (buffer_puts(&b, S6_EXTBINPREFIX "s6-log -bd3 -- ") < 0) goto err ; + if (stamptype & 1 && buffer_put(&b, "t ", 2) < 0) goto err ; + if (stamptype & 2 && buffer_put(&b, "T ", 2) < 0) goto err ; + if (buffer_put(&b, "n", 1) < 0 + || buffer_put(&b, fmt, uint_fmt(fmt, nfiles)) < 0 + || buffer_put(&b, " s", 2) < 0 + || buffer_put(&b, fmt, uint64_fmt(fmt, filesize)) < 0 + || buffer_put(&b, " ", 1) < 0) goto err ; + if (maxsize) + { + if (buffer_put(&b, "S", 1) < 0 + || buffer_put(&b, fmt, uint64_fmt(fmt, maxsize)) < 0 + || buffer_put(&b, " ", 1) < 0) goto err ; + } + if (!string_quote(&sa, logdir, strlen(logdir))) strerr_diefu1sys(111, "quote string") ; + if (buffer_put(&b, sa.s, sa.len) < 0 || buffer_put(&b, "\n", 1) < 0) goto err ; + sa.len = 0 ; + + if (!buffer_flush(&b)) goto err ; + fd_close(fd) ; + return ; + err: + strerr_diefu2sys(111, "write to ", runfile) ; +} + +static void write_service (char const *dir, char const *user, char const *sc, char const *logger, char const *path, char const *userenvdir, char const *const *vars, size_t varlen) +{ + size_t dirlen = strlen(dir) ; + char fn[dirlen + 17] ; + memcpy(fn, dir, dirlen) ; + memcpy(fn + dirlen, "/notification-fd", 17) ; + if (!openwritenclose_unsafe(fn, "3\n", 2)) strerr_diefu2sys(111, "write to ", fn) ; + memcpy(fn + dirlen + 1, "run", 4) ; + write_run(fn, user, sc, path, userenvdir, vars, varlen) ; + if (logger) + { + struct iovec v[2] = { { .iov_base = (char *)logger, .iov_len = strlen(logger) }, { .iov_base = "\n", .iov_len = 1 } } ; + memcpy(fn + dirlen + 1, "type", 5) ; + if (!openwritenclose_unsafe(fn, "longrun\n", 8)) strerr_diefu2sys(111, "write to ", fn) ; + memcpy(fn + dirlen + 1, "producer-for", 13) ; + if (!openwritevnclose_unsafe(fn, v, 2)) strerr_diefu2sys(111, "write to ", fn) ; + } + else + { + if (chmod(fn, mask | ((mask >> 2) & 0111)) < 0) + strerr_diefu2sys(111, "chmod ", fn) ; + } +} + +static void write_logger (char const *dir, char const *user, char const *logdir, unsigned int stamptype, unsigned int nfiles, uint64_t filesize, uint64_t maxsize, char const *service, char const *pipelinename) +{ + size_t dirlen = strlen(dir) ; + char fn[dirlen + 17] ; + if (mkdir(dir, 0755) < 0) strerr_diefu2sys(111, "mkdir ", dir) ; + memcpy(fn, dir, dirlen) ; + memcpy(fn + dirlen, "/notification-fd", 17) ; + if (!openwritenclose_unsafe(fn, "3\n", 2)) strerr_diefu2sys(111, "write to ", fn) ; + memcpy(fn + dirlen + 1, "run", 4) ; + write_logrun(fn, user, logdir, stamptype, nfiles, filesize, maxsize) ; + if (service) + { + struct iovec v[2] = { { .iov_base = (char *)service, .iov_len = strlen(service) }, { .iov_base = "\n", .iov_len = 1 } } ; + memcpy(fn + dirlen + 1, "type", 5) ; + if (!openwritenclose_unsafe(fn, "longrun\n", 8)) strerr_diefu2sys(111, "write to ", fn) ; + memcpy(fn + dirlen + 1, "consumer-for", 13) ; + if (!openwritevnclose_unsafe(fn, v, 2)) strerr_diefu2sys(111, "write to ", fn) ; + if (pipelinename) + { + v[0].iov_base = (char *)pipelinename ; + v[0].iov_len = strlen(pipelinename) ; + memcpy(fn + dirlen + 1, "pipeline-name", 14) ; + if (!openwritevnclose_unsafe(fn, v, 2)) strerr_diefu2sys(111, "write to ", fn) ; + } + } + else + { + if (chmod(fn, mask | ((mask >> 2) & 0111)) < 0) + strerr_diefu2sys(111, "chmod ", fn) ; + } +} + +int main (int argc, char *const *argv) +{ + char const *userscandir = "${HOME}/service" ; + char const *path = SKALIBS_DEFAULTPATH ; + char const *userenvdir = 0 ; + char *rcinfo[3] = { 0, 0, 0 } ; + char const *loguser = 0 ; + unsigned int stamptype = 1 ; + unsigned int nfiles = 10 ; + uint64_t filesize = 1000000 ; + uint64_t maxsize = 0 ; + size_t dirlen ; + size_t varlen = 0 ; + char const *vars[VARS_MAX] ; + PROG = "s6-usertree-maker" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, (char const *const *)argv, "d:p:E:e:r:l:t:n:s:S:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'd' : userscandir = l.arg ; break ; + case 'p' : path = l.arg ; break ; + case 'E' : userenvdir = l.arg ; break ; + case 'e' : + if (varlen >= VARS_MAX) strerr_dief1x(100, "too many -v variables") ; + if (strchr(l.arg, '=')) strerr_dief2x(100, "invalid variable name: ", l.arg) ; + vars[varlen++] = l.arg ; + break ; + case 'r' : rcinfo[0] = (char *)l.arg ; break ; + case 'l' : loguser = l.arg ; break ; + case 't' : if (!uint0_scan(l.arg, &stamptype)) dieusage() ; break ; + case 'n' : if (!uint0_scan(l.arg, &nfiles)) dieusage() ; break ; + case 's' : if (!uint640_scan(l.arg, &filesize)) dieusage() ; break ; + case 'S' : if (!uint640_scan(l.arg, &maxsize)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (argc < 3) dieusage() ; + if (argv[1][0] != '/') strerr_dief1x(100, "logdir must be absolute") ; + if (userscandir[0] != '/' && !str_start(userscandir, "${HOME}/")) + strerr_dief1x(100, "userscandir must be absolute or start with ${HOME}/") ; + if (stamptype > 3) strerr_dief1x(100, "stamptype must be 0, 1, 2 or 3") ; + if (rcinfo[0]) + { + if (strchr(rcinfo[0], '\n')) + strerr_dief2x(100, "newlines", " are forbidden in s6-rc names") ; + if (rcinfo[0][0] == '/') + strerr_dief2x(100, "service", " name cannot be empty") ; + rcinfo[1] = strchr(rcinfo[0], '/') ; + if (!rcinfo[1]) strerr_dief1x(100, "argument to -r must be: service/logger or service/logger/pipeline") ; + *rcinfo[1]++ = 0 ; + if (!rcinfo[1][0]) strerr_dief1x(100, "argument to -r must be: service/logger or service/logger/pipeline") ; + if (rcinfo[1][0] == '/') + strerr_dief2x(100, "logger", " name cannot be empty") ; + rcinfo[2] = strchr(rcinfo[1], '/') ; + if (rcinfo[2]) + { + *rcinfo[2]++ = 0 ; + if (!rcinfo[2][0]) strerr_dief2x(100, "pipeline", " name cannot be empty") ; + if (strchr(rcinfo[2], '/')) strerr_dief2x(100, "slashes", " are forbidden in s6-rc names") ; + } + } + mask = umask(0) ; + umask(mask) ; + mask = ~mask & 0666 ; + + if (mkdir(argv[2], 0755) < 0) strerr_diefu2sys(111, "mkdir ", argv[2]) ; + dirlen = strlen(argv[2]) ; + if (rcinfo[0]) + { + size_t svclen = strlen(rcinfo[0]) ; + size_t loglen = strlen(rcinfo[1]) ; + char dir[dirlen + 2 + svclen > loglen ? svclen : loglen] ; + memcpy(dir, argv[2], dirlen) ; + dir[dirlen] = '/' ; + memcpy(dir + dirlen + 1, rcinfo[0], svclen + 1) ; + if (mkdir(dir, 0755) < 0) strerr_diefu2sys(111, "mkdir ", dir) ; + write_service(dir, argv[0], userscandir, rcinfo[1], path, userenvdir, vars, varlen) ; + memcpy(dir + dirlen + 1, rcinfo[1], loglen + 1) ; + write_logger(dir, loguser, argv[1], stamptype, nfiles, filesize, maxsize, rcinfo[0], rcinfo[2]) ; + } + else + { + char dir[dirlen + 5] ; + memcpy(dir, argv[2], dirlen) ; + memcpy(dir + dirlen, "/log", 5) ; + write_service(argv[2], argv[0], userscandir, 0, path, userenvdir, vars, varlen) ; + write_logger(dir, loguser, argv[1], stamptype, nfiles, filesize, maxsize, 0, 0) ; + } + return 0 ; +}