commit 09750ce3525129a676dec4f579aea3ad2eca1b19
parent 191b46491407632f2431c46eab97dbf5543fa26d
Author: Laurent Bercot <ska-skaware@skarnet.org>
Date: Mon, 9 Jan 2023 11:09:14 +0000
Add instances implementation (still needs testing)
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat:
22 files changed, 881 insertions(+), 13 deletions(-)
diff --git a/doc/index.html b/doc/index.html
@@ -271,6 +271,16 @@ synchronization</a>.
<li><a href="s6-usertree-maker.html">The <tt>s6-usertree-maker</tt> program</a></li>
</ul>
+<h4> Management of dynamic instances </h4>
+
+<ul>
+<li>An <a href="instances.html">overview</a> of dynamic instantiation in s6</li>
+<li><a href="s6-instance-maker.html">The <tt>s6-instance-maker</tt> program</a></li>
+<li><a href="s6-instance-create.html">The <tt>s6-instance-create</tt> program</a></li>
+<li><a href="s6-instance-delete.html">The <tt>s6-instance-delete</tt> program</a></li>
+<li><a href="s6-instance-control.html">The <tt>s6-instance-control</tt> program</a></li>
+</ul>
+
<h4> Timed lock acquisition </h4>
<ul>
diff --git a/doc/instances.html b/doc/instances.html
@@ -0,0 +1,121 @@
+<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: dynamic instantiation</title>
+ <meta name="Description" content="s6: dynamic instantiation" />
+ <meta name="Keywords" content="s6 instances dynamic instantiation" />
+ <!-- <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> Dynamic instantiation under s6 </h1>
+
+<p>
+ A <em>instanced service</em> is a parameterized service that you want to
+run several copies of, with only the parameter changing. Each copy of the
+service is called an <em>instance</em>.
+</p>
+
+<p>
+ With s6, a <a href="servicedir.html">service directory</a> can only
+handle one process at a time. So, if we want instanced services, there
+will have to be one service directory per instance, always.
+</p>
+
+<p>
+ <em>Static instantiation</em> means that the set of possible instances
+is finite and known in advance. With s6, it means that all the service
+directories for all possible instances are created, typically by a
+preprocessor, and instances are treated like regular services.
+</p>
+
+<p>
+ <em>Dynamic instantiation</em> means that instances are created
+on demand instead of preallocated. Starting with version 2.11.2.0, s6
+provides a few tools to help users set up and manage dynamically
+instanced services.
+</p>
+
+<h2> How to make a dynamically instanced service under s6 </h2>
+
+<ul>
+ <li> Write a template for a service directory that would run under
+<a href="s6-supervise.html">s6-supervise</a>.
+The <tt>run</tt> script should take the name of the instance as its
+first argument; the <tt>finish</tt> script should take the name of the
+instance as its third argument. </li>
+ <li> Call the <a href="s6-instance-maker.html">s6-instance-maker</a> program
+with this template as first argument, and a path <em>dir</em> as second
+argument. <a href="s6-instance-maker.html">s6-instance-maker</a> will create
+a service directory in <em>dir</em>. This is an offline tool: it does not
+interact with any currently active services or supervision trees. </li>
+ <li> Supervise <em>dir</em> by adding it to your regular
+<a href="scandir.html">scan directory</a>. This will be your instanced
+service, but it's not running any instances yet. It is, instead, a nested
+supervision tree - the instanced service is an
+<a href="s6-svscan.html">s6-svscan</a> process that will supervise all the
+instances. </li>
+ <li> Create and delete instances at will with the
+<a href="s6-instance-create.html">s6-instance-create</a> and
+<a href="s6-instance-delete.html">s6-instance-delete</a> programs. Instances
+are regular supervised processes; you can control them with
+<a href="s6-instance-control.html">s6-instance-control</a>. These tools are
+<em>online</em>: they work with live service directories (i.e. that are
+being supervised by <a href="s6-supervise.html">s6-supervise</a>). They
+are really syntactic sugar around the
+<a href="s6-svlink.html">s6-svlink</a>,
+<a href="s6-svunlink.html">s6-svunlink</a> and
+<a href="s6-svc.html">s6-svc</a> programs; they provide you with the
+same functionality but allow you to address individual instances via the
+instanced service name (the service directory running the nested
+supervision tree) and the instance name. </li>
+</ul>
+
+<h2> Internal workings </h2>
+
+<p>
+This section is not normative; users should not rely on it. It is only
+here for informational purposes.
+</p>
+
+<ul>
+ <li> The service directory created by <a href="s6-instance-maker.html">s6-instance-maker</a>
+has two specifics subdirectories in it: <tt>instance</tt> and <tt>instances</tt>. They
+are initially empty, except that the template service directory is stored in
+<tt>instances/.template</tt>. </li>
+ <li> When the service is active, there is an <a href="s6-svscan.html">s6-svscan</a>
+process running on <tt>instance</tt>. </li>
+ <li> <a href="s6-instance-create.html">s6-instance-create</a> makes a copy of
+<tt>instances/.template</tt> into <tt>instances/<em>name</em>/tt>, and
+<a href="s6-svlink.html">s6-svlink</a>s <tt>instances/<em>name</em>/tt> to
+<tt>instance</tt>. When it returns, there is an <a href="s6-supervise.html">s6-supervise</a>
+process running on <tt>instance/<em>name</em></tt>, and the instance may be up
+or not depending on the given options. </li>
+ <li> <a href="s6-instance-control.html">s6-instance-control</a> is syntactic sugar
+around <a href="s6-svc.html">s6-svc</a> on <tt>instance/<em>name</em></tt>. </li>
+ <li> <a href="s6-instance-delete.html">s6-instance-delete</a> is syntactic sugar
+around <a href="s6-svunlink.html">s6-svunlink</a> on <tt>instance/<em>name</em></tt>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> This implementation of dynamic instances may seem expensive: it
+creates one <a href="s6-svscan.html">s6-svscan</a> process per
+instanced service, and one <a href="s6-supervise.html">s6-supervise</a>
+process per instance. However, remember that these processes use very
+little private memory, so having additional copies of them is far less
+expensive than it looks. It's really a convenient way to implement the
+feature by reusing existing code. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-instance-control.html b/doc/s6-instance-control.html
@@ -0,0 +1,65 @@
+<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-instance-control program</title>
+ <meta name="Description" content="s6: the s6-instance-control program" />
+ <meta name="Keywords" content="s6 command s6-instance-control instance dynamic instantiation instanced services control s6-svc" />
+ <!-- <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-instance-control program </h1>
+
+<p>
+s6-instance-control sends commands to a running instance of an
+<a href="instances.html">instanced service</a>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-instance-control [ -wu | -wU | -wd | -wD | -wr | -wR ] [ -T <em>timeout</em> ] [ -abqhkti12pcyoduDUxOr ] <em>servicedir</em> <em>name</em>
+</pre>
+
+<ul>
+ <li> s6-instance-control expects a running, supervised
+<a href="instances.html">instanced service</a> in <em>servicedir</em>,
+as well as an existing instance of this service named <em>name</em>. </li>
+ <li> It sends the given series of commands to the supervisor monitoring
+the <em>name</em> instance. </li>
+ <li> It exits 0. </li>
+</ul>
+
+<h2> Exit codes </h2>
+
+<ul>
+ <li> 0: success </li>
+ <li> 99: with one of the <tt>-w</tt> options, timed out while waiting for the command to complete </li>
+ <li> 100: wrong usage </li>
+ <li> 111: system call failed </li>
+</ul>
+
+<h2> Options </h2>
+
+<p>
+ The options, and the commands they represent, are exactly the same as the ones
+understood by <a href="s6-svc.html">s6-svc</a>.
+</p>
+
+ In fact, s6-instance-control is
+nothing more than a call to <a href="s6-svc.html">s6-svc</a> on the service
+directory representing the <em>name</em> instance. It is syntactic sugar so
+the user does not have to depend on the internal representation of instances
+and the location of instances' service directories.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-instance-create.html b/doc/s6-instance-create.html
@@ -0,0 +1,92 @@
+<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-instance-create program</title>
+ <meta name="Description" content="s6: the s6-instance-create program" />
+ <meta name="Keywords" content="s6 command s6-instance-create instance dynamic instantiation instanced services creation s6-svlink" />
+ <!-- <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-instance-create program </h1>
+
+<p>
+s6-instance-create creates a new instance of a currently supervised
+<a href="instances.html">instanced service</a>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-instance-create [ -d | -D ] [ -P ] [ -f ] [ -t <em>timeout</em> ] <em>servicedir</em> <em>name</em>
+</pre>
+
+<ul>
+ <li> s6-instance-create expects a running, supervised
+<a href="instances.html">instanced service</a> in <em>servicedir</em>.
+This service typically has been created by linking the result of an
+<a href="s6-instance-maker.html">s6-instance-maker</a> invocation into
+an existing <a href="scandir.html">scan directory</a>. </li>
+ <li> s6-instance-create creates a new instance of that service, named
+<em>name</em>. Depending on the given options, it may start it
+immediately, or keep it down until a later
+<a href="s6-instance-control.html">s6-instance-control</a> invocation. </li>
+ <li> It waits for the new instance to be ready to take commands from
+<a href="s6-instance-control.html">s6-instance-control</a>. </li>
+ <li> It exits 0. </li>
+</ul>
+
+<h2> Exit codes </h2>
+
+<ul>
+ <li> 0: success </li>
+ <li> 99: timeout while waiting for the instance supervisor to start </li>
+ <li> 100: wrong usage </li>
+ <li> 111: system call failed </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-d</tt> : down. The instance supervisor will be started, but the instance
+itself will remain down. Any <tt>down</tt> file for the instance will be
+deleted. By default, if neither the <tt>-d</tt> nor <tt>-D</tt> options have
+been given, the supervisor auto-starts the instance as soon as it runs. </li>
+ <li> <tt>-D</tt> : down, and stay down. The instance supervisor will be started,
+but the instance itself will remain down. A <tt>down</tt> file
+will be created for the instance. By default, if neither the <tt>-d</tt> nor <tt>-D</tt> options have
+been given, the supervisor auto-starts the instancece as soon as it runs. </li>
+ <li> <tt>-P</tt> : public. Everyone will be able to subscribe to the
+instance supervisor's notification. By default, only processes running with the same gid
+as the instanced service can subscribe to it. </li>
+ <li> <tt>-f</tt> : force permissions. You should never need to use this
+option, it is only there for testing purposes. </li>
+ <li> <tt>-t <em>timeout</em></tt> : if the instance supervisor has not started
+after <em>timeout</em> milliseconds, s6-instance-create will print a message
+to stderr and exit 99. By default, <em>timeout</em> is 0, which means no time
+limit. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-instance-create is similar to
+<a href="s6-svlink.html">s6-svlink</a>, because it uses the same underlying
+library functions. Under the hood, an instance is a regular service running
+on a supervision tree that is specific to the instanced service, and
+s6-instance-create adds a service directory to that tree and ensures it gets
+supervised. </li>
+ <li> If the template for the service is logged, then s6-instance-create will
+wait until supervisors have been spawned for both the instance and its logger. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-instance-delete.html b/doc/s6-instance-delete.html
@@ -0,0 +1,72 @@
+<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-instance-delete program</title>
+ <meta name="Description" content="s6: the s6-instance-delete program" />
+ <meta name="Keywords" content="s6 command s6-instance-delete instance dynamic instantiation instanced services deletion s6-svunlink" />
+ <!-- <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-instance-delete program </h1>
+
+<p>
+s6-instance-delete deletes an existing instance of a currently supervised
+<a href="instances.html">instanced service</a>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-instance-delete [ -X ] [ -t <em>timeout</em> ] <em>servicedir</em> <em>name</em>
+</pre>
+
+<ul>
+ <li> s6-instance-delete expects a running, supervised
+<a href="instances.html">instanced service</a> in <em>servicedir</em>,
+as well as an existing instance of this service named <em>name</em>
+(it doesn't matter if the instance is up or down). </li>
+ <li> It deletes the <em>name</em> instance. </li>
+ <li> It exits 0. </li>
+</ul>
+
+<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>-X</tt> : don't wait. s6-instance-delete will exit right away,
+without waiting for the instance (and its supervisor) to properly disappear. </li>
+ <li> <tt>-t <em>timeout</em></tt> : if the instance supervisor has not exited
+after <em>timeout</em> milliseconds, s6-instance-delete will still exit.
+By default, <em>timeout</em> is 0, which means no time limit. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-instance-delete is similar to
+<a href="s6-svunlink.html">s6-svunlink</a>, because it uses the same underlying
+library functions. Under the hood, an instance is a regular service running
+on a supervision tree that is specific to the instanced service, and
+s6-instance-delete removes a service directory from that tree. </li>
+ <li> If the template for the service is logged, then s6-instance-delete will
+delete both the instance and its logger. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-instance-maker.html b/doc/s6-instance-maker.html
@@ -0,0 +1,203 @@
+<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-instance-maker program</title>
+ <meta name="Description" content="s6: the s6-instance-maker program" />
+ <meta name="Keywords" content="s6 command s6-instance-maker instance dynamic instantiation instanced services" />
+ <!-- <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-instance-maker program </h1>
+
+<p>
+s6-instance-maker creates a <a href="servicedir.html">service directory</a>
+implementing an <a href="instances.html">instanced service</a>. Give it a
+templated service directory describing how to run an instance of a service,
+and it will create a different service directory that can launch and
+manage several instances; each of these instances will be running a
+copy of the service directory you gave.
+</p>
+
+<p>
+ Alternatively, s6-instance-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-instance-maker \
+ [ -c <em>maxinstances</em> ] \
+ [ -r <em>service</em>[/<em>logger</em>[/<em>pipeline</em>]] ] \
+ [ -u <em>user</em> ] \
+ [ -l <em>loguser</em> ] \
+ [ -L <em>logdir</em> ] \
+ [ -t <em>stamptype</em> ] \
+ [ -n <em>nfiles</em> ] \
+ [ -s <em>filesize</em> ] \
+ [ -S <em>maxsize</em> ] \
+ [ -P <em>prefix</em> ] \
+ template dir
+</pre>
+
+<p>
+s6-instance-maker creates a service directory in <em>dir</em>. The
+created service will be a supervisor for a set of instances (initially empty)
+each running a copy of the service directory given in <em>template</em>.
+</p>
+
+<p>
+s6-instance-maker is an <em>offline</em> tool: it is run before you need
+instances. Once the created service directory is live, i.e. there is a
+supervisor running on it, then you can create, delete, or control
+individual instances via the
+<a href="s6-instance-create.html">s6-instance-create</a>,
+<a href="s6-instance-delete.html">s6-instance-delete</a> and
+<a href="s6-instance-control.html">s6-instance-control</a> <em>online</em>
+tools, that work with active services.
+</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>-c</tt> <em>max</em> : Plan for a maximum of <em>max</em>
+instances. Default is <strong>500</strong>. You can't set it lower than 2 or
+higher than 90000. If your template service directory is logged, it's unadvisable
+to set this above the default. </li> <br>
+
+ <li> <tt>-r</tt> <em>service</em>[/<em>logger</em>[/<em>pipeline</em>]] :
+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 at least one service: <em>dir</em>/<em>service</em>.
+<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>. If
+a <em>logger</em> part is given, then a second service, <em>dir</em>/<em>logger</em>,
+is also created, as a consumer for <em>dir</em>/<em>service</em>, and the <tt>-L</tt>
+option must also be used, to provide a directory to store the logs into. If the
+<tt>/</tt><em>pipeline</em> part is also given, <em>pipeline</em>
+is used as a name for a bundle containing both <em>service</em> and <em>logger</em>.
+When the <tt>-r</tt> option is not given at all, <em>dir</em> is a regular service
+directory for direct inclusion (or linking) in a host
+<a href="scandir.html">scan directory</a>, and if the <tt>-L</tt> option is given
+then the logger for the instance supervisor and all its instances is declared in
+<em>dir</em><tt>/log</tt>). </li> <br>
+
+ <li> <tt>-u</tt> <em>user</em> : run the instance supervisor, and all
+of the instances, as user <em>user</em>. This option should only be used when the
+supervision tree that will host the instanced service is run as root. The default
+is that the service runs as the same user as the host supervision tree. </li> <br>
+
+ <li> <tt>-l</tt> <em>loguser</em> : run the logger of the instance
+supervisor, if any (see <tt>-L</tt> below) as user <em>loguser</em>. This option
+should only be used when the
+supervision tree that will host the instanced service is run as root. The default
+is that the logger runs as the same user as the host supervision tree. </li> <br>
+
+ <li> <tt>-L</tt> <em>logdir</em> : make the service logged via
+<a href="s6-log.html">s6-log</a>, and ensure its log messages go into <em>logdir</em>.
+Error messages from the instance supervisor as well as from every instance will
+be logged to <em>logdir</em>. If this option is not given, these error messages
+will fall through to the host supervision tree's catch-all logger, if any,
+or standard error otherwise. <br>
+The options listed below are only used to configured the logger and are meaningless
+if <tt>-L</tt> is not given. </li> <br>
+
+ <li> <tt>-t</tt> <em>stamptype</em> : how
+logs are timestamped in <em>logdir</em>. 0 means no timestamp, 1 means
+<a href="https://cr.yp.to/libtai/tai64.html">external TAI64N format</a>,
+2 means
+<a href="https://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> <em>nfiles</em> : maximum number of archive files
+in <em>logdir</em>. Default is <strong><tt>10</tt></strong>. </li> <br>
+
+ <li> <tt>-s</tt> <em>filesize</em> : 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> <em>maxsize</em> : maximum total size of the
+archives in <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>
+
+ <li> <tt>-P</tt> <em>prefix</em> : when logging to <em>logdir</em>,
+prefix logged lines with the <em>prefix</em> string. Default is no prefix. </li>
+</ul>
+
+<h2> The templated service directory </h2>
+
+<p>
+ <em>template</em> should be a directory that looks exactly like a service
+directory. It will not be live, i.e. at no point will <em>template</em>
+itself be supervised; instead, a copy of it is stored under <em>dir</em>
+(and a copy of that copy will be used for every instance of the service).
+You can safely move or delete <em>template</em> after running
+s6-instance-maker.
+</p>
+
+<p>
+ To differentiate between instances, the <tt>run</tt> and <tt>finish</tt>
+script in <em>template</em> should take one additional argument (the
+first argument for <tt>run</tt> and the third argument for <tt>finish</tt>).
+This argument will be the name of the instance, as provided by the
+<a href="s6-instance-create.html">s6-instance-create</a> invocation.
+</p>
+
+<h2> Logging </h2>
+
+<p>
+ The service is logged: its stderr and stdout are piped to an
+<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>
+
+<p>
+ If <em>template</em> has a <em>log</em> subdirectory, then each instance
+will have its own dedicated logger. The <tt>run</tt> and <tt>finish</tt>
+scripts for the logger of an instance named <em>name</em> will be called
+with an additional argument of <tt><em>name</em>/log</tt>. They should
+make use of this, to ensure loggers are properly differentiated between
+instances: for instance, it is not possible to run several
+<a href="s6-log.html">s6-log</a> processes on the same log directory,
+so an instance logger script containing an invocation of s6-log on a fixed
+logdir will fail as soon as there are 2 instances.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-instance-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>
+ <li> If s6-instance-maker encounters failure (and exits 111), it does not clean up
+the directories it created. Make sure to always test s6-usertree-maker's return code
+and clean up after it if needed. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-log.html b/doc/s6-log.html
@@ -233,7 +233,7 @@ printed on every output line. For instance, a <tt>pfoobar:</tt> directive means
that the next action directives should prepend every line with <tt>foobar:</tt>
(plus a space) before outputting it. Note that a prefix is always printed
<em>after</em> the timestamps, if any. To remove a prefix for the next action
-directives, use <tt>p</tt>. </li>
+directives, use a standalone <tt>p</tt>. </li>
<li> <strong>!<em>processor</em></strong>: registers
<tt>execlineb -Pc <em>processor</em></tt> as a processor for the next logdirs;
<tt>execlineb</tt> must be found in s6-log's PATH. This directive is only
diff --git a/doc/s6-svc.html b/doc/s6-svc.html
@@ -28,7 +28,7 @@ knowing their PIDs, and without using horrible hacks such as .pid files.
<h2> Interface </h2>
<pre>
- s6-svc [ -wu | -wU | -wd | -wD | -wr | -wR ] [ -T <em>timeout</em> ] [ -abqhkti12pcyoduxOr ] <em>servicedir</em>
+ s6-svc [ -wu | -wU | -wd | -wD | -wr | -wR ] [ -T <em>timeout</em> ] [ -abqhkti12pcyoduDUxOr ] <em>servicedir</em>
</pre>
<p>
@@ -59,8 +59,13 @@ a SIGTERM (by default) then a SIGCONT (to make sure even stopped processes
receive the signal aimed to kill them) and do not restart it.
The SIGTERM default can be changed by editing the <tt>./down-signal</tt>
file in the <a href="servicedir.html">service directory</a>. </li>
+ <li> <tt>-D</tt> : down, and create a <tt>./down</tt> file so the
+service does not restart automatically if the supervisor dies. </li>
<li> <tt>-u</tt> : up. If the supervised process is down, start it.
Automatically restart it when it dies. </li>
+ <li> <tt>-U</tt> : up, and remove any <tt>./down</tt> file that may
+exist, in order to make sure the service is automatically restarted even
+if the supervisor dies. </li>
<li> <tt>-x</tt> : exit. When the service is asked to be down and
the supervised process dies, s6-supervise will exit too. This command should
normally never be used on a working system. Note that if this command is
diff --git a/doc/s6-usertree-maker.html b/doc/s6-usertree-maker.html
@@ -21,7 +21,7 @@
<p>
s6-usertree-maker creates a <a href="servicedir.html">service directory</a>
implementing a service that runs an <a href="s6-svscan.html">s6-svscan</a>
-instance owned by a given user, on a <a href="scandir.html">scan directory</a>
+process 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.
@@ -45,6 +45,7 @@ for the <a href="//skarnet.org/software/s6-rc/">s6-rc</a> service manager.
[ -n <em>nfiles</em> ] \
[ -s <em>filesize</em> ] \
[ -S <em>maxsize</em> ] \
+ [ -P <em>prefix</em> ] \
user logdir dir
</pre>
@@ -123,6 +124,9 @@ file (and archive files) in <em>logdir</em>. Default is <strong><tt>1000000</tt>
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 />
+
+ <li> <tt>-P</tt> <em>prefix</em> : when logging to <em>logdir</em>,
+prefix logged lines with the <em>prefix</em> string. </li> <br />
</ul>
<h2> Operation of the service </h2>
diff --git a/doc/servicedir.html b/doc/servicedir.html
@@ -209,6 +209,13 @@ created by <a href="s6-supervise.html">s6-supervise</a> if it does not exist.
is the rendez-vous point for listeners, where <a href="s6-supervise.html">s6-supervise</a>
will send notifications when the service goes up or down. </li>
+ <li style="margin-bottom:1em"> Optional directories named <tt>instance<tt>
+and <tt>instances</tt>. Those are internal subdirectories created by
+<a href="s6-instance-maker.html">s6-instance maker</a> in a templated service
+directory. Outside of instanced services, these directories should never
+appear, and you should never create them manually. </li>
+
+
<li style="margin-bottom:1em"> An optional service directory named <tt>log</tt>. If it exists and <em>foo</em>
is in a <a href="scandir.html">scandir</a>, and <a href="s6-svscan.html">s6-svscan</a>
runs on that scandir, then <em>two</em> services are monitored: <em>foo</em> and
diff --git a/package/deps.mak b/package/deps.mak
@@ -42,6 +42,9 @@ src/fdholder/s6-fdholder-setdump.o src/fdholder/s6-fdholder-setdump.lo: src/fdho
src/fdholder/s6-fdholder-store.o src/fdholder/s6-fdholder-store.lo: src/fdholder/s6-fdholder-store.c src/include/s6/fdholder.h
src/fdholder/s6-fdholder-transferdump.o src/fdholder/s6-fdholder-transferdump.lo: src/fdholder/s6-fdholder-transferdump.c src/include/s6/fdholder.h
src/fdholder/s6-fdholderd.o src/fdholder/s6-fdholderd.lo: src/fdholder/s6-fdholderd.c src/include/s6/accessrules.h src/include/s6/fdholder.h
+src/instance/s6-instance-control.o src/instance/s6-instance-control.lo: src/instance/s6-instance-control.c src/include/s6/config.h
+src/instance/s6-instance-create.o src/instance/s6-instance-create.lo: src/instance/s6-instance-create.c src/include/s6/supervise.h
+src/instance/s6-instance-delete.o src/instance/s6-instance-delete.lo: src/instance/s6-instance-delete.c src/include/s6/supervise.h
src/instance/s6-instance-maker.o src/instance/s6-instance-maker.lo: src/instance/s6-instance-maker.c src/include/s6/auto.h src/include/s6/config.h
src/libs6/ftrig1_free.o src/libs6/ftrig1_free.lo: src/libs6/ftrig1_free.c src/libs6/ftrig1.h
src/libs6/ftrig1_make.o src/libs6/ftrig1_make.lo: src/libs6/ftrig1_make.c src/libs6/ftrig1.h
@@ -217,6 +220,12 @@ s6-fdholder-transferdump: EXTRA_LIBS := -lskarnet ${SOCKET_LIB} ${SYSCLOCK_LIB}
s6-fdholder-transferdump: src/fdholder/s6-fdholder-transferdump.o ${LIBS6}
s6-fdholderd: EXTRA_LIBS := -lskarnet ${SOCKET_LIB} ${SYSCLOCK_LIB}
s6-fdholderd: src/fdholder/s6-fdholderd.o ${LIBS6}
+s6-instance-control: EXTRA_LIBS := -lskarnet
+s6-instance-control: src/instance/s6-instance-control.o
+s6-instance-create: EXTRA_LIBS := -lskarnet
+s6-instance-create: src/instance/s6-instance-create.o ${LIBS6}
+s6-instance-delete: EXTRA_LIBS := -lskarnet
+s6-instance-delete: src/instance/s6-instance-delete.o ${LIBS6}
s6-instance-maker: EXTRA_LIBS := -lskarnet
s6-instance-maker: src/instance/s6-instance-maker.o libs6auto.a.xyzzy
ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
diff --git a/package/modes b/package/modes
@@ -66,3 +66,6 @@ s6-fdholder-transferdump 0755
s6-fdholder-transferdumpc 0755
s6-usertree-maker 0755
s6-instance-maker 0755
+s6-instance-create 0755
+s6-instance-delete 0755
+s6-instance-control 0755
diff --git a/package/targets.mak b/package/targets.mak
@@ -56,7 +56,10 @@ s6-fdholder-getdump \
s6-fdholder-setdump \
s6-fdholder-transferdump \
s6-applyuidgid \
-s6-setuidgid
+s6-setuidgid \
+s6-instance-create \
+s6-instance-delete \
+s6-instance-control
LIBEXEC_TARGETS := s6lockd-helper
diff --git a/src/instance/deps-exe/s6-instance-control b/src/instance/deps-exe/s6-instance-control
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/instance/deps-exe/s6-instance-create b/src/instance/deps-exe/s6-instance-create
@@ -0,0 +1,2 @@
+${LIBS6}
+-lskarnet
diff --git a/src/instance/deps-exe/s6-instance-delete b/src/instance/deps-exe/s6-instance-delete
@@ -0,0 +1,2 @@
+${LIBS6}
+-lskarnet
diff --git a/src/instance/s6-instance-control.c b/src/instance/s6-instance-control.c
@@ -0,0 +1,79 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/bytestr.h>
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/exec.h>
+
+#include <s6/config.h>
+
+#define USAGE "s6-instance-control [ -wu | -wU | -wd | -wD | -wr | -wR ] [ -T timeout ] [ -abqhkti12pcyroduDUxOX ] service instance"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+#define DATASIZE 63
+
+int main (int argc, char const **argv)
+{
+ char const **fullargv = argv ;
+ size_t namelen ;
+ PROG = "s6-instance-control" ;
+
+ {
+ subgetopt l = SUBGETOPT_ZERO ;
+ unsigned int datalen = 1 ;
+ unsigned int timeout = 0 ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "abqhkti12pcyroduxOT:w:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'a' :
+ case 'b' :
+ case 'q' :
+ case 'h' :
+ case 'k' :
+ case 't' :
+ case 'i' :
+ case '1' :
+ case '2' :
+ case 'p' :
+ case 'c' :
+ case 'y' :
+ case 'r' :
+ case 'o' :
+ case 'd' :
+ case 'u' :
+ case 'D' :
+ case 'U' :
+ case 'x' :
+ case 'O' : if (datalen++ >= DATASIZE) strerr_dief1x(100, "too many commands") ; break ;
+ case 'T' : if (!uint0_scan(l.arg, &timeout)) dieusage() ; break ;
+ case 'w' : if (!memchr("dDuUrR", l.arg[0], 6)) dieusage() ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+
+ if (argc < 2) dieusage() ;
+ namelen = strlen(argv[1]) ;
+ if (!argv[0][0]) strerr_dief1x(100, "invalid service name") ;
+ if (!argv[1][0] || argv[1][0] == '.' || byte_in(argv[1], namelen, " \t\f\r\n", 5) < 5)
+ strerr_dief1x(100, "invalid instance name") ;
+
+ {
+ size_t svlen = strlen(argv[0]) ;
+ char fn[svlen + 11 + namelen] ;
+ memcpy(fn, argv[0], svlen) ;
+ memcpy(fn + svlen, "/instance/", 10) ;
+ memcpy(fn + svlen + 10, argv[1], namelen + 1) ;
+ argv[0] = fn ;
+ argv[1] = 0 ;
+ fullargv[0] = S6_BINPREFIX "s6-svc" ;
+ xexec(fullargv) ;
+ }
+}
diff --git a/src/instance/s6-instance-create.c b/src/instance/s6-instance-create.c
@@ -0,0 +1,97 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <skalibs/bytestr.h>
+#include <skalibs/types.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/tai.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#include <s6/supervise.h>
+
+#define USAGE "s6-instance-create [ -d | -D ] [ -f ] [ -P ] [ -t timeout ] service instancename"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static inline void checkinstanced (char const *s) /* chdirs */
+{
+ int fd, r ;
+ size_t len = strlen(s) ;
+ char fn[len + 10] ;
+ memcpy(fn, s, len) ;
+ memcpy(fn + len, "/instance", 10) ;
+ if (chdir(fn) == -1) strerr_diefu2sys(111, "chdir to ", fn) ;
+ fd = open_read(S6_SVSCAN_CTLDIR "/lock") ;
+ if (fd < 0) strerr_diefu3sys(111, "open ", fn, "/" S6_SVSCAN_CTLDIR "/lock") ;
+ r = fd_islocked(fd) ;
+ if (r < 0) strerr_diefu3sys(111, "check lock on ", fn, "/" S6_SVSCAN_CTLDIR "/lock") ;
+ if (!r) strerr_dief2x(1, "instanced service not running on ", s) ;
+ fd_close(fd) ;
+}
+
+static void cleanup (char const *s)
+{
+ int e = errno ;
+ rm_rf(s) ;
+ errno = e ;
+}
+
+int main (int argc, char const *const *argv)
+{
+ tain tto = TAIN_INFINITE_RELATIVE ;
+ size_t namelen ;
+ uint32_t options = 16 ;
+ PROG = "s6-instance-create" ;
+ {
+ unsigned int t = 0 ;
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "dDfPt:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'd' : options |= 12 ; break ;
+ case 'D' : options |= 4 ; options &= ~8U ; break ;
+ case 'f' : options |= 1 ; break ;
+ case 'P' : options |= 2 ; break ;
+ case 't' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ if (t) tain_from_millisecs(&tto, t) ;
+ }
+ if (argc < 2) dieusage() ;
+
+ namelen = strlen(argv[1]) ;
+ if (!argv[0][0]) strerr_dief1x(100, "invalid service path") ;
+ if (!argv[1][0] || argv[1][0] == '.' || byte_in(argv[1], namelen, " \t\f\r\n", 5) < 5)
+ strerr_dief1x(100, "invalid instance name") ;
+ checkinstanced(argv[0]) ;
+
+ tain_now_set_stopwatch_g() ;
+ tain_add_g(&tto, &tto) ;
+
+ {
+ char sv[namelen + 14] ;
+ memcpy(sv, "../instances/", 13) ;
+ memcpy(sv + 13, argv[1], namelen + 1) ;
+ if (!hiercopy("../instances/.template", sv))
+ {
+ cleanup(sv) ;
+ strerr_diefu5sys(111, "copy ", argv[0], "/instances/.template to ", argv[0], sv+2) ;
+ }
+ if (s6_supervise_link_names_g(".", (char const *const *)&sv, argv + 1, 1, options, &tto) == -1)
+ {
+ cleanup(sv) ;
+ strerr_diefu4sys(errno == ETIMEDOUT ? 99 : 111, "creatre instance of ", argv[0], " named ", argv[1]) ;
+ }
+ }
+ return 0 ;
+}
diff --git a/src/instance/s6-instance-delete.c b/src/instance/s6-instance-delete.c
@@ -0,0 +1,61 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <skalibs/bytestr.h>
+#include <skalibs/types.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/tai.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#include <s6/supervise.h>
+
+#define USAGE "s6-instance-delete [ -X ] [ -t timeout ] service instancename"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv)
+{
+ tain tto = TAIN_INFINITE_RELATIVE ;
+ uint32_t options = 1 ;
+ PROG = "s6-instance-delete" ;
+ {
+ unsigned int t = 0 ;
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "Xt:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'X' : options &= ~1U ; break ;
+ case 't' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ if (t) tain_from_millisecs(&tto, t) ;
+ }
+ if (argc < 2) dieusage() ;
+ if (!argv[0][0]) strerr_dief1x(100, "invalid service path") ;
+ if (!argv[1][0] || argv[1][0] == '.' || byte_in(argv[1], strlen(argv[1]), " \t\f\r\n", 5) < 5)
+ strerr_dief1x(100, "invalid instance name") ;
+
+ tain_now_set_stopwatch_g() ;
+ tain_add_g(&tto, &tto) ;
+
+ {
+ size_t svlen = strlen(argv[0]) ;
+ char sc[svlen + 10] ;
+ memcpy(sc, argv[0], svlen) ;
+ memcpy(sc + svlen, "/instance", 10) ;
+ if (s6_supervise_unlink_names_g(sc, argv + 1, 1, options, &tto) == -1)
+ strerr_diefu4sys(111, "prepare deletion of instance ", argv[1], " of service ", argv[0]) ;
+ }
+
+ return 0 ;
+}
diff --git a/src/instance/s6-instance-maker.c b/src/instance/s6-instance-maker.c
@@ -37,7 +37,7 @@ static int write_run (buffer *b, void *data)
size_t l ;
char fmt[UINT_FMT] ;
l = uint_fmt(fmt, t->maxinstances) ;
- if (buffer_puts(b, EXECLINE_EXTBINPREFIX "execlineb -S1\n\n"
+ if (buffer_puts(b, EXECLINE_EXTBINPREFIX "execlineb -P\n\n"
EXECLINE_EXTBINPREFIX "fdmove -c 2 1\n") < 0) return 0 ;
if (t->user)
{
diff --git a/src/libs6/s6_supervise_link_names.c b/src/libs6/s6_supervise_link_names.c
@@ -42,6 +42,7 @@ static uint16_t registerit (ftrigr_t *a, char *fn, size_t len, gid_t gid, uint32
bit 1: make event/ public
bit 2: don't start the service
bit 3: remove down files after starting supervisors
+ bit 4: allow links to relative paths
*/
int s6_supervise_link_names (char const *scdir, char const *const *servicedirs, char const *const *names, size_t n, uint32_t options, tain const *deadline, tain *stamp)
@@ -118,7 +119,7 @@ int s6_supervise_link_names (char const *scdir, char const *const *servicedirs,
}
fn[len] = 0 ;
strcpy(lname + scdirlen + 1, names[i]) ;
- if (servicedirs[i][0] != '/')
+ if (!(options & 16) && servicedirs[i][0] != '/')
{
rpsa.len = 0 ;
if (sarealpath(&rpsa, servicedirs[i]) < 0 || !stralloc_0(&rpsa)) goto err ;
diff --git a/src/supervision/s6-svc.c b/src/supervision/s6-svc.c
@@ -7,28 +7,31 @@
#include <skalibs/types.h>
#include <skalibs/sgetopt.h>
#include <skalibs/strerr.h>
+#include <skalibs/djbunix.h>
#include <skalibs/exec.h>
#include <s6/config.h>
#include <s6/supervise.h>
-#define USAGE "s6-svc [ -wu | -wU | -wd | -wD | -wr | -wR ] [ -T timeout ] [ -abqhkti12pcyroduxOX ] servicedir"
+#define USAGE "s6-svc [ -wu | -wU | -wd | -wD | -wr | -wR ] [ -T timeout ] [ -abqhkti12pcyroduDUxOX ] servicedir"
#define dieusage() strerr_dieusage(100, USAGE)
#define DATASIZE 63
int main (int argc, char const *const *argv)
{
- char data[DATASIZE+1] = "-" ;
+ size_t len ;
+ int downfile = -1 ;
unsigned int datalen = 1 ;
unsigned int timeout = 0 ;
+ char data[DATASIZE+1] = "-" ;
char updown[3] = "-\0" ;
PROG = "s6-svc" ;
{
subgetopt l = SUBGETOPT_ZERO ;
for (;;)
{
- int opt = subgetopt_r(argc, argv, "abqhkti12pcyroduxOT:w:", &l) ;
+ int opt = subgetopt_r(argc, argv, "abqhkti12pcyroduDUxOT:w:", &l) ;
if (opt == -1) break ;
switch (opt)
{
@@ -55,6 +58,20 @@ int main (int argc, char const *const *argv)
data[datalen++] = opt ;
break ;
}
+ case 'D' :
+ {
+ if (datalen >= DATASIZE) strerr_dief1x(100, "too many commands") ;
+ data[datalen++] = 'd' ;
+ downfile = 1 ;
+ break ;
+ }
+ case 'U' :
+ {
+ if (datalen >= DATASIZE) strerr_dief1x(100, "too many commands") ;
+ data[datalen++] = 'u' ;
+ downfile = 0 ;
+ break ;
+ }
case 'T' : if (!uint0_scan(l.arg, &timeout)) dieusage() ; break ;
case 'w' :
{
@@ -69,13 +86,14 @@ int main (int argc, char const *const *argv)
}
if (!argc) dieusage() ;
if (argc > 1) strerr_warnw1x("ignoring extra arguments") ;
+ len = strlen(argv[0]) ;
+ if (!len) strerr_dief1x(100, "invalid service path") ;
if (updown[1] == 'U' || updown[1] == 'R')
{
- size_t arglen = strlen(argv[0]) ;
- char fn[arglen + 17] ;
- memcpy(fn, argv[0], arglen) ;
- memcpy(fn + arglen, "/notification-fd", 17) ;
+ char fn[len + 17] ;
+ memcpy(fn, argv[0], len) ;
+ memcpy(fn + len, "/notification-fd", 17) ;
if (access(fn, F_OK) < 0)
{
if (errno != ENOENT) strerr_diefu2sys(111, "access ", fn) ;
@@ -84,6 +102,19 @@ int main (int argc, char const *const *argv)
}
}
+ if (downfile >= 0)
+ {
+ char fn[len + 6] ;
+ memcpy(fn, argv[0], len) ;
+ memcpy(fn + len, "/down", 6) ;
+ if (downfile)
+ {
+ if (!openwritenclose_unsafe(fn, "", 0))
+ strerr_diefu2sys(111, "touch ", fn) ;
+ }
+ else unlink_void(fn) ;
+ }
+
if (updown[1])
{
char const *newargv[11] ;