diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2023-01-09 11:09:14 +0000 |
---|---|---|
committer | Laurent Bercot <ska@appnovation.com> | 2023-01-09 11:09:14 +0000 |
commit | 09750ce3525129a676dec4f579aea3ad2eca1b19 (patch) | |
tree | acae6b5f47914d7ed85bbb8fe9780ed34a85cfd0 | |
parent | 191b46491407632f2431c46eab97dbf5543fa26d (diff) | |
download | s6-09750ce3525129a676dec4f579aea3ad2eca1b19.tar.xz |
Add instances implementation (still needs testing)
Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r-- | doc/index.html | 10 | ||||
-rw-r--r-- | doc/instances.html | 121 | ||||
-rw-r--r-- | doc/s6-instance-control.html | 65 | ||||
-rw-r--r-- | doc/s6-instance-create.html | 92 | ||||
-rw-r--r-- | doc/s6-instance-delete.html | 72 | ||||
-rw-r--r-- | doc/s6-instance-maker.html | 203 | ||||
-rw-r--r-- | doc/s6-log.html | 2 | ||||
-rw-r--r-- | doc/s6-svc.html | 7 | ||||
-rw-r--r-- | doc/s6-usertree-maker.html | 6 | ||||
-rw-r--r-- | doc/servicedir.html | 7 | ||||
-rw-r--r-- | package/deps.mak | 9 | ||||
-rw-r--r-- | package/modes | 3 | ||||
-rw-r--r-- | package/targets.mak | 5 | ||||
-rw-r--r-- | src/instance/deps-exe/s6-instance-control | 1 | ||||
-rw-r--r-- | src/instance/deps-exe/s6-instance-create | 2 | ||||
-rw-r--r-- | src/instance/deps-exe/s6-instance-delete | 2 | ||||
-rw-r--r-- | src/instance/s6-instance-control.c | 79 | ||||
-rw-r--r-- | src/instance/s6-instance-create.c | 97 | ||||
-rw-r--r-- | src/instance/s6-instance-delete.c | 61 | ||||
-rw-r--r-- | src/instance/s6-instance-maker.c | 2 | ||||
-rw-r--r-- | src/libs6/s6_supervise_link_names.c | 3 | ||||
-rw-r--r-- | src/supervision/s6-svc.c | 45 |
22 files changed, 881 insertions, 13 deletions
diff --git a/doc/index.html b/doc/index.html index c0c8403..8b87d71 100644 --- 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 new file mode 100644 index 0000000..0586fdb --- /dev/null +++ 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 new file mode 100644 index 0000000..cd76688 --- /dev/null +++ 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 new file mode 100644 index 0000000..c81dfe4 --- /dev/null +++ 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 new file mode 100644 index 0000000..28eca9c --- /dev/null +++ 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 new file mode 100644 index 0000000..2eb0f4e --- /dev/null +++ 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 index 4f2b99f..a76f9aa 100644 --- 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 index 62e93f9..b8d3c4d 100644 --- 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 index b62727a..29175d6 100644 --- 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 index 4ae06c2..89a272d 100644 --- 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 index 7aad077..c881c5e 100644 --- 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 index a0ee4ae..a76c1e1 100644 --- 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 index 2f6f81a..8e58122 100644 --- 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 new file mode 100644 index 0000000..e7187fe --- /dev/null +++ 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 new file mode 100644 index 0000000..08815d9 --- /dev/null +++ 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 new file mode 100644 index 0000000..08815d9 --- /dev/null +++ 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 new file mode 100644 index 0000000..899ab89 --- /dev/null +++ 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 new file mode 100644 index 0000000..2e97618 --- /dev/null +++ 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 new file mode 100644 index 0000000..fc1337a --- /dev/null +++ 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 index 20f557f..e70e569 100644 --- 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 index 9af573f..de8e5c5 100644 --- 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 index 7e00f17..5982248 100644 --- 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] ; |