about summary refs log tree commit diff
path: root/doc/libs6
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2015-01-15 20:14:44 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2015-01-15 20:14:44 +0000
commit87c5b2118efcee65eeda3f743d081ea9c2b866d9 (patch)
tree31ca07d6134adf44bc3d58f4fcf4ea8be9cb7dbb /doc/libs6
parentcd2500fcc704287c4994a3253b593593c867913e (diff)
downloads6-87c5b2118efcee65eeda3f743d081ea9c2b866d9.tar.gz
s6-87c5b2118efcee65eeda3f743d081ea9c2b866d9.tar.xz
s6-87c5b2118efcee65eeda3f743d081ea9c2b866d9.zip
Move Unix domain utilities and access control utilites,
as well as the accessrules library, from s6-networking to here
Diffstat (limited to 'doc/libs6')
-rw-r--r--doc/libs6/accessrules.html331
-rw-r--r--doc/libs6/ftrigr.html268
-rw-r--r--doc/libs6/ftrigw.html100
-rw-r--r--doc/libs6/index.html72
-rw-r--r--doc/libs6/s6-ftrigrd.html80
-rw-r--r--doc/libs6/s6lock.html238
-rw-r--r--doc/libs6/s6lockd-helper.html52
-rw-r--r--doc/libs6/s6lockd.html73
8 files changed, 1214 insertions, 0 deletions
diff --git a/doc/libs6/accessrules.html b/doc/libs6/accessrules.html
new file mode 100644
index 0000000..bd98b5f
--- /dev/null
+++ b/doc/libs6/accessrules.html
@@ -0,0 +1,331 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the accessrules library interface</title>
+    <meta name="Description" content="s6: the accessrules library interface" />
+    <meta name="Keywords" content="s6 net accessrules library libs6net unix tcp access control dns ipv4 ipv6" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>accessrules</tt> library interface </h1>
+
+<p>
+ The following functions and structures are declared in the <tt>s6/accessrules.h</tt> header,
+and implemented in the <tt>libs6.a</tt> or <tt>libs6.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>accessrules</tt> is an access control library. It looks up
+a key in a user-specified database, then returns a code depending on
+whether the database allows access (in which case additional information
+can also be returned), denies access, or does not contain the key.
+</p>
+
+<p>
+ <tt>accessrules</tt> has been designed to be easily extensible to any
+database format and any key format.
+</p>
+
+<p>
+ Check the <tt>s6/accessrules.h</tt> header for the exact definitions.
+</p>
+
+<h2> Data structures </h2>
+
+<ul>
+ <li> A <tt>s6_accessrules_result_t</tt> is a scalar that
+can have the following values: S6_ACCESSRULES_ERROR,
+S6_ACCESSRULES_DENY, S6_ACCESSRULES_ALLOW or S6_ACCESSRULES_NOTFOUND. </li>
+ <li> A <tt>s6_accessrules_params_t</tt> is a structure containing two
+<a href="http://skarnet.org/software/skalibs/libstddjb/stralloc.html">strallocs</a>,
+<em>.env</em> and <em>.exec</em>, used to return data contained in the
+database when a key has been allowed. The interpretation of this data is
+application-defined. </li>
+</ul>
+
+<h2> Function types </h2>
+
+<h3> Backend lookups </h3>
+
+<p>
+ A <tt>s6_accessrules_backend_func_t</tt> is the type of a function
+that takes a single key, looks it up in a database, and returns the result.
+Namely:
+</p>
+
+<p>
+<code>s6_accessrules_result_t f (char const *key, unsigned int keylen, void *handle, s6_accessrules_params_t *params) </code>
+</p>
+
+<p>
+ <em>f</em> looks up key <em>key</em> of length <em>keylen</em> in the database
+represented by <em>handle</em> in an implementation-defined way. It returns a
+number that says the key has been allowed, denied or not found, or an error
+occurred. If the key has been allowed, <em>f</em> stores additional information
+from the database into *<em>params</em>.
+</p>
+
+<p>
+ Two s6_accessrules_backend_func_t functions are natively implemented:
+</p>
+
+<ul>
+ <li> <tt>s6_accessrules_backend_fs</tt> takes a <tt>char const *</tt>
+<em>handle</em> and interprets it as a base directory to look up <em>key</em>
+under, in the format understood by
+<a href="../s6-accessrules-cdb-from-fs.html">s6-accessrules-cdb-from-fs</a>. </li>
+ <li> <tt>s6_accessrules_backend_cdb</tt> takes a <tt>struct cdb *</tt>
+<em>handle</em> and looks up <em>key</em> in the
+<a href="http://cr.yp.to/cdb.html">CDB</a> it points to. <em>handle</em> must
+already be mapped to a CDB file. Such a file can be built with the
+<a href="../s6-accessrules-cdb-from-fs.html">s6-accessrules-cdb-from-fs</a>
+utility. </li>
+</ul>
+
+<h3> Frontend key checking </h3>
+
+<p>
+ A <tt>s6_accessrules_keycheck_func_t</tt> is the type of a function that
+takes a user-level key, makes a list of corresponding backend-level keys and
+calls a <tt>s6_accessrules_backend_func_t</tt> function until it finds
+a match. Namely:
+</p>
+
+<p>
+<code>s6_accessrules_result_t f (void const *key, void *handle, s6_accessrules_params_t *params, s6_accessrules_backend_func_t *backend) </code>
+</p>
+
+<p>
+ <em>f</em> derives a list of low-level keys to check from <em>key</em>.
+Then, for each key <em>k</em> of length <em>klen</em> in this list, it calls
+<tt>(*backend)(k, klen, handle, params)</tt>, returning *<em>backend</em>'s result if it
+is not S6_ACCESSRULES_NOTFOUND. If no match can be found in the whole list,
+<em>f</em> finally returns S6_ACCESSRULES_NOTFOUND.
+</p>
+
+<p>
+ Five s6_accessrules_keycheck_func_t functions are natively implemented:
+</p>
+
+<ul>
+ <li>
+<a name="uidgid" />
+ <tt>s6_accessrules_keycheck_uidgid</tt> interprets <em>key</em> as a
+<a href="http://skarnet.org/software/skalibs/libstddjb/">diuint</a>, i.e. a
+structure containing two unsigned ints. The first one is interpreted as an
+uid <em>u</em>, the second one as a gid <em>g</em>. The function first looks
+for a <tt>uid/<em>u</em></tt> match; if it cannot find one, it looks for a
+<tt>gid/<em>g</em></tt> match. If it cannot find one either, it checks
+<tt>uid/default</tt> and returns the result. </li>
+ <li>
+<a name="reversedns" />
+ <tt>s6_accessrules_keycheck_reversedns</tt> interprets <em>key</em>
+as a string containing a FQDN. Then for each suffix <em>k</em> of <em>key</em>,
+starting with <em>key</em> itself and ending with <em>key</em>'s TLD,
+it looks up <tt>reversedns/<em>k</em></tt>. The final dot is excluded from
+<em>k</em>. If no match can be found, the function checks <tt>reversedns/@</tt>
+and returns the result. For instance, if <em>key</em> is "foo.bar.com",
+the following strings are looked up, in that order:
+  <ul>
+   <li> reversedns/foo.bar.com </li>
+   <li> reversedns/bar.com </li>
+   <li> reversedns/com </li>
+   <li> reversedns/@ </li>
+  </ul> </li>
+ <li>
+<a name="ip4" />
+ <tt>s6_accessrules_keycheck_ip4</tt> interprets <em>key</em> as
+4 network-byte-order characters containing an IPv4 address. Then for each
+netmask <em>mask</em> from 32 to 0, it constructs the IPv4 network
+prefix <em>addr</em> corresponding to that address, and looks up
+<tt>ip4/<em>addr</em>_<em>mask</em></tt>. For instance, if <em>key</em>
+is "\300\250\001\007", representing the 192.168.1.7 address, the following
+strings are looked up, in that order:
+ <ul>
+  <li> ip4/192.168.1.7_32 </li>
+  <li> ip4/192.168.1.6_31 </li>
+  <li> ip4/192.168.1.4_30 </li>
+  <li> ip4/192.168.1.0_29 </li>
+  <li> ip4/192.168.0.0_28 </li>
+  <li> ip4/192.168.0.0_27 </li>
+ </ul>
+ and so on, down to:
+ <ul>
+  <li> ip4/192.0.0.0_3 </li>
+  <li> ip4/192.0.0.0_2 </li>
+  <li> ip4/128.0.0.0_1 </li>
+  <li> ip4/0.0.0.0_0 </li>
+ </ul>
+ Note that the <tt>ip4/0.0.0.0_0</tt> string is a catch-all key that
+matches everything. </li>
+ <li>
+<a name="ip6" />
+ <tt>s6_accessrules_keycheck_ip6</tt> interprets <em>key</em> as
+16 network-byte-order characters containing an IPv6 address. Then for each
+netmask <em>mask</em> from 128 to 0, it constructs the IPv6 network
+prefix <em>addr</em> corresponding to that address,
+<strong>in canonical form</strong>,
+and looks up
+<tt>ip6/<em>addr</em>_<em>mask</em></tt>. For instance, if <em>key</em>
+is "*\0\024P@\002\b\003\0\0\0\0\0\0\020\006", representing the
+2a00:1450:4002:803::1006 address, the following
+strings are looked up, in that order:
+ <ul>
+  <li> ip6/2a00:1450:4002:803::1006_128 </li>
+  <li> ip6/2a00:1450:4002:803::1006_127 </li>
+  <li> ip6/2a00:1450:4002:803::1004_126 </li>
+  <li> ip6/2a00:1450:4002:803::1000_125 </li>
+  <li> ip6/2a00:1450:4002:803::1000_124 </li>
+  <li> ip6/2a00:1450:4002:803::1000_123 </li>
+  <li> ip6/2a00:1450:4002:803::1000_122 </li>
+  <li> ip6/2a00:1450:4002:803::1000_121 </li>
+  <li> ip6/2a00:1450:4002:803::1000_120 </li>
+  <li> ip6/2a00:1450:4002:803::1000_119 </li>
+  <li> ip6/2a00:1450:4002:803::1000_118 </li>
+  <li> ip6/2a00:1450:4002:803::1000_117 </li>
+  <li> ip6/2a00:1450:4002:803::1000_116 </li>
+  <li> ip6/2a00:1450:4002:803::1000_115 </li>
+  <li> ip6/2a00:1450:4002:803::1000_114 </li>
+  <li> ip6/2a00:1450:4002:803::1000_113 </li>
+  <li> ip6/2a00:1450:4002:803::_112 </li>
+  <li> ip6/2a00:1450:4002:803::_111 </li>
+ </ul>
+ and so on, down to:
+ <ul>
+  <li> ip6/2a00::_11 </li>
+  <li> ip6/2800::_10 </li>
+  <li> ip6/2800::_9 </li>
+  <li> ip6/2000::_8 </li>
+  <li> ip6/2000::_7 </li>
+  <li> ip6/2000::_6 </li>
+  <li> ip6/2000::_5 </li>
+  <li> ip6/2000::_4 </li>
+  <li> ip6/2000::_3 </li>
+  <li> ip6/::_2 </li>
+  <li> ip6/::_1 </li>
+  <li> ip6/::_0 </li>
+ </ul>
+ Note that the <tt>ip6/::_0</tt> string is a catch-all key that
+matches everything. </li>
+ <li>
+<a name="ip46" />
+ <tt>s6_accessrules_keycheck_ip46</tt> interprets <em>key</em> as a pointer to an
+<a href="http://skarnet.org/software/skalibs/libstddjb/ip46.html">ip46_t</a>, and
+behaves either as s6_accessrules_keycheck_ip6 or s6_accessrules_keycheck_ip4,
+depending on the type of address *<em>key</em> contains. </li>
+</ul>
+
+<h2> Ready-to-use functions </h2>
+
+ Those functions are mostly macros; they're built by associating a frontend
+function with a backend function.
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_uidgid_cdb
+(unsigned int u, unsigned int g, struct cdb *c,
+s6_accessrules_params_t *params) </code> <br />
+Checks the *<em>c</em> CDB database for an authorization for uid <em>u</em>
+and gid <em>g</em>. If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_uidgid_fs
+(unsigned int u, unsigned int g, char const *dir,
+s6_accessrules_params_t *params) </code> <br />
+Checks the <em>dir</em> base directory for an authorization for uid <em>u</em>
+and gid <em>g</em>. If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_reversedns_cdb
+(char const *name, struct cdb *c,
+s6_accessrules_params_t *params) </code> <br />
+Checks the *<em>c</em> CDB database for an authorization for the
+<em>name</em> FQDN. If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_reversedns_fs
+(char const *name, char const *dir,
+s6_accessrules_params_t *params) </code> <br />
+Checks the <em>dir</em> base directory for an authorization for the
+<em>name</em> FQDN. If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_ip4_cdb
+(char const *ip4, struct cdb *c,
+s6_accessrules_params_t *params) </code> <br />
+Checks the *<em>c</em> CDB database for an authorization for the
+<em>ip4</em> IPv4 address (4 network byte order characters).
+If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_ip4_fs
+(char const *ip4, char const *dir,
+s6_accessrules_params_t *params) </code> <br />
+Checks the <em>dir</em> base directory for an authorization for the
+<em>ip4</em> IPv4 address (4 network byte order characters).
+If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_ip6_cdb
+(char const *ip6, struct cdb *c,
+s6_accessrules_params_t *params) </code> <br />
+Checks the *<em>c</em> CDB database for an authorization for the
+<em>ip6</em> IPv6 address (16 network byte order characters).
+If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_ip6_fs
+(char const *ip6, char const *dir,
+s6_accessrules_params_t *params) </code> <br />
+Checks the <em>dir</em> base directory for an authorization for the
+<em>ip6</em> IPv6 address (16 network byte order characters).
+If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_ip46_cdb
+(ip46_t *ip, struct cdb *c,
+s6_accessrules_params_t *params) </code> <br />
+Checks the *<em>c</em> CDB database for an authorization for the
+<em>ip</em> IP address.
+If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+<p>
+<code> s6_accessrules_result_t s6_accessrules_ip46_fs
+(ip46_t const *ip, char const *dir,
+s6_accessrules_params_t *params) </code> <br />
+Checks the <em>dir</em> base directory for an authorization for the
+<em>ip</em> IP address.
+If the result is S6_ACCESSRULES_ALLOW, additional
+information may be stored into <em>params</em>.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6/ftrigr.html b/doc/libs6/ftrigr.html
new file mode 100644
index 0000000..2c9bf88
--- /dev/null
+++ b/doc/libs6/ftrigr.html
@@ -0,0 +1,268 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the ftrigr library interface</title>
+    <meta name="Description" content="s6: the ftrigr library interface" />
+    <meta name="Keywords" content="s6 ftrig notification subscriber listener libftrigr ftrigr library interface" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ftrigr</tt> library interface </h1>
+
+<p>
+ The <tt>ftrigr</tt> library provides an API for listeners, i.e.
+programs that want to subscribe to fifodirs and be instantly
+notified when the proper sequence of events happens.
+</p>
+
+<h2> Programming </h2>
+
+<p>
+ Check the <tt>s6/ftrigr.h</tt> header for the
+exact function prototypes.
+</p>
+
+<p>
+ Make sure your application is not disturbed by children it doesn't
+know it has. This means paying some attention to the SIGCHLD handler,
+if any, and to the way you perform <tt>waitpid()</tt>s. The best
+practice is to use a
+<a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a>
+to handle SIGCHLD (as well as other signals the application needs to trap),
+and to <em>always</em> use <tt>wait_nohang()</tt> to reap children,
+simply ignoring pids you don't know.
+</p>
+
+<p>
+ If your (badly programmed) application has trouble handling unknown
+children, consider using a ftrigrd service.
+</p>
+
+<h3> A programming example </h3>
+
+<p>
+ The <tt>src/pipe-tools/s6-ftrig-listen1.c</tt> and
+<tt>src/supervision/s6-svwait.c</tt> files in the s6 package,
+for instance, illustrate how to use the ftrigr library.
+</p>
+
+
+<h3> Synchronous functions with a specified maximum execution time </h3>
+
+<ul>
+ <li> Synchronous functions take a <tt>tain_t const *</tt>
+(<em>deadline</em>) parameter and a <tt>tain_t *</tt> (<em>stamp</em>)
+parameter. Those are pointers to tain_t structures containing absolute times;
+the former represents a deadline (in most cases, this time will be in the
+future) and the latter must be an accurate enough timestamp. These
+structures can be filled using the <tt>tain_</tt> primitives declared in
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html">skalibs/tai.h</a>. </li>
+ <li> ("Accurate enough" means that <strong>no blocking system call must have
+been made</strong> since the last time <em>stamp</em> was updated (by
+<tt>tain_now(&amp;stamp)</tt>). It's a good policy to always update
+<em>stamp</em> right after a (potentially) blocking system call like
+<tt>select()</tt> returns. And unless the application is extremely CPU-intensive
+(think calculus for physicists or astronomers) updating <em>stamp</em> more
+frequently is unnecessary.) </li>
+ <li> If such a synchronous function still hasn't returned when the deadline
+occurs, then it will immediately return a failure code and set errno to ETIMEDOUT.
+It is possible to pass null pointers to the function instead of pointers to
+tain_t structures, in which case the function will never timeout. </li>
+ <li> If a timeout occurs, the library does not guarantee proper interprocess
+communication later on; the application should either die, or at least close
+the communication channel and open a new one. </li>
+ <li> If any waiting occurred, the <em>stamp</em> structure is automatically
+updated by the called function, so it always represents an accurate enough estimation
+of the current time. This allows the programmer to call several such functions
+in a sequence without modifying the <em>deadline</em> and <em>stamp</em>
+parameters: then the whole sequence is bound in execution time. </li>
+ <li> This is a general safety mechanism implemented in
+<a href="http://skarnet.org/software/skalibs/libunixonacid/">libunixonacid</a>:
+in interprocess communication, purely synchronous primitives are dangerous
+because they make the calling process rely on proper behaviour of the called
+process. Giving synchronous primitives the ability to timeout allows developers
+to write reliable programs even when interacting with software they have no
+control on. </li>
+</ul>
+
+
+<h3> Starting and ending a session </h3>
+
+<pre>
+ftrigr_t a = FTRIGR_ZERO ;
+tain_t deadline, stamp ;
+
+tain_now(&amp;stamp) ;
+tain_addsec(&amp;deadline, &amp;stamp, 2)
+
+// char const *path = FTRIGR_IPCPATH ;
+// ftrigr_start(&amp;a, path, &amp;deadline, &amp;stamp) ;
+ftrigr_startf(&amp;a, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+<tt>ftrigr_start</tt> starts a session with a ftrigrd service listening on
+<em>path</em>. <br />
+<tt>ftrigr_startf</tt> starts a session with a ftrigrd process as a child
+(which is the simplest usage). <br />
+<tt>a</tt> is a ftrigr_t structure that must be declared in the stack and
+initialized to FTRIGR_ZERO.
+<tt>stamp</tt> must be an accurate enough timestamp. <br />
+If the session initialization fails, the function returns 0 and errno is set;
+else the function returns 1.
+</p>
+<p>
+If the absolute time <tt>deadline</tt> is reached and the function
+has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT.
+
+Only local interprocess communications are involved; unless your system is
+heavily overloaded, the function should return near-instantly. One or two
+seconds of delay between <tt>stamp</tt> and <tt>deadline</tt> should be
+enough: if the function takes more than that to return, then there is a
+problem with the underlying processes.
+</p>
+
+<p>
+ You can have more than one session open in parallel, by declaring
+several distinct <tt>ftrigr_t</tt> structures and calling
+<tt>ftrigr_startf</tt> (or <tt>ftrigr_start</tt>) more than once.
+However, this is useless, since one single session can handle
+virtually as many concurrent fifodirs as your application needs.
+</p>
+
+<pre>
+ftrigr_end(&amp;a) ;
+</pre>
+
+<p>
+<tt>ftrigr_end</tt> frees all the resources used by the session. The
+<tt>a</tt> structure is then reusable for another session.
+</p>
+
+<h3> Subscribing to a fifodir </h3>
+
+<pre>
+char const *path = "/var/lib/myservice/fifodir" ;
+char const *re = "a.*b|c*d" ;
+uint32 options = 0 ;
+
+uint16 id = ftrigr_subscribe (&amp;a, path, re, options, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+<tt>ftrigr_subscribe</tt> instructs the
+<a href="s6-ftrigrd.html">s6-ftrigrd daemon</a>, related to the open
+session represented by the <tt>a</tt> structure, to subscribe to the
+<tt>path</tt> fifodir, and to notify the application when it receives
+a series of events that matches the <tt>re</tt> regexp.
+<tt>options</tt> can be 0 or FTRIGR_REPEAT. If it is 0, the daemon will
+automatically unsubscribe from <tt>path</tt> once <tt>re</tt> has been
+matched by a series of events. If it is FTRIGR_REPEAT, it will remain
+subscribed until told otherwise.
+</p>
+
+<p>
+ <tt>ftrigr_subscribe()</tt> returns 0 and sets errno in case of failure, or
+a nonzero 16-bit number identifying the subscription in case of success.
+</p>
+
+<p>
+<tt>ftrigr_subscribe</tt> should return near-instantly, but if
+<em>deadline</em> is reached, it will return 0 ETIMEDOUT. If
+<tt>ftrigr_subscribe</tt> returns successfully, then the
+s6-ftrigrd daemon is guaranteed to be listening on <tt>path</tt>,
+and events can be sent without the risk of a race condition.
+</p>
+
+<h3> Synchronously waiting for events </h3>
+
+<pre>
+uint16 list[1] ;
+unsigned int n = 1 ;
+char trigger ;
+list[0] = id ;
+
+// r = ftrigr_wait_and(&amp;a, list, n, &amp;deadline) ;
+r = ftrigr_wait_or(&amp;a, list, n, &amp;deadline, &amp;trigger) ;
+</pre>
+
+<p>
+ <tt>ftrigr_wait_and()</tt> waits for <em>all</em> the <tt>n</tt> fifodirs
+whose ids are listed in <tt>list</tt> to receive an event. It returns -1
+in case of error or timeout, or a non-negative integer in case of success. <br />
+ <tt>ftrigr_wait_or()</tt> waits for <em>one</em> of the <tt>n</tt> fifodirs
+whose ids are listed in <tt>list</tt> to receive an event. It returns -1
+in case of error or timeout; if it succeeds, the return value is the
+position in <tt>list</tt>, starting at 0, of the identifier that received
+an event; and <tt>trigger</tt> is set to the character that triggered that
+event, i.e. the last character of a sequence that matched the regular
+expression <tt>re</tt> used in the subscription.
+</p>
+
+<h3> Asynchronously waiting for events </h3>
+
+<p>
+<em> (from now on, the functions are listed with their prototypes instead
+of usage examples.) </em>
+</p>
+
+<pre>
+int ftrigr_fd (ftrigr_t const *a)
+</pre>
+
+<p>
+ Returns a file descriptor to select on for reading. Do not
+<tt>read()</tt> it though.
+</p>
+
+<pre>
+int ftrigr_update (ftrigr_t *a)
+</pre>
+
+<p>
+ Call this function whenever the fd checks readability: it will
+update <em>a</em>'s internal structures with information from the
+<a href="s6-ftrigrd.html">s6-ftrigrd</a> daemon. It returns -1 if an error
+occurs; in case of success, it returns the number of identifiers for
+which something happened.
+</p>
+
+<p>
+ When <tt>ftrigr_update</tt> returns,
+<tt>genalloc_s(uint16, &amp;a-&gt;list)</tt> points to an array of
+<tt>genalloc_len(uint16, &amp;a-&gt;list)</tt> 16-bit unsigned
+integers. Those integers are ids waiting to be passed to
+<tt>ftrigr_check</tt>.
+</p>
+
+<pre>
+int ftrigr_check (ftrigr_t *a, uint16 id, char *what)
+</pre>
+
+<p>
+ Checks whether an event happened to <em>id</em>. Use after a
+call to <tt>ftrigr_update()</tt>.
+</p>
+
+<ul>
+ <li> If an error occurred, returns -1 and sets errno. The error
+number may have been transmitted from
+<a href="s6-ftrigrd.html">s6-ftrigrd</a>. </li>
+ <li> If no notification happened yet, returns 0. </li>
+ <li> If something happened, writes the character that triggered the
+latest notification into <em>what</em> and returns the number of
+times that an event happened to this identifier since the last
+call to <tt>ftrigr_check()</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6/ftrigw.html b/doc/libs6/ftrigw.html
new file mode 100644
index 0000000..b0feb31
--- /dev/null
+++ b/doc/libs6/ftrigw.html
@@ -0,0 +1,100 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the ftrigw library interface</title>
+    <meta name="Description" content="s6: the ftrigw library interface" />
+    <meta name="Keywords" content="s6 ftrig notification notifier writer libftrigw ftrigw library interface" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ftrigw</tt> library interface </h1>
+
+<p>
+ The <tt>ftrigw</tt> library provides an API for notifiers, i.e.
+programs that want to regularly announce what they're doing.
+</p>
+
+<p>
+ Notifiers should create a fifodir in a hardcoded place in the
+filesystem, and document its location. Listeners will then be
+able to subscribe to that fifodir, and receive the events.
+</p>
+
+<h2> Programming </h2>
+
+<p>
+ Check the <tt>s6/ftrigw.h</tt> header for the
+exact function prototypes.
+</p>
+
+<h3> Creating a fifodir </h3>
+
+<pre>
+char const *path = "/var/lib/myservice/fifodir" ;
+int gid = -1 ;
+int forceperms = 0 ;
+int r = ftrigw_fifodir_make(path, gid, forceperms) ;
+</pre>
+
+<p>
+<tt>ftrigw_fifodir_make</tt> creates a fifodir at the <tt>path</tt> location.
+It returns 0, and sets errno, if an error occurs.
+It returns 1 if it succeeds. <br />
+If a fifodir, owned by the user, already exists at <tt>path</tt>, and
+<tt>forceperms</tt> is zero, then <tt>ftrigw_fifodir_make</tt> immediately
+returns a success. If <tt>forceperms</tt> is nonzero, then
+it tries to adjust <tt>path</tt>'s permissions before returning.
+</p>
+
+<p>
+If <tt>gid</tt> is negative, then <tt>path</tt> is created "public".
+Any listener will be able to subscribe to <tt>path</tt>.
+If <tt>gid</tt> is nonnegative, then <tt>path</tt> is created "private".
+Only processes belonging to group <tt>gid</tt> will be able to
+subscribe to <tt>path</tt>.
+</p>
+
+<h3> Sending an event </h3>
+
+<pre>
+char event = 'a' ;
+r = ftrigw_notify(path, event) ;
+</pre>
+
+<p>
+<tt>ftrigw_notify</tt> sends <tt>event</tt> to all the processes that are
+currently subscribed to <tt>path</tt>.
+It returns -1 if an error occurs, or the number of successfully notified
+processes.
+</p>
+
+<h3> Cleaning up </h3>
+
+<p>
+When stray KILL signals hit <a href="s6-ftrigrd.html">s6-ftrigrd</a> processes,
+1. it's a sign of incorrect system administration, 2. they can leave
+unused named pipes in the fifodir. It's the fifodir's owner's job, i.e.
+the notifier's job, to periodically do some housecleaning and remove
+those unused pipes.
+</p>
+
+<pre>
+r = ftrigw_clean(path) ;
+</pre>
+
+<p>
+<tt>ftrigw_clean</tt> cleans <tt>path</tt>. It returns 0, and sets errno,
+if it encounters an error. It returns 1 if it succeeds.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6/index.html b/doc/libs6/index.html
new file mode 100644
index 0000000..9fe7e65
--- /dev/null
+++ b/doc/libs6/index.html
@@ -0,0 +1,72 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6 library interface</title>
+    <meta name="Description" content="s6: the s6 library interface" />
+    <meta name="Keywords" content="s6 s6 libs6 library libs6net" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6</tt> library interface </h1>
+
+<h2> General information </h2>
+
+<p>
+ <tt>libs6</tt> is a collection of utility
+C interfaces, used in the s6 executables.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure the s6 headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;s6/s6.h&gt;</tt> </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the s6 libraries, as well as the skalibs
+libraries, are visible in your library search path. </li>
+ <li> Link against <tt>-ls6</tt> and <tt>-lskarnet</tt>.
+If you're using socket functions (which is the case with
+<a href="ftrigr.html">libftrigr</a>, for instance, add
+<tt>`cat $sysdeps/socket.lib`</tt> to your command line.
+If you're using timed functions involving TAI timestamps
+(which is also the case with <a href="ftrigr.html">libftrigr</a>
+for instance), add
+<tt>`cat $sysdeps/tainnow.lib`</tt>. <tt>$sysdeps</tt>
+stands for your skalibs sysdeps directory. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<p>
+ The <tt>s6/s6.h</tt> header is actually a
+concatenation of other headers:
+the libs6net is separated into several modules, each of them with its
+own header.
+</p>
+
+<ul>
+ <li> The <a href="accessrules.html">s6/accessrules.h</a> header
+provides functions to check credentials against configuration files. </li>
+ <li> The <a href="ftrigr.html">s6/ftrigr.h</a> header provides
+functions to subscribe to fifodirs and be notified of events. </li>
+ <li> The <a href="ftrigw.html">s6/ftrigw.h</a> header provides
+functions to manage fifodirs and send notifications to them. </li>
+ <li> The <a href="s6lock.html">s6/s6lock.h</a> header provides
+functions to acquire locks with a timeout. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6/s6-ftrigrd.html b/doc/libs6/s6-ftrigrd.html
new file mode 100644
index 0000000..4bbfc69
--- /dev/null
+++ b/doc/libs6/s6-ftrigrd.html
@@ -0,0 +1,80 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-ftrigrd program</title>
+    <meta name="Description" content="s6: the s6-ftrigrd program" />
+    <meta name="Keywords" content="s6 command s6-ftrigrd program internal libexec fifodir regexp subscribe notification listener" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-ftrigrd program </h1>
+
+<p>
+s6-ftrigrd is a helper program that manages a set of subscriptions to fifodirs as well
+as regular expressions on events. It takes orders from its client program that controls
+it via the <a href="ftrigr.html">ftrigr library</a>, and notifies it when desired
+events happen.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ s6-ftrigrd is not meant to be called directly.
+</p>
+
+<ul>
+ <li> If the client program uses <tt>ftrigr_startf()</tt>, it spawns an instance of
+s6-ftrigrd as a child. s6-ftrigrd's stdin is a pipe reading from the client; its
+stdout is a pipe writing to the client; its stderr is the same as the client's; and
+there's an additional pipe from s6-ftrigrd to the client, used for asynchronous
+notifications. </li>
+ <li> If the client program uses <tt>ftrigr_start()</tt>, then it tries to connect
+to a Unix domain socket. A ftrigrd <a href="../localservice.html">local service</a> should be listening to that
+socket, i.e. a Unix domain superserver such as
+<a href="s6-ipcserver.html">s6-ipcserver</a>
+spawning a s6-ftrigrd program on every connection. Then a s6-ftrigrd instance is created
+for the client. </li>
+ <li> When the client uses <tt>ftrigr_end()</tt>, or closes s6-ftrigrd's stdin in
+any way, s6-ftrigrd exits 0. </li>
+</ul>
+
+<p>
+ s6-ftrigrd handles the grunt work of creating fifos in a
+<a href="fifodir.html">fifodir</a> for a subscriber. It also wakes up on every
+event, and compares the chain of events it received on a given fifodir with the
+client-provided regexp. If the chain of events matches the regexp, it notifies
+the client.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+ The connection management between the client and s6-ftrigrd is entirely done
+by the <a href="http://skarnet.org/software/skalibs/libunixonacid/skaclient.html">skaclient</a>
+library.
+</p>
+
+<p>
+ s6-ftrigrd is entirely asynchronous. It stores unread notifications into heap
+memory; it can grow in size if there are a lot of events and the client fails
+to read them. To avoid uncontrolled growth, make sure your client calls
+<tt>ftrigr_update()</tt> as soon as <tt>ftrigr_fd()</tt> becomes readable.
+</p>
+
+<p>
+ A s6-ftrigrd instance can only handle up to FTRIGRD_MAX (defined in <tt>s6/ftrigr.h</tt>)
+subscriptions at once. By default, this number is 1000, which is more than enough for
+any reasonable system.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6/s6lock.html b/doc/libs6/s6lock.html
new file mode 100644
index 0000000..ca22fe4
--- /dev/null
+++ b/doc/libs6/s6lock.html
@@ -0,0 +1,238 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6lock library interface</title>
+    <meta name="Description" content="s6: the s6lock library interface" />
+    <meta name="Keywords" content="s6 timed lock s6lock libs6 library interface" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6lock</tt> library interface </h1>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6lock</tt> is a C interface to timed locks. Unix natively provides
+locks, but the locking primitives are synchronous, so either they are
+unbounded in execution time or they require polling. s6lock provides
+poll-free locks that can timeout during attempted acquisition.
+</p>
+
+<h2> Programming </h2>
+
+<ul>
+ <li> Check the <tt>s6/s6lock.h</tt> header
+for the prototypes. The functions documented here are
+often simplified macros, for instance relying on the STAMP global variable
+to hold the current time. Fully reentrant functions with more control
+options are usually available. </li>
+ <li> Given the nature of the s6lock library, it makes sense to use a
+<a href="../localservice.html">s6lockd service</a> concurrently
+accessed by several applications using such locks to gate shared
+resources. </li>
+ <li> If you're not using a s6lockd service,
+make sure your application is not disturbed by children it doesn't
+know it has. Using nonblocking waits, ignoring pids you don't know, and
+using a
+<a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a>
+if your application is built around an event loop, are good programming
+practices. </li>
+</ul>
+
+<h3> Starting and ending a session </h3>
+
+<pre>
+s6lock_t a = S6LOCK_ZERO ;
+tain_t deadline ;
+
+tain_now_g() ;
+tain_addsec_g(&amp;deadline, 2)
+
+char const *path = S6LOCK_IPCPATH ;
+s6lock_start_g(&amp;a, path, &amp;deadline) ;
+// char const *lockdir = "/tmp/lock" ;
+// s6lock_startf_g(&amp;a, lockdir, &amp;deadline) ;
+</pre>
+
+<p>
+<tt>s6lock_start_g</tt> starts a session by connecting to a s6lockd service
+listening on <em>path</em>. The working directory is set by the administrator
+of the service. <br />
+<tt>s6lock_startf_g</tt> starts a session with a s6lockd process as a child,
+using <em>lockdir</em> as its working directory.
+<br />
+<tt>a</tt> is a s6lock_t structure that must be declared in the stack and
+initialized to S6LOCK_ZERO.
+If the session initialization fails, the function returns 0 and errno is set;
+else the function returns 1.
+</p>
+<p>
+If the absolute time <tt>deadline</tt> is reached and the function
+has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT.
+
+Only local interprocess communications are involved; unless your system is
+heavily overloaded, the function should return near-instantly. One or two
+seconds of delay between the current time and <tt>deadline</tt> should be
+enough: if the function takes more than that to return, then there is a
+problem with the underlying processes.
+</p>
+
+<p>
+ You can have more than one session open in parallel, by declaring
+several distinct <tt>s6lock_t</tt> structures and calling
+<tt>s6lock_startf_g</tt> (or <tt>s6lock_start_g</tt>) more than once.
+However, one single session can handle
+virtually as many concurrent locks as your application needs, so
+opening several sessions is only useful if you need to acquire locks
+in various distinct lock directories.
+</p>
+
+<pre>
+s6lock_end(&amp;a) ;
+</pre>
+
+<p>
+<tt>s6lock_end</tt> frees all the resources used by the session. The
+<tt>a</tt> structure is then reusable for another session.
+</p>
+
+<h3> Acquiring and releasing locks </h3>
+
+<pre>
+uint16 id ;
+char const *file = "lockfile" ;
+tain_t limit ;
+tain_t deadline ;
+
+int r = s6lock_acquire_sh_g (&amp;a, &amp;id, file, &amp;limit, &amp;deadline) ;
+/* int r = s6lock_acquire_ex_g (&amp;a, &amp;id, file, &amp;limit, &amp;deadline) ; */
+r = s6lock_release_g(&amp;a, id, &amp;deadline) ;
+</pre>
+
+<p>
+<tt>s6lock_acquire_sh_g</tt> instructs the
+<a href="s6lockd.html">s6lockd daemon</a>, related to the open
+session represented by the <tt>a</tt> handle, to try and acquire a
+shared lock on the
+<em>file</em> file located under that daemon's working directory
+(typically <tt>/var/lock</tt>). <em>file</em> will be interpreted as
+relative to the daemon's working directory even if it starts with a
+slash; however, slashes in the middle of <em>file</em> are likely to
+result in an error.
+</p>
+
+<p>
+<em>limit</em> and <em>deadline</em> are two absolute dates.
+<em>deadline</em> is a deadline for the execution of the
+function: if by <em>deadline</em> the function has not returned,
+then it instantly returns 0 and sets errno to ETIMEDOUT. The
+function is normally near-instantaneous, so <em>deadline</em> can
+be very close in the future and serves only as a protection against
+malicious servers. <em>limit</em> is the acquisition deadline: if
+by <em>limit</em> the daemon still has not been able to acquire a lock
+on <em>file</em>, then it will report a timeout to the client.
+</p>
+
+<p>
+The function returns 1 in case of success, or 0 if an error occurs,
+with errno set to a suitable value. If it succeeds, then a 16-bit
+number is stored into *<em>id</em>; this number serves as an identifier
+for this lock.
+</p>
+
+<p>
+<tt>s6lock_acquire_ex_g</tt> works just like <tt>s6lock_acquire_sh_g</tt>,
+except that the daemon tries to acquire an exclusive lock.
+</p>
+
+<p>
+<tt>s6lock_release_g</tt> releases the lock identified by <em>id</em>.
+It normally returns 1. It can return 0 with errno set to a suitable
+value if it fails. <em>id</em> is not valid after the corresponding
+lock has been released. The function normally returns instantly, with
+<em>deadline</em> as a safeguard.
+</p>
+
+<h3> Asynchronously waiting for locks </h3>
+
+<p>
+<em> (from now on, the functions are listed with their prototypes instead
+of usage examples.) </em>
+</p>
+
+<pre>
+int s6lock_fd (s6lock_t const *a)
+</pre>
+
+<p>
+ Returns a file descriptor to select on for reading. Do not
+<tt>read()</tt> it though.
+</p>
+
+<pre>
+int s6lock_update (s6lock_t *a)
+</pre>
+
+<p>
+ Call this function whenever the fd checks readability: it will
+update <em>a</em>'s internal structures with information from the
+<a href="s6lockd.html">s6lockd</a> daemon. It returns -1 if an error
+occurs; in case of success, it returns the number of identifiers for
+which something happened.
+</p>
+
+<p>
+ When <tt>s6lock_update</tt> returns,
+<tt>genalloc_s(uint16, &amp;a-&gt;list)</tt> points to an array of
+<tt>genalloc_len(uint16, &amp;a-&gt;list)</tt> 16-bit unsigned
+integers. Those integers are ids waiting to be passed to
+<tt>s6lock_check</tt>.
+</p>
+
+<pre>
+int s6lock_check (s6lock_t *a, uint16 id, char *what)
+</pre>
+
+<p>
+ Checks whether the lock identified by <em>id</em> has
+been acquired. Use after a call to <tt>s6lock_update()</tt>.
+</p>
+
+<ul>
+ <li> If an error occurred, returns -1 and sets errno. The error
+number may have been transmitted from
+<a href="s6lockd.html">s6lockd</a>. </li>
+ <li> If the lock has not been acquired yet, returns 0. </li>
+ <li> If the lock has been acquired, returns 1. </li>
+</ul>
+
+<h3> Synchronously waiting for locks </h3>
+
+<p>
+<code> int s6lock_wait_or_g (s6lock_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline) </code> <br />
+Synchronously waits for <em>one</em> of the locks represented by the array pointed to
+by <em>idlist</em> of length <em>n</em> to be acquired. Returns -1 if it fails,
+or a nonnegative number on success, which is the index in <em>idlist</em> of the
+acquired lock's id. If no result has been obtained by <em>deadline</em>, the
+function returns -1 ETIMEDOUT.
+</p>
+
+<p>
+<code> int s6lock_wait_and_g (s6lock_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline) </code> <br />
+Synchronously waits for <em>all</em> of the locks represented by the array pointed to
+by <em>idlist</em> of length <em>n</em> to be acquired. Returns -1 if it fails and
+0 if it succeeds. If no result has been obtained by <em>deadline</em>, the
+function returns -1 ETIMEDOUT.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6/s6lockd-helper.html b/doc/libs6/s6lockd-helper.html
new file mode 100644
index 0000000..2c54e5c
--- /dev/null
+++ b/doc/libs6/s6lockd-helper.html
@@ -0,0 +1,52 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6lockd-helper internal program</title>
+    <meta name="Description" content="s6: the s6lockd-helper internal program" />
+    <meta name="Keywords" content="s6 s6lockd-helper lockd asynchronous timed lock daemon helper" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a><p />
+
+<h1> The <tt>s6lockd-helper</tt> program </h1>
+
+<p>
+<tt>s6lockd-helper</tt> is a helper program for the s6lock daemon.
+It just acquires a lock and holds it until it is killed or told to
+exit by its parent daemon.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+  s6lockd-helper is not meant to be invoked directly by the user:
+it will be spawned by the
+<a href="s6lockd.html">s6lockd</a> program.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6lockd-helper blocks on lock acquisition until it succeeds. It then
+notifies its parent. It exits when its parent tells him to (i.e. when the
+client asks for lock release). During the lock acquisition phase, it can
+be killed if its parent detects a timeout. </li>
+ <li> One s6lockd-helper process per lock is the only way (apart from
+threads) to implement timed lock acquisition. This can lead to a lot of
+s6lockd-helper processes, but this is not a problem:
+  <ul>
+   <li> Processes are not a scarce resource. Today's schedulers work in O(1),
+i.e. a sleeping process takes no scheduling time at all. </li>
+   <li> s6lockd-helper is extremely tiny. Every instance should use up at
+most one or two pages of non-sharable memory. </li>
+  </ul> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6/s6lockd.html b/doc/libs6/s6lockd.html
new file mode 100644
index 0000000..7db76be
--- /dev/null
+++ b/doc/libs6/s6lockd.html
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6lockd internal program</title>
+    <meta name="Description" content="s6: the s6lockd internal program" />
+    <meta name="Keywords" content="s6 s6lockd lockd asynchronous timed lock daemon" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<a href="index.html">libs6</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a><p />
+
+<h1> The <tt>s6lockd</tt> program </h1>
+
+<p>
+<tt>s6lockd</tt> is the s6lock daemon. It is a program that manages
+a set of lock files in a given directory, and associated timeouts.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+  s6lockd does not fork, does not background itself automatically,
+and does not use syslog. It is not meant to be run directly by the
+user: it will be spawned by the
+<a href="s6lock.html">s6lock client library</a>.
+</p>
+
+<p>
+ There are 2 ways to use s6lockd:
+</p>
+
+<ol>
+ <li> Use the <tt>s6lock_startf()</tt> library call.
+A <tt>s6lockd</tt> child will then be spawned from your
+calling process, and automatically reaped when you call
+<tt>s6lock_end()</tt>. It requires care with applications that
+trap SIGCHLD. It also requires care with lock file permissions:
+a s6lockd instance might not be able
+to open a lock file created by a former instance run by another
+client with different permissions. </li>
+ <li> Use the <tt>s6lock_start()</tt> library call, together with a
+<a href="../localservice.html">s6lockd service</a>.
+For once, <em>this is the recommended setup</em>: s6lockd creates empty
+lock files, and having all s6lockd instances run under the same user
+simplifies permissions management considerably. </li>
+ </li>
+</ol>
+
+<p>
+When run as a service, s6lockd has no "standalone" mode: it is
+designed to work with a Unix
+domain superserver, like
+<a href="../s6-ipcserver.html">s6-ipcserver</a>.
+s6lockd follows the <a href="http://cr.yp.to/proto/ucspi.txt">UCSPI</a>
+interface, it can be directly executed from the superserver.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Unix does not natively provide a way to stop blocking on a lock
+acquisition after a timeout. To emulate such behaviour, s6lockd actually
+spawns a <a href="s6lockd-helper.html">s6lockd-helper</a> child per
+requested lock. </li>
+</ul>
+
+</body>
+</html>