about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--AUTHORS2
-rw-r--r--COPYING13
-rw-r--r--INSTALL175
-rw-r--r--Makefile154
-rw-r--r--NEWS6
-rw-r--r--README27
-rw-r--r--README.macosx4
-rw-r--r--README.solaris12
-rwxr-xr-xconfigure470
-rw-r--r--doc/index.html132
-rw-r--r--doc/libutmps/index.html171
-rw-r--r--doc/overview.html109
-rw-r--r--doc/upgrade.html28
-rw-r--r--doc/utmps-utmpd.html77
-rw-r--r--doc/utmps-wtmpd.html78
-rwxr-xr-xexamples/openrc/utmpd8
-rwxr-xr-xexamples/openrc/wtmpd8
-rw-r--r--examples/s6-rc/sutmp-prepare/dependencies1
-rw-r--r--examples/s6-rc/sutmp-prepare/down1
-rw-r--r--examples/s6-rc/sutmp-prepare/type1
-rw-r--r--examples/s6-rc/sutmp-prepare/up2
-rw-r--r--examples/s6-rc/utmpd-log/consumer-for1
-rw-r--r--examples/s6-rc/utmpd-log/dependencies1
-rw-r--r--examples/s6-rc/utmpd-log/run5
-rw-r--r--examples/s6-rc/utmpd-log/type1
-rw-r--r--examples/s6-rc/utmpd/dependencies1
-rw-r--r--examples/s6-rc/utmpd/notification-fd1
-rw-r--r--examples/s6-rc/utmpd/pipeline-name1
-rw-r--r--examples/s6-rc/utmpd/producer-for1
-rw-r--r--examples/s6-rc/utmpd/run8
-rw-r--r--examples/s6-rc/utmpd/type1
-rw-r--r--examples/s6-rc/wtmpd-log/consumer-for1
-rw-r--r--examples/s6-rc/wtmpd-log/dependencies1
-rw-r--r--examples/s6-rc/wtmpd-log/run5
-rw-r--r--examples/s6-rc/wtmpd-log/type1
-rw-r--r--examples/s6-rc/wtmpd/dependencies1
-rw-r--r--examples/s6-rc/wtmpd/notification-fd1
-rw-r--r--examples/s6-rc/wtmpd/pipeline-name1
-rw-r--r--examples/s6-rc/wtmpd/producer-for1
-rw-r--r--examples/s6-rc/wtmpd/run8
-rw-r--r--examples/s6-rc/wtmpd/type1
-rwxr-xr-xexamples/s6/utmpd/log/run5
-rw-r--r--examples/s6/utmpd/notification-fd1
-rwxr-xr-xexamples/s6/utmpd/run11
-rwxr-xr-xexamples/s6/wtmpd/log/run5
-rw-r--r--examples/s6/wtmpd/notification-fd1
-rwxr-xr-xexamples/s6/wtmpd/run9
-rw-r--r--package/deps-build1
-rw-r--r--package/deps.mak41
-rw-r--r--package/info4
-rw-r--r--package/modes2
-rw-r--r--package/targets.mak7
-rwxr-xr-xpatch-for-solaris21
-rw-r--r--src/include/utmps/utmps.h35
-rw-r--r--src/include/utmps/utmpx.h75
-rw-r--r--src/include/utmpx.h13
-rw-r--r--src/utmps/deps-exe/utmps-utmpd2
-rw-r--r--src/utmps/deps-exe/utmps-wtmpd2
-rw-r--r--src/utmps/deps-lib/utmps21
-rw-r--r--src/utmps/endutxent.c10
-rw-r--r--src/utmps/getutxent.c12
-rw-r--r--src/utmps/getutxid.c12
-rw-r--r--src/utmps/getutxline.c12
-rw-r--r--src/utmps/logwtmp.c22
-rw-r--r--src/utmps/pututxline.c14
-rw-r--r--src/utmps/setutxent.c11
-rw-r--r--src/utmps/updwtmpx.c10
-rw-r--r--src/utmps/utmps-internal.h16
-rw-r--r--src/utmps/utmps-utmpd.c216
-rw-r--r--src/utmps/utmps-wtmpd.c107
-rw-r--r--src/utmps/utmps_end.c10
-rw-r--r--src/utmps/utmps_getent.c21
-rw-r--r--src/utmps/utmps_getid.c27
-rw-r--r--src/utmps/utmps_getline.c25
-rw-r--r--src/utmps/utmps_here.c8
-rw-r--r--src/utmps/utmps_here_maybe_init.c11
-rw-r--r--src/utmps/utmps_putline.c22
-rw-r--r--src/utmps/utmps_rewind.c19
-rw-r--r--src/utmps/utmps_start.c18
-rw-r--r--src/utmps/utmps_updwtmpx.c28
-rw-r--r--src/utmps/utmps_utmpx_pack.c9
-rw-r--r--src/utmps/utmps_utmpx_unpack.c13
-rwxr-xr-xtools/gen-deps.sh93
-rwxr-xr-xtools/install.sh64
85 files changed, 2595 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..29db243
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.o
+*.lo
+/config.mak
+/src/include/utmps/config.h
+/libutmps.a.xyzzy
+/libutmps.so.xyzzy
+/utmps-utmpd
+/utmps-wtmpd
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..7a708a1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Main author:
+  Laurent Bercot <ska-skaware@skarnet.org>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..9e2739b
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,13 @@
+Copyright (c) 2017 Laurent Bercot <ska-skaware@skarnet.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..53d90eb
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,175 @@
+Build Instructions
+------------------
+
+* Requirements
+  ------------
+
+  - A POSIX-compliant C development environment
+  - GNU make version 3.81 or later
+  - skalibs version 2.6.1.0 or later: http://skarnet.org/software/skalibs/
+
+ This software will run on any operating system that implements
+POSIX.1-2008, available at:
+  http://pubs.opengroup.org/onlinepubs/9699919799/
+
+
+* Standard usage
+  --------------
+
+  ./configure && make && sudo make install
+
+ will work for most users.
+ It will install the binaries in /bin and the static libraries in
+/usr/lib/utmps.
+
+ Please note that static libraries in /usr/lib/utmps *will not*
+be found by a default linker invocation: you need -L/usr/lib/utmps.
+Other skarnet.org software automatically handles that case if the
+default configuration is used, but if you change the configuration,
+remember to use the appropriate --with-lib configure option.
+
+ You can strip the binaries and libraries of their extra symbols via
+"make strip" before the "make install" phase. It will shave a few bytes
+off them.
+
+
+* Customization
+  -------------
+
+ You can customize paths via flags given to configure.
+ See ./configure --help for a list of all available configure options.
+
+
+* Environment variables
+  ---------------------
+
+ Controlling a build process via environment variables is a big and
+dangerous hammer. You should try and pass flags to configure instead;
+nevertheless, a few standard environment variables are recognized.
+
+ If the CC environment variable is set, its value will override compiler
+detection by configure. The --host=HOST option will still add a HOST-
+prefix to the value of CC.
+
+ The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags
+auto-detected by configure. To entirely override the flags set by
+configure instead, use make variables.
+
+
+* Make variables
+  --------------
+
+ You can invoke make with a few variables for more configuration.
+
+ CC, CFLAGS, CPPFLAGS, LDFLAGS, LDLIBS, AR, RANLIB, STRIP, INSTALL and
+CROSS_COMPILE can all be overridden on the make command line. This is
+an even bigger hammer than running ./configure with environment
+variables, so it is advised to only do this when it is the only way of
+obtaining the behaviour you want.
+
+ DESTDIR can be given on the "make install" command line in order to
+install to a staging directory.
+
+
+* Shared libraries
+  ----------------
+
+ Software from skarnet.org is small enough that shared libraries are
+generally not worth using. Static linking is simpler and incurs less
+runtime overhead and less points of failure: so by default, shared
+libraries are not built and binaries are linked against the static
+versions of the skarnet.org libraries. Nevertheless, you can:
+  * build shared libraries: --enable-shared
+  * link binaries against shared libraries: --disable-allstatic
+
+
+* Static binaries
+  ---------------
+
+ By default, binaries are linked against static versions of all the
+libraries they depend on, except for the libc. You can enforce
+linking against the static libc with --enable-static-libc.
+
+ (If you are using a GNU/Linux system, be aware that the GNU libc
+behaves badly with static linking and produces huge executables,
+which is why it is not the default. Other libcs are better suited
+to static linking, for instance musl: http://musl-libc.org/. If
+you're using utmps, chances are you're running musl, or a similar
+libc, anyway.)
+
+
+* Cross-compilation
+  -----------------
+
+ skarnet.org packages centralize all the difficulty of
+cross-compilation in one place: skalibs. Once you have
+cross-compiled skalibs, the rest is easy.
+
+ * Use the --host=HOST option to configure, HOST being the triplet
+for your target.
+ * Make sure your cross-toolchain binaries (i.e. prefixed with HOST-)
+are accessible via your PATH environment variable.
+ * Make sure to use the correct version of skalibs for your target,
+and the correct sysdeps directory, making use of the
+--with-include, --with-lib, --with-dynlib and --with-sysdeps
+options as necessary.
+
+
+* The slashpackage convention
+  ---------------------------
+
+ The slashpackage convention (http://cr.yp.to/slashpackage.html)
+is a package installation scheme that provides a few guarantees
+over other conventions such as the FHS, for instance fixed
+absolute pathnames. skarnet.org packages support it: use the
+--enable-slashpackage option to configure, or
+--enable-slashpackage=DIR for a prefixed DIR/package tree.
+This option will activate slashpackage support during the build
+and set slashpackage-compatible installation directories.
+If $package_home is the home of the package, defined as
+DIR/package/$category/$package-$version with the variables
+read from the package/info file, then:
+
+  --dynlibdir is set to $package_home/library.so
+  --bindir is set to $package_home/command
+  --sbindir is also set to $package_home/command (slashpackage
+differentiates root-only binaries by their Unix rights, not their
+location in the filesystem)
+  --libexecdir is also set to $package_home/command (slashpackage
+does not need a specific directory for internal binaries)
+  --libdir is set to $package_home/library
+  --includedir is set to $package_home/include
+
+ --prefix is pretty much ignored when you use --enable-slashpackage.
+You should probably not use both --enable-slashpackage and --prefix.
+
+ When using slashpackage, two additional Makefile targets are
+available after "make install":
+ - "make update" changes the default version of the software to the
+freshly installed one. (This is useful when you have several installed
+versions of the same software, which slashpackage supports.)
+ - "make -L global-links" adds links from /command and /library.so to the
+default version of the binaries and shared libraries. The "-L" option to
+make is necessary because targets are symbolic links, and the default make
+behaviour is to check the pointed file's timestamp and not the symlink's
+timestamp.
+
+
+* Absolute pathnames
+  ------------------
+
+ You may want to use fixed absolute pathnames even if you're not
+following the slashpackage convention: for instance, the Nix packaging
+system prefers calling binaries with immutable paths rather than rely on
+PATH resolution. If you are in that case, use the --enable-absolute-paths
+option to configure. This will ensure that programs calling binaries from
+this package will call them with their full installation path (in bindir)
+without relying on a PATH search.
+
+
+* Out-of-tree builds
+  ------------------
+
+ skarnet.org packages do not support out-of-tree builds. They
+are small, so it does not cost much to duplicate the entire
+source tree if parallel builds are needed.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..04360b4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,154 @@
+#
+# This Makefile requires GNU make.
+#
+# Do not make changes here.
+# Use the included .mak files.
+#
+
+it: all
+
+make_need := 3.81
+ifeq "" "$(strip $(filter $(make_need), $(firstword $(sort $(make_need) $(MAKE_VERSION)))))"
+fail := $(error Your make ($(MAKE_VERSION)) is too old. You need $(make_need) or newer)
+endif
+
+CC = $(error Please use ./configure first)
+
+STATIC_LIBS :=
+SHARED_LIBS :=
+INTERNAL_LIBS :=
+EXTRA_TARGETS :=
+LIB_DEFS :=
+
+define library_definition
+LIB$(firstword $(subst =, ,$(1))) := lib$(lastword $(subst =, ,$(1))).$(if $(DO_ALLSTATIC),a,so).xyzzy
+ifdef DO_SHARED
+SHARED_LIBS += lib$(lastword $(subst =, ,$(1))).so.xyzzy
+endif
+ifdef DO_STATIC
+STATIC_LIBS += lib$(lastword $(subst =, ,$(1))).a.xyzzy
+endif
+endef
+
+-include config.mak
+include package/targets.mak
+
+$(foreach var,$(LIB_DEFS),$(eval $(call library_definition,$(var))))
+
+include package/deps.mak
+
+version_m := $(basename $(version))
+version_M := $(basename $(version_m))
+version_l := $(basename $(version_M))
+CPPFLAGS_ALL := $(CPPFLAGS_AUTO) $(CPPFLAGS)
+CFLAGS_ALL := $(CFLAGS_AUTO) $(CFLAGS)
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+CFLAGS_SHARED := -fPIC
+else
+CFLAGS_SHARED :=
+endif
+LDFLAGS_ALL := $(LDFLAGS_AUTO) $(LDFLAGS)
+REALCC = $(CROSS_COMPILE)$(CC)
+AR := $(CROSS_COMPILE)ar
+RANLIB := $(CROSS_COMPILE)ranlib
+STRIP := $(CROSS_COMPILE)strip
+INSTALL := ./tools/install.sh
+
+ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS)
+ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) $(INTERNAL_LIBS)
+ALL_INCLUDES := $(wildcard src/include/$(package)/*.h)
+
+all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES) src/include/utmpx.h
+
+clean:
+	@exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo) $(EXTRA_TARGETS)
+
+distclean: clean
+	@exec rm -f config.mak src/include/$(package)/config.h
+
+tgz: distclean
+	@. package/info && \
+	rm -rf /tmp/$$package-$$version && \
+	cp -a . /tmp/$$package-$$version && \
+	cd /tmp && \
+	tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \
+	exec rm -rf /tmp/$$package-$$version
+
+strip: $(ALL_LIBS) $(ALL_BINS)
+ifneq ($(strip $(STATIC_LIBS)),)
+	exec $(STRIP) -x -R .note -R .comment -R .note.GNU-stack $(STATIC_LIBS)
+endif
+ifneq ($(strip $(ALL_BINS)$(SHARED_LIBS)),)
+	exec $(STRIP) -R .note -R .comment -R .note.GNU-stack $(ALL_BINS) $(SHARED_LIBS)
+endif
+
+install: install-dynlib install-libexec install-bin install-lib install-include
+install-dynlib: $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(dynlibdir)/lib%.so)
+install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%)
+install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%)
+install-lib: $(STATIC_LIBS:lib%.a.xyzzy=$(DESTDIR)$(libdir)/lib%.a)
+install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h) $(DESTDIR)$(includedir)/utmpx.h
+install-data: $(ALL_DATA:src/etc/%=$(DESTDIR)$(datadir)/%)
+
+ifneq ($(exthome),)
+
+$(DESTDIR)$(exthome): $(DESTDIR)$(home)
+	exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome)
+
+update: $(DESTDIR)$(exthome)
+
+global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M)) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%)
+
+$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/%
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/command/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M): $(DESTDIR)$(dynlibdir)/lib%.so.$(version_M)
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/library.so/$(<F) $@
+
+.PHONY: update global-links
+
+endif
+
+$(DESTDIR)$(datadir)/%: src/etc/%
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(dynlibdir)/lib%.so: lib%.so.xyzzy
+	$(INSTALL) -D -m 755 $< $@.$(version) && \
+	$(INSTALL) -l $(@F).$(version) $@.$(version_m) && \
+	$(INSTALL) -l $(@F).$(version_m) $@.$(version_M) && \
+	exec $(INSTALL) -l $(@F).$(version_M) $@
+
+$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/%: % package/modes
+	exec $(INSTALL) -D -m 600 $< $@
+	grep -- ^$(@F) < package/modes | { read name mode owner && \
+	if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \
+	chmod $$mode $@ ; }
+
+$(DESTDIR)$(libdir)/lib%.a: lib%.a.xyzzy
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/utmpx.h: src/include/utmpx.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+%.o: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $<
+
+%.lo: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $<
+
+$(ALL_BINS):
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(EXTRA_LIBS) $(LDLIBS)
+
+lib%.a.xyzzy:
+	exec $(AR) rc $@ $^
+	exec $(RANLIB) $@
+
+lib%.so.xyzzy:
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$(patsubst lib%.so.xyzzy,lib%.so.$(version_M),$@) $^ $(EXTRA_LIBS) $(LDLIBS)
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-lib install-include install-data
+
+.DELETE_ON_ERROR:
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..5bb2cac
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,6 @@
+Changelog for utmps.
+
+In 0.0.1.0
+----------
+
+ - Initial release.
diff --git a/README b/README
new file mode 100644
index 0000000..9817d42
--- /dev/null
+++ b/README
@@ -0,0 +1,27 @@
+utmps - a secure utmpx and wtmp implementation
+----------------------------------------------
+
+ utmps is a library implementing the utmpx.h
+family of functions, for use with C libraries that do not
+implement them, such as musl.
+
+ Its particularity is that it does not use setuid or setgid
+programs or files; it does not give random programs the right
+to modify system-wide files. Instead, it comes with a daemon
+that must be running for utmp account to be functional, and
+that takes charge of the utmp data management itself; utmpx.h
+primitives simply communicate with that daemon.
+
+ See http://skarnet.org/software/utmps/ for details.
+
+
+* Installation
+  ------------
+
+ See the INSTALL file.
+
+
+* Contact information
+  -------------------
+
+ Laurent Bercot <ska-skaware at skarnet.org>
diff --git a/README.macosx b/README.macosx
new file mode 100644
index 0000000..d71a096
--- /dev/null
+++ b/README.macosx
@@ -0,0 +1,4 @@
+
+ This package will compile and run on Darwin (MacOS X), but the building of
+shared libraries is not supported.
+ Make sure you use the --disable-shared option to configure.
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..91a5b26
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,12 @@
+
+ This package assumes the existence of a POSIX shell in /bin/sh.
+ On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide
+a POSIX shell in /usr/xpg4/bin/sh.
+
+ To compile this package on Solaris, you will need to run
+
+     ./patch-for-solaris
+
+ before you run ./configure. This script will change the #! invocation
+of the configure script and various tools so that a POSIX shell is used
+for the compilation process.
diff --git a/configure b/configure
new file mode 100755
index 0000000..f21429a
--- /dev/null
+++ b/configure
@@ -0,0 +1,470 @@
+#!/bin/sh
+
+. package/info
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [TARGET]
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=BINDIR               user executables [EPREFIX/bin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib/$package]
+  --includedir=DIR              C header files [PREFIX/include]
+
+ If no --prefix option is given, by default libdir (but not dynlibdir) will be
+ /usr/lib/$package, and includedir will be /usr/include.
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [PREFIX/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+ If no --prefix option is given, by default sysdeps will be fetched from
+ /usr/lib/skalibs/sysdeps.
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-all-pic              build everything as PIC [enabled iff toolchain builds PIE]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-absolute-paths       do not rely on PATH to access this package's binaries,
+                                  hardcode absolute BINDIR/foobar paths instead [disabled]
+
+  --with-utmp-socket=PATH       assume the utmpd socket is at PATH [/run/utmps/utmpd-socket]
+  --with-wtmp-socket=PATH       assume the wtmpd socket is at PATH [/run/utmps/wtmpd-socket]
+
+EOF
+exit 0
+}
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://www.etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO="$*"
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $CFLAGS $CFLAGS_POST $LDFLAGS_AUTO $LDFLAGS $LDFLAGS_POST -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+CC_AUTO=
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -iquote src/include-local -Isrc/include"
+CPPFLAGS_POST="$CPPFLAGS"
+CPPFLAGS=
+CFLAGS_AUTO="-pipe -Wall"
+CFLAGS_POST="$CFLAGS"
+CFLAGS=-O2
+LDFLAGS_AUTO=
+LDFLAGS_POST="$LDFLAGS"
+LDFLAGS=
+LDFLAGS_NOSHARED=
+LDFLAGS_SHARED=-shared
+prefix=
+exec_prefix='$prefix'
+dynlibdir='$prefix/lib'
+libexecdir='$exec_prefix/libexec'
+bindir='$exec_prefix/bin'
+libdir='$prefix/lib/$package'
+includedir='$prefix/include'
+sysdeps='$prefix/lib/skalibs/sysdeps'
+manualsysdeps=false
+shared=false
+static=true
+allpic=detect
+slashpackage=false
+abspath=false
+sproot=
+home=
+exthome=
+allstatic=true
+evenmorestatic=false
+addincpath=''
+addlibspath=''
+addlibdpath=''
+vpaths=''
+vpathd=''
+build=
+utmpdpath=/run/utmps/utmpd-socket
+wtmpdpath=/run/utmps/wtmpd-socket
+
+for arg ; do
+  case "$arg" in
+    --help) usage ;;
+    --prefix=*) prefix=${arg#*=} ;;
+    --exec-prefix=*) exec_prefix=${arg#*=} ;;
+    --dynlibdir=*) dynlibdir=${arg#*=} ;;
+    --libexecdir=*) libexecdir=${arg#*=} ;;
+    --bindir=*) bindir=${arg#*=} ;;
+    --libdir=*) libdir=${arg#*=} ;;
+    --includedir=*) includedir=${arg#*=} ;;
+    --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;;
+    --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;;
+    --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;;
+    --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;;
+    --enable-shared|--enable-shared=yes) shared=true ;;
+    --disable-shared|--enable-shared=no) shared=false ;;
+    --enable-static|--enable-static=yes) static=true ;;
+    --disable-static|--enable-static=no) static=false ;;
+    --enable-allstatic|--enable-allstatic=yes) allstatic=true ;;
+    --disable-allstatic|--enable-allstatic=no) allstatic=false ; evenmorestatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) allstatic=true ; evenmorestatic=true ;;
+    --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;;
+    --enable-all-pic|--enable-all-pic=yes) allpic=true ;;
+    --disable-all-pic|--enable-all-pic=no) allpic=false ;;
+    --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;;
+    --enable-slashpackage) sproot= ; slashpackage=true ;;
+    --disable-slashpackage) sproot= ; slashpackage=false ;;
+    --enable-absolute-paths|--enable-absolute-paths=yes) abspath=true ;;
+    --disable-absolute-paths|--enable-absolute-paths=no) abspath=false ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;;
+    --host=*|--target=*) target=${arg#*=} ;;
+    --build=*) build=${arg#*=} ;;
+    --with-utmpd-socket=*) utmpdpath=${arg#*=} ;;
+    --with-wtmpd-socket=*) wtmpdpath=${arg#*=} ;;
+    -* ) echo "$0: unknown option $arg" ;;
+    *=*) ;;
+    *) target=$arg ;;
+  esac
+done
+
+# Add /usr in the default default case
+if test -z "$prefix" ; then
+  if test "$libdir" = '$prefix/lib/$package' ; then
+    libdir=/usr/lib/$package
+  fi
+  if test "$includedir" = '$prefix/include' ; then
+    includedir=/usr/include
+  fi
+  if test "$sysdeps" = '$prefix/lib/skalibs/sysdeps' ; then
+    sysdeps=/usr/lib/skalibs/sysdeps
+  fi
+fi
+
+# Expand installation directories
+stripdir prefix
+for i in exec_prefix dynlibdir libexecdir bindir libdir includedir sysdeps sproot ; do
+  eval tmp=\${$i}
+  eval $i=$tmp
+  stripdir $i
+done
+
+# Get usable temp filenames
+i=0
+set -C
+while : ; do
+  i=$(($i+1))
+  tmpc="./tmp-configure-$$-$PPID-$i.c"
+  tmpe="./tmp-configure-$$-$PPID-$i.tmp"
+  2>|/dev/null > "$tmpc" && break
+  2>|/dev/null > "$tmpe" && break
+  test "$i" -gt 50 && fail "$0: cannot create temporary files"
+done
+set +C
+trap 'rm -f "$tmpc" "$tmpe"' EXIT ABRT INT QUIT TERM HUP
+
+# Set slashpackage values
+if $slashpackage ; then
+  home=${sproot}/package/${category}/${package}-${version}
+  exthome=${sproot}/package/${category}/${package}
+  if $manualsysdeps ; then
+    :
+  else
+    sysdeps=${sproot}/package/prog/skalibs/sysdeps
+  fi
+  extbinprefix=${exthome}/command
+  dynlibdir=${home}/library.so
+  bindir=${home}/command
+  libdir=${home}/library
+  libexecdir=$bindir
+  includedir=${home}/include
+  while read dep ; do
+    addincpath="$addincpath -I${sproot}${dep}/include"
+    vpaths="$vpaths ${sproot}${dep}/library"
+    addlibspath="$addlibspath -L${sproot}${dep}/library"
+    vpathd="$vpathd ${sproot}${dep}/library.so"
+    addlibdpath="$addlibdpath -L${sproot}${dep}/library.so"
+  done < package/deps-build
+fi
+
+# Find a C compiler to use
+if test -n "$target" && test x${build} != x${target} ; then
+  cross=${target}-
+else
+  cross=
+fi
+echo "checking for C compiler..."
+trycc ${cross}${CC}
+trycc ${cross}gcc
+trycc ${cross}clang
+trycc ${cross}cc
+test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
+echo "  ... $CC_AUTO"
+echo "checking whether C compiler works... "
+echo "typedef int x;" > "$tmpc"
+if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST -c -o /dev/null "$tmpc" 2>"$tmpe" ; then
+  echo "  ... yes"
+else
+  echo "  ... no. Compiler output follows:"
+  cat < "$tmpe"
+  exit 1
+fi
+
+echo "checking target system type..."
+if test -z "$target" ; then
+  if test -n "$build" ; then
+    target=$build ;
+  else
+    target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown
+  fi
+fi
+echo "  ... $target"
+if test ! -d $sysdeps || test ! -f $sysdeps/target ; then
+  echo "$0: error: $sysdeps is not a valid sysdeps directory"
+  exit 1
+fi
+if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then
+  echo "$0: error: target $target does not match the contents of $sysdeps/target"
+  exit 1
+fi
+
+if test $allpic = detect ; then
+  echo "Checking whether we need to build everything as PIC..."
+  if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST -dM -E - < /dev/null | grep -qF __PIE__ ; then
+    allpic=true
+    echo "  ... yes"
+  else
+    allpic=false
+    echo "  ... no"
+  fi
+fi
+if $allpic ; then
+  tryflag CFLAGS_AUTO -fPIC
+fi
+
+spawn_lib=$(cat $sysdeps/spawn.lib)
+socket_lib=$(cat $sysdeps/socket.lib)
+sysclock_lib=$(cat $sysdeps/sysclock.lib)
+tainnow_lib=$(cat $sysdeps/tainnow.lib)
+timer_lib=$(cat $sysdeps/timer.lib)
+util_lib=$(cat $sysdeps/util.lib)
+
+tryflag CFLAGS_AUTO -std=c99
+tryflag CFLAGS -fomit-frame-pointer
+tryflag CFLAGS_AUTO -fno-exceptions
+tryflag CFLAGS_AUTO -fno-unwind-tables
+tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables
+tryflag CFLAGS_AUTO -Wa,--noexecstack
+tryflag CFLAGS -fno-stack-protector
+tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration
+tryflag CPPFLAGS_AUTO -Werror=implicit-int
+tryflag CPPFLAGS_AUTO -Werror=pointer-sign
+tryflag CPPFLAGS_AUTO -Werror=pointer-arith
+tryflag CFLAGS_AUTO -ffunction-sections
+tryflag CFLAGS_AUTO -fdata-sections
+
+tryldflag LDFLAGS_AUTO -Wl,--sort-section=alignment
+tryldflag LDFLAGS_AUTO -Wl,--sort-common
+
+CPPFLAGS_AUTO="${CPPFLAGS_AUTO}${addincpath}"
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS -Wl,--hash-style=both
+fi
+
+LDFLAGS_SHARED="${LDFLAGS_SHARED}${addlibdpath}"
+
+if $allstatic ; then
+  LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibspath}"
+  tryldflag LDFLAGS_NOSHARED -Wl,--gc-sections
+else
+  LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibdpath}"
+fi
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+echo "creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sproot := $sproot
+version := $version
+home := $home
+exthome := $exthome
+SPAWN_LIB := ${spawn_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TAINNOW_LIB := ${tainnow_lib}
+TIMER_LIB := ${timer_lib}
+UTIL_LIB := ${util_lib}
+
+CC := ${CC_AUTO##${cross}}
+CPPFLAGS_AUTO := $CPPFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS $CPPFLAGS_POST
+CFLAGS_AUTO := $CFLAGS_AUTO
+CFLAGS := $CFLAGS $CFLAGS_POST
+LDFLAGS_AUTO := $LDFLAGS_AUTO
+LDFLAGS := $LDFLAGS $LDFLAGS_POST
+LDFLAGS_SHARED := $LDFLAGS_SHARED
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%.a$vpaths
+vpath lib%.so$vpathd
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  echo "DO_ALLSTATIC := 1"
+else
+  echo ".LIBPATTERNS := lib%.so"
+fi
+if $static ; then
+  echo "DO_STATIC := 1"
+else
+  echo "DO_STATIC :="
+fi
+if $shared ; then
+  echo "DO_SHARED := 1"
+else
+  echo "DO_SHARED :="
+fi
+if $allpic ; then
+  echo "STATIC_LIBS_ARE_PIC := 1"
+else
+  echo "STATIC_LIBS_ARE_PIC :="
+fi
+
+exec 1>&3 3>&-
+echo "  ... done."
+
+echo "creating src/include/${package}/config.h..."
+mkdir -p -m 0755 src/include/${package}
+exec 3>&1 1> src/include/${package}/config.h
+cat <<EOF
+/* ISC license. */
+
+/* Generated by: $cmdline */
+
+#ifndef ${package_macro_name}_CONFIG_H
+#define ${package_macro_name}_CONFIG_H
+
+#define ${package_macro_name}_VERSION "$version"
+EOF
+if $slashpackage ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix/\""
+elif $abspath ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$bindir/\""
+else
+  echo "#define ${package_macro_name}_BINPREFIX \"\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"\""
+fi
+echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir/\""
+echo "#define ${package_macro_name}_UTMPD_PATH \"$utmpdpath\""
+echo "#define ${package_macro_name}_WTMPD_PATH \"$wtmpdpath\""
+echo
+echo "#endif"
+exec 1>&3 3>&-
+echo "  ... done."
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..8d080da
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,132 @@
+<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>utmps - a secure utmp implementation</title>
+    <meta name="Description" content="utmps - a secure utmp implementation" />
+    <meta name="Keywords" content="utmps utmp utmpx unix login accounting wtmp laurent bercot skarnet" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> utmps </h1>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ utmps is an implementation of the <tt>utmp.h</tt> and <tt>utmpx.h</tt>
+family of functions performing user accounting on Unix systems.
+</p>
+
+<p>
+ Traditionally, <tt>utmp</tt> functionality is provided by the system's
+libc. However, not all libcs implement utmp: for instance the
+<a href="https://musl-libc.org/">musl</a> libc, on Linux, does not. The
+main reason for it is that <tt>utmp</tt> functionality is difficult to
+implement in a secure way; in particular, it is impossible to implement
+without either running a daemon or allowing arbitrary programs to tamper
+with user accounting.
+</p>
+
+<p>
+ <tt>utmps</tt> is a secure implementation of user accounting, using
+a daemon as the only authority to manage the utmp and wtmp data; programs
+running utmp functions are just clients to this daemon.
+</p>
+
+<hr />
+
+<ul>
+ <li> <a href="overview.html">An overview of utmps</a> </li>
+</ul>
+
+<hr />
+
+<h2> Installation </h2>
+
+<h3> Requirements </h3>
+
+<ul>
+ <li> A POSIX-compliant system with a standard C development environment </li>
+ <li> GNU make, version 3.81 or later </li>
+ <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version
+2.6.1.0 or later. It's a build-time requirement. It's also a run-time
+requirement if you link against the shared version of the skalibs
+library. </li>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ s6 is free software. It is available under the
+<a href="http://opensource.org/licenses/ISC">ISC license</a>.
+</p>
+
+<h3> Download </h3>
+
+<ul>
+ <li> The current released version of utmps is <a href="utmps-0.0.1.0.tar.gz">0.0.1.0</a>. </li>
+ <li> Alternatively, you can checkout a copy of the
+<a href="//git.skarnet.org/cgi-bin/cgit.cgi/utmps/">utmps
+git repository</a>:
+<pre> git clone git://git.skarnet.org/utmps </pre> </li>
+ <li> There's also a
+<a href="https://github.com/skarnet/utmps">GitHub mirror</a>
+of the utmps git repository. </li>
+</ul>
+
+<h3> Compilation </h3>
+
+<ul>
+ <li> See the enclosed INSTALL file for installation details. </li>
+</ul>
+
+<h3> Upgrade notes </h3>
+
+<ul>
+ <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between
+the previous versions of utmps and the current one. </li>
+</ul>
+
+<hr />
+
+<h2> Reference </h2>
+
+<h3> Commands </h3>
+
+<ul>
+<li><a href="utmps-utmpd.html">The <tt>utmps-utmpd</tt> program</a></li>
+<li><a href="utmps-wtmpd.html">The <tt>utmps-wtmpd</tt> program</a></li>
+</ul>
+
+<h3> Libraries </h3>
+
+<ul>
+<li> <a href="libutmps/">The <tt>utmps</tt> library interface</a> </li>
+<li> <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/utmpx.h.html">The
+<tt>utmpx.h</tt> library interface</a> is implemented on top of the utmps library. </li>
+<li> <a href="http://man7.org/linux/man-pages/man3/logwtmp.3.html">The <tt>logwtmp()</tt>
+and <tt>updwtmpx()</tt> functions</a> are also implemented. </li>
+</ul>
+
+<hr />
+
+<a name="related">
+<h2> Related resources </h2>
+</a>
+
+<h3> utmps discussion </h3>
+
+<ul>
+ <li> <tt>utmps</tt> is discussed on the
+<a href="//skarnet.org/lists.html#skaware">skaware</a> mailing-list. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libutmps/index.html b/doc/libutmps/index.html
new file mode 100644
index 0000000..0372726
--- /dev/null
+++ b/doc/libutmps/index.html
@@ -0,0 +1,171 @@
+<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>utmps: the utmps library interface</title>
+    <meta name="Description" content="utmps: the utmps library interface" />
+    <meta name="Keywords" content="utmps utmp wtmp library libutmps" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="../">utmps</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>utmps</tt> library interface </h1>
+
+<h2> General information </h2>
+
+<p>
+ <tt>libutmps</tt> is a client library meant to be used by client
+programs needing utmp functionality. It interacts with the
+<a href="../utmps-utmpd.html">utmps-utmpd</a> and
+<a href="../utmps-wtmpd.html">utmps-wtmpd</a> daemons.
+</p>
+
+<p>
+ Application programs can use it directly, but most existing programs
+simply use the standard
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/utmpx.h.html">utmpx.h</a>
+interface, which in utmps is implemented as a series of thin wrappers
+around the utmps library.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure the utmps headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;utmps/utmps.h&gt;</tt> </li>
+ <li> To use the standard <tt>utmpx.h</tt> interface, you can
+just <tt>#include &lt;utmpx.h&gt;</tt>, which will work as long
+as the <tt>utmps/utmpx.h</tt> header is accessible in your header
+search path. </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the utmps library, as well as the skalibs library,
+are visible in your library search path. </li>
+ <li> Link against <tt>-lutmps</tt>, <tt>-lskarnet</tt>, </li>
+<tt>`cat $SYSDEPS/socket.lib`</tt> and
+<tt>`cat $SYSDEPS/tainnow.lib`</tt>, $SYSDEPS being your skalibs
+sysdeps directory. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<p>
+ Check the <tt>utmps/utmps.h</tt> header for the exact function list,
+and the <tt>utmps/utmpx.h</tt> header for the definition of the standard
+<tt>struct utmpx</tt> data type.
+</p>
+
+<h3> Synchronous functions with a specified maximum execution time </h3>
+
+<p>
+ The standard utmpx.h functions are fully synchronous. They were not
+initially meant to perform inter-processus communication; however, in
+utmps, they do. Their synchronous nature is obviously not changed here,
+but the underlying utmps functions use a safety mechanism to bound their
+execution time in case daemons fail to respond. This mechanism is described,
+for instance,
+<a href="//skarnet.org/libs6/ftrigr.html#synctimed">here</a>.
+</p>
+
+<h3> Starting and ending a session </h3>
+
+<p>
+<code>int utmps_start (utmps *a, char const *path, tain_t const *deadline, tain_t *stamp)</code> <br />
+Connects to a <tt>utmps-utmpd</tt> service listening on a Unix domain socket at <em>path</em>.
+<em>a</em> must point to a previously allocated <em>utmps</em> object, which is flat and can
+be allocated in the stack. This object must have been initialized to UTMPS_ZERO before the call.
+<em>a</em> will be a handle describing the session, and must be given to all utmps functions
+called in that session.
+<em>deadline</em> and <em>stamp</em> are used to bound the execution time as described in the
+above link. The function returns 1 if it succeeds; it returns 0, and sets errno, if it fails.
+</p>
+
+<p>
+<code>void utmps_end (utmps *a)</code> <br />
+Ends the session described by <em>a</em>, and releases all used resources.
+</p>
+
+<h3> Reading from the utmp database </h3>
+
+<p>
+ Any user authorized to connect to the utmpd service can call these functions. In other
+words, if <tt>utmps_start()</tt> succeeded, then these functions should not fail due to
+insufficient permissions.
+</p>
+
+<p>
+<code>int utmps_rewind (utmps *a, tain_t const *deadline, tain_t *stamp)</code> <br />
+Performs the <tt>setutxent()</tt> functionality on the utmp database addressed via <em>a</em>,
+i.e. sets the internal pointer at the start of the database.
+On success, stores the result into <em>*b</em> and returns 1. On failure, returns 0 and sets errno.
+</p>
+
+<p>
+<code>int utmps_getent (utmps *a, struct utmpx *b, tain_t const *deadline, tain_t *stamp)</code> <br />
+Performs the <tt>getutxent()</tt> functionality on the utmp database addressed via <em>a</em>.
+On success, stores the result into <em>*b</em> and returns 1. On failure, returns 0 and sets errno.
+</p>
+
+<p>
+<code>int utmps_getid (utmps *a, unsigned short type, char const *id, struct utmpx *b, tain_t const *deadline, tain_t *stamp)</code> <br />
+Performs the <tt>getutxid()</tt> functionality on the utmp database addressed via <em>a</em>,
+using ut_type <em>type</em> and ut_id <em>id</em>. <em>id</em> must be a null-terminated
+string; only its first UTMPS_UT_IDSIZE-1 characters will be taken into account.
+On success, the function stores the result into <em>*b</em> and returns 1. On failure,
+it returns 0 and sets errno.
+</p>
+
+<p>
+<code>int utmps_getline (utmps *a, char const *line, struct utmpx *b, tain_t const *deadline, tain_t *stamp)</code> <br />
+Performs the <tt>getutxline()</tt> functionality on the utmp database addressed via <em>a</em>,
+using ut_line <em>line</em>. <em>line</em> must be a null-terminated
+string; only its first UTMPS_UT_LINESIZE-1 characters will be taken into account.
+On success, the function stores the result into <em>*b</em> and returns 1. On failure,
+it returns 0 and sets errno.
+</p>
+
+<h3> Writing to the utmp database </h3>
+
+<p>
+ Currently, only the super-user is allowed to use this function.
+</p>
+
+<p>
+<code>int utmps_putline (utmps *a, struct utmpx const *b, tain_t const *deadline, tain_t *stamp)</code> <br />
+Performs the <tt>pututxline()</tt> functionality on the utmp database addressed via <em>a</em>,
+i.e. writes the <em>*b</em> structure into the utmp database looking for an appropriate
+record to replace, and appending to the database if no such record can be found.
+On success, the function returns 1. On failure, it returns 0 and sets errno.
+</p>
+
+<h3> Writing to the wtmp database </h3>
+
+<p>
+<code>int utmps_updwtmpx (char const *path, struct utmpx const *b, tain_t const *deadline, tain_t *stamp)</code> <br />
+Unlike the previous functions, <tt>utmps_updwtmpx()</tt> does not use a utmps handle, because
+it does not connect to an utmpd service. Instead, it connects to a wtmpd service listening
+on Unix domain socket <em>path</em>, once for every call. It appends the <em>*b</em> structure
+to the wtmp database, returning 1 on success and 0 (and setting errno) on failure.
+</p>
+
+<p>
+ <tt>utmps_updwtmpx()</tt> will only succeed if the caller is root, or if
+<em>b&rarr;ut_user</em> resolves (according to <tt>getpwnam()</tt>) to the
+effective uid of the caller. In other words: users can append phony records
+for themselves, but not for others, and only root can spoof the whole
+wtmp database.
+</p>
+
+</body>
+</html>
diff --git a/doc/overview.html b/doc/overview.html
new file mode 100644
index 0000000..22e9210
--- /dev/null
+++ b/doc/overview.html
@@ -0,0 +1,109 @@
+<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>utmps: an overview</title>
+    <meta name="Description" content="s6: an overview" />
+    <meta name="Keywords" content="utmps overview utmp wtmp utmpx login user accounting unix" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">utmps</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> An overview of utmps </h1>
+
+<p>
+ utmps is a secure implementation of the <em>utmp</em> functionality, i.e.
+user accounting on Unix systems. It includes full POSIX
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/utmpx.h.html">utmpx.h</a>
+functionality, a few extensions created by GNU, and an underlying
+<a href="libutmps/">C client library</a> with better error reporting
+than the POSIX interface specifies.
+</p>
+
+<h2> The issues with traditional utmp </h2>
+
+<p>
+ Traditional <em>utmp</em> implementations, as performed by most Unix
+libcs, are woefully insecure. The fundamental issue with <em>utmp</em>
+is that it requires user programs to write to files (the utmp or wtmp
+databases) owned by either root or a specific system user. That means
+having the suid bit set on programs using it.
+</p>
+
+<h2> The utmps solution </h2>
+
+<p>
+ utmps uses the age old Unix client-server model, following the
+adage "one resource &rarr; one daemon". It provides two daemons,
+<a href="utmps-utmpd.html">utmps-utmpd</a> and
+<a href="utmps-wtmpd.html">utmps-wtmpd</a>, which should be the only
+programs allowed to access the utmp and wtmp databases respectively.
+It provides the
+<a href="libutmps/">utmps client library</a> to communicate with
+those daemons; and it implements the
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/utmpx.h.html">utmpx.h</a>
+interfaces, and the extensions, as wrappers for this client library.
+</p>
+
+<h2> Authentication, local services and superservers </h2>
+
+<p>
+<a href="utmps-utmpd.html">utmps-utmpd</a> and
+<a href="utmps-wtmpd.html">utmps-wtmpd</a> do not listen to the
+network themselves. They are designed to serve only one client
+connection, following the
+<a href="http://cr.yp.to/proto/ucspi.txt">UCSPI model</a> -
+also known as the inetd model. To implement the utmpd and wtmpd
+<a href="//skarnet.org/software/s6/localservice.html">local services</a>,
+a Unix domain superserver such as
+<a href="//skarnet.org/software/s6/s6-ipcserver.html">s6-ipcserver</a>
+is required. s6-ipcserver listens to a socket, and spawns a
+<a href="utmps-utmpd.html">utmps-utmpd</a> process when a client
+calls <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/setutxent.html">setutxent()</a>
+for instance.
+</p>
+
+<p>
+ The utmpd and wtmpd services must be started at boot time in
+order for utmp calls to succeed. The <tt>examples/</tt> subdirectory
+of the utmps package has examples on how to start those services
+when using the
+<a href="//skarnet.org/software/s6/">s6</a> supervision suite,
+the <a href="//skarnet.org/software/s6-rc/">s6-rc</a> service manager, or
+the <a href="https://wiki.gentoo.org/wiki/OpenRC">OpenRC</a> service manager.
+</p>
+
+<p>
+ This model has advantages and drawbacks. The main drawback is that it requires
+a daemon to be running in order for the system to provide full POSIX
+functionality. The main advantage, on the other hand, is that no program
+needs to be suid or sgid, and permissions can actually be quite fine-grained.
+</p>
+
+<ul>
+ <li> <a href="utmps-utmpd.html">utmps-utmpd</a> will allow any user to
+read from the utmp database, but will only allow root to write to it. </li>
+ <li> <a href="utmps-wtmpd.html">utmps-wtmpd</a> will only allow a user
+to add an entry to the wtmp database if the user is root, or if the
+<tt>ut_user</tt> field of the added entry resolves to the user's effective
+uid. </li>
+  <li> The <a href="//skarnet.org/software/s6/s6-ipcserver.html">s6-ipcserver</a>
+superserver, which is recommended to implement the utmpd and wtmpd services,
+allows fine-tuning the permissions: it is possible to deny users from
+connecting to the service, or to only allow certain groups, etc. </li>
+</ul>
+
+<p>
+ All in all, I believe the flexibility it offers overweighs the inconvenience
+of having to run services before providing utmp/wtmp.
+</p>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..b5c9db9
--- /dev/null
+++ b/doc/upgrade.html
@@ -0,0 +1,28 @@
+<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>How to upgrade utmps</title>
+    <meta name="Description" content="How to upgrade utmps" />
+    <meta name="Keywords" content="utmps installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">utmps</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> What has changed in utmps </h1>
+
+<h2> in 0.0.1.0 </h2>
+
+<p>
+ Initial release.
+</p>
+
+</body>
+</html>
diff --git a/doc/utmps-utmpd.html b/doc/utmps-utmpd.html
new file mode 100644
index 0000000..57fa6ac
--- /dev/null
+++ b/doc/utmps-utmpd.html
@@ -0,0 +1,77 @@
+<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>utmps: the utmps-utmpd program</title>
+    <meta name="Description" content="utmps: the utmps-utmpd program" />
+    <meta name="Keywords" content="utmps utmp utmpd daemon service utmps-utmpd" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">utmps</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The utmps-utmpd program </h1>
+
+<p>
+utmps-utmpd manages the utmp database. It expects to be able to create, read and
+modify a file named <tt>utmp</tt> in the directory it is launched in.
+</p>
+
+<p>
+ It is not meant to be called directly; instead, it is expected to be run from
+a script as a part of a "utmpd"
+<a href="//skarnet.org/software/s6/localservice.html">local service</a>.
+</p>
+
+<p>
+ The <tt>examples/</tt> subdirectory of the utmps package provides examples
+on how to run such a service.
+ The simplest way to do so, for testing purposes, is a command line such as:
+</p>
+<pre>s6-ipcserver -l0 utmpd-socket utmps-utmpd</pre>
+<p>
+ while being in the <tt>/run/utmps</tt> directory.
+</p>
+
+<p>
+<tt>/run/utmps/utmpd-socket</tt> is the default place where utmps's
+implementation of the <tt>utmpx.h</tt> functions expects the utmpd
+service to be. It can be changed at build time by giving the
+<tt>--with-utmp-socket=PATH</tt> option to configure.
+</p>
+
+<p>
+ utmps-utmpd does not listen to the socket itself: it reads from its
+standard input and writes to its standard output. It relies
+on a superserver such as
+<a href="//skarnet.org/software/s6/s6-ipcserver.html">s6-ipcserver</a>
+to manage connections to the socket. An instance of utmps-utmpd is run
+for every client connection;
+every instance reads the effective uid of the client in an environment
+variable set by the superserver, which allows it to filter operations -
+for instance, it allows any user to read from the database but it only
+allows root to write to it.
+</p>
+
+<p>
+ If more fine-grained authorizations are required (only allowing
+certain users and groups to connect to the service), the superserver
+can be configured to enforce them.
+</p>
+
+<p>
+ utmps-utmpd does not need to run as root, provided it can write its file;
+it is recommended to create a <em>utmp</em> user and group, dedicated to
+utmps-utmpd and
+<a href="utmps-wtmpd.html">utmps-wtmpd</a> operation, and run the
+superserver as this user and group.
+</p>
+
+</body>
+</html>
diff --git a/doc/utmps-wtmpd.html b/doc/utmps-wtmpd.html
new file mode 100644
index 0000000..a1e090b
--- /dev/null
+++ b/doc/utmps-wtmpd.html
@@ -0,0 +1,78 @@
+<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>utmps: the utmps-wtmpd program</title>
+    <meta name="Description" content="utmps: the utmps-wtmpd program" />
+    <meta name="Keywords" content="utmps wtmp wtmpd daemon service utmps-wtmpd" />
+    <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">utmps</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The utmps-wtmpd program </h1>
+
+<p>
+utmps-wtmpd manages the wtmp database. It expects to be able to create, read and
+modify a file named <tt>wtmp</tt> in the directory it is launched in.
+</p>
+
+<p>
+ It is not meant to be called directly; instead, it is expected to be run from
+a script as a part of a "wtmpd"
+<a href="//skarnet.org/software/s6/localservice.html">local service</a>.
+</p>
+
+<p>
+ The <tt>examples/</tt> subdirectory of the utmps package provides examples
+on how to run such a service.
+ The simplest way to do so, for testing purposes, is a command line such as:
+</p>
+<pre>s6-ipcserver -l0 utmpd-socket utmps-wtmpd</pre>
+<p>
+ while being in the <tt>/run/utmps</tt> directory.
+</p>
+
+<p>
+<tt>/run/utmps/wtmpd-socket</tt> is the default place where utmps's
+implementation of the <tt>updwtmpx()</tt> function expects the wtmpd
+service to be. It can be changed at build time by giving the
+<tt>--with-wtmp-socket=PATH</tt> option to configure.
+</p>
+
+<p>
+ utmps-wtmpd does not listen to the socket itself: it reads from its
+standard input and writes to its standard output. It relies
+on a superserver such as
+<a href="//skarnet.org/software/s6/s6-ipcserver.html">s6-ipcserver</a>
+to manage connections to the socket. An instance of utmps-wtmpd is run
+for every client connection;
+every instance reads the effective uid of the client in an environment
+variable set by the superserver, which allows it to filter operations:
+only lines whose <tt>ut_user</tt> field resolves to the effective uid of
+the client will be appended to the database.
+</p>
+
+<p>
+ If more fine-grained authorizations are required (only allowing
+certain users and groups to connect to the service), the superserver
+can be configured to enforce them. For instance, it is possible to
+tell s6-ipcserver to only accept connections from root.
+</p>
+
+<p>
+ utmps-wtmpd does not need to run as root, provided it can write its file;
+it is recommended to create a <em>utmp</em> user and group, dedicated to
+utmps-wtmpd and
+<a href="utmps-utmpd.html">utmps-utmpd</a> operation, and run the
+superserver as this user and group.
+</p>
+
+</body>
+</html>
diff --git a/examples/openrc/utmpd b/examples/openrc/utmpd
new file mode 100755
index 0000000..a091528
--- /dev/null
+++ b/examples/openrc/utmpd
@@ -0,0 +1,8 @@
+#!/sbin/openrc-run
+
+# Assumes the /run/utmps directory already exists and belongs to utmp
+
+name="utmpd"
+command="s6-ipcserver -- /run/utmps/utmpd-socket utmps-utmpd"
+pidfile="/run/utmps/utmpd.pid"
+start_stop_daemon_args="-b -m -c utmp -d /run/utmps"
diff --git a/examples/openrc/wtmpd b/examples/openrc/wtmpd
new file mode 100755
index 0000000..5485a8d
--- /dev/null
+++ b/examples/openrc/wtmpd
@@ -0,0 +1,8 @@
+#!/sbin/openrc-run
+
+# Assumes the /run/utmps directory already exists and belongs to utmp
+
+name="wtmpd"
+command="s6-ipcserver -- /run/utmps/wtmpd-socket utmps-wtmpd"
+pidfile="/run/utmps/wtmpd.pid"
+start_stop_daemon_args="-b -m -c utmp -d /run/utmps"
diff --git a/examples/s6-rc/sutmp-prepare/dependencies b/examples/s6-rc/sutmp-prepare/dependencies
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/s6-rc/sutmp-prepare/dependencies
@@ -0,0 +1 @@
+
diff --git a/examples/s6-rc/sutmp-prepare/down b/examples/s6-rc/sutmp-prepare/down
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/s6-rc/sutmp-prepare/down
@@ -0,0 +1 @@
+
diff --git a/examples/s6-rc/sutmp-prepare/type b/examples/s6-rc/sutmp-prepare/type
new file mode 100644
index 0000000..bdd22a1
--- /dev/null
+++ b/examples/s6-rc/sutmp-prepare/type
@@ -0,0 +1 @@
+oneshot
diff --git a/examples/s6-rc/sutmp-prepare/up b/examples/s6-rc/sutmp-prepare/up
new file mode 100644
index 0000000..6091aad
--- /dev/null
+++ b/examples/s6-rc/sutmp-prepare/up
@@ -0,0 +1,2 @@
+foreground { mkdir -p -m 0755 /run/utmps }
+chown utmp:utmp /run/utmps
diff --git a/examples/s6-rc/utmpd-log/consumer-for b/examples/s6-rc/utmpd-log/consumer-for
new file mode 100644
index 0000000..fe07d03
--- /dev/null
+++ b/examples/s6-rc/utmpd-log/consumer-for
@@ -0,0 +1 @@
+utmpd
diff --git a/examples/s6-rc/utmpd-log/dependencies b/examples/s6-rc/utmpd-log/dependencies
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/s6-rc/utmpd-log/dependencies
@@ -0,0 +1 @@
+
diff --git a/examples/s6-rc/utmpd-log/run b/examples/s6-rc/utmpd-log/run
new file mode 100644
index 0000000..586e53d
--- /dev/null
+++ b/examples/s6-rc/utmpd-log/run
@@ -0,0 +1,5 @@
+#!/bin/execlineb -P
+
+s6-setuidgid utmplog
+exec -c
+s6-log t /var/log/utmpd
diff --git a/examples/s6-rc/utmpd-log/type b/examples/s6-rc/utmpd-log/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/examples/s6-rc/utmpd-log/type
@@ -0,0 +1 @@
+longrun
diff --git a/examples/s6-rc/utmpd/dependencies b/examples/s6-rc/utmpd/dependencies
new file mode 100644
index 0000000..6cfcf60
--- /dev/null
+++ b/examples/s6-rc/utmpd/dependencies
@@ -0,0 +1 @@
+utmps-prepare
diff --git a/examples/s6-rc/utmpd/notification-fd b/examples/s6-rc/utmpd/notification-fd
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/examples/s6-rc/utmpd/notification-fd
@@ -0,0 +1 @@
+3
diff --git a/examples/s6-rc/utmpd/pipeline-name b/examples/s6-rc/utmpd/pipeline-name
new file mode 100644
index 0000000..d75e141
--- /dev/null
+++ b/examples/s6-rc/utmpd/pipeline-name
@@ -0,0 +1 @@
+utmpd-pipeline
diff --git a/examples/s6-rc/utmpd/producer-for b/examples/s6-rc/utmpd/producer-for
new file mode 100644
index 0000000..8ff8e12
--- /dev/null
+++ b/examples/s6-rc/utmpd/producer-for
@@ -0,0 +1 @@
+utmpd-log
diff --git a/examples/s6-rc/utmpd/run b/examples/s6-rc/utmpd/run
new file mode 100644
index 0000000..417ef5e
--- /dev/null
+++ b/examples/s6-rc/utmpd/run
@@ -0,0 +1,8 @@
+#!/bin/execlineb -P
+
+fdmove -c 2 1
+s6-setuidgid utmp
+cd /run/utmps
+fdmove 1 3
+s6-ipcserver -1 -- /run/utmps/utmpd-socket
+utmps-utmpd
diff --git a/examples/s6-rc/utmpd/type b/examples/s6-rc/utmpd/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/examples/s6-rc/utmpd/type
@@ -0,0 +1 @@
+longrun
diff --git a/examples/s6-rc/wtmpd-log/consumer-for b/examples/s6-rc/wtmpd-log/consumer-for
new file mode 100644
index 0000000..18e5a30
--- /dev/null
+++ b/examples/s6-rc/wtmpd-log/consumer-for
@@ -0,0 +1 @@
+wtmpd
diff --git a/examples/s6-rc/wtmpd-log/dependencies b/examples/s6-rc/wtmpd-log/dependencies
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/s6-rc/wtmpd-log/dependencies
@@ -0,0 +1 @@
+
diff --git a/examples/s6-rc/wtmpd-log/run b/examples/s6-rc/wtmpd-log/run
new file mode 100644
index 0000000..715f41a
--- /dev/null
+++ b/examples/s6-rc/wtmpd-log/run
@@ -0,0 +1,5 @@
+#!/bin/execlineb -P
+
+s6-setuidgid utmplog
+exec -c
+s6-log t /var/log/wtmpd
diff --git a/examples/s6-rc/wtmpd-log/type b/examples/s6-rc/wtmpd-log/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/examples/s6-rc/wtmpd-log/type
@@ -0,0 +1 @@
+longrun
diff --git a/examples/s6-rc/wtmpd/dependencies b/examples/s6-rc/wtmpd/dependencies
new file mode 100644
index 0000000..6cfcf60
--- /dev/null
+++ b/examples/s6-rc/wtmpd/dependencies
@@ -0,0 +1 @@
+utmps-prepare
diff --git a/examples/s6-rc/wtmpd/notification-fd b/examples/s6-rc/wtmpd/notification-fd
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/examples/s6-rc/wtmpd/notification-fd
@@ -0,0 +1 @@
+3
diff --git a/examples/s6-rc/wtmpd/pipeline-name b/examples/s6-rc/wtmpd/pipeline-name
new file mode 100644
index 0000000..37edf1f
--- /dev/null
+++ b/examples/s6-rc/wtmpd/pipeline-name
@@ -0,0 +1 @@
+wtmpd-pipeline
diff --git a/examples/s6-rc/wtmpd/producer-for b/examples/s6-rc/wtmpd/producer-for
new file mode 100644
index 0000000..4206ab1
--- /dev/null
+++ b/examples/s6-rc/wtmpd/producer-for
@@ -0,0 +1 @@
+wtmpd-log
diff --git a/examples/s6-rc/wtmpd/run b/examples/s6-rc/wtmpd/run
new file mode 100644
index 0000000..b9d9da3
--- /dev/null
+++ b/examples/s6-rc/wtmpd/run
@@ -0,0 +1,8 @@
+#!/bin/execlineb -P
+
+fdmove -c 2 1
+s6-setuidgid utmp
+cd /run/utmps
+fdmove 1 3
+s6-ipcserver -1 -- /run/utmps/wtmpd-socket
+utmps-wtmpd
diff --git a/examples/s6-rc/wtmpd/type b/examples/s6-rc/wtmpd/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/examples/s6-rc/wtmpd/type
@@ -0,0 +1 @@
+longrun
diff --git a/examples/s6/utmpd/log/run b/examples/s6/utmpd/log/run
new file mode 100755
index 0000000..586e53d
--- /dev/null
+++ b/examples/s6/utmpd/log/run
@@ -0,0 +1,5 @@
+#!/bin/execlineb -P
+
+s6-setuidgid utmplog
+exec -c
+s6-log t /var/log/utmpd
diff --git a/examples/s6/utmpd/notification-fd b/examples/s6/utmpd/notification-fd
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/examples/s6/utmpd/notification-fd
@@ -0,0 +1 @@
+3
diff --git a/examples/s6/utmpd/run b/examples/s6/utmpd/run
new file mode 100755
index 0000000..238069b
--- /dev/null
+++ b/examples/s6/utmpd/run
@@ -0,0 +1,11 @@
+#!/bin/execlineb -P
+
+fdmove -c 2 1
+foreground { mkdir -p -m 0755 /run/utmps }
+foreground { chown utmp:utmp /run/utmps }
+unexport ?
+s6-setuidgid utmp
+cd /run/utmps
+fdmove 1 3
+s6-ipcserver -1 -- /run/utmps/utmpd-socket
+utmps-utmpd
diff --git a/examples/s6/wtmpd/log/run b/examples/s6/wtmpd/log/run
new file mode 100755
index 0000000..5dd01cb
--- /dev/null
+++ b/examples/s6/wtmpd/log/run
@@ -0,0 +1,5 @@
+#!/bin/execlineb -P
+
+s6-setuidgid utmplog
+exec -c
+s6-log t /var/log/utmps-wtmpd
diff --git a/examples/s6/wtmpd/notification-fd b/examples/s6/wtmpd/notification-fd
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/examples/s6/wtmpd/notification-fd
@@ -0,0 +1 @@
+3
diff --git a/examples/s6/wtmpd/run b/examples/s6/wtmpd/run
new file mode 100755
index 0000000..9cc49b7
--- /dev/null
+++ b/examples/s6/wtmpd/run
@@ -0,0 +1,9 @@
+#!/bin/execlineb -P
+
+fdmove -c 2 1
+unexport ?
+s6-setuidgid utmp
+cd /run/utmps
+fdmove 1 3
+s6-ipcserver -1 -- /run/utmps/wtmpd-socket
+utmps-wtmpd
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..05d5af4
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1 @@
+/package/prog/skalibs
diff --git a/package/deps.mak b/package/deps.mak
new file mode 100644
index 0000000..303fc9c
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,41 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/include/utmps/utmps.h: src/include/utmps/utmpx.h
+src/include/utmpx.h: src/include/utmps/utmpx.h
+src/utmps/utmps-internal.h: src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/endutxent.o src/utmps/endutxent.lo: src/utmps/endutxent.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/getutxent.o src/utmps/getutxent.lo: src/utmps/getutxent.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/getutxid.o src/utmps/getutxid.lo: src/utmps/getutxid.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/getutxline.o src/utmps/getutxline.lo: src/utmps/getutxline.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/logwtmp.o src/utmps/logwtmp.lo: src/utmps/logwtmp.c src/include/utmps/utmpx.h
+src/utmps/pututxline.o src/utmps/pututxline.lo: src/utmps/pututxline.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/setutxent.o src/utmps/setutxent.lo: src/utmps/setutxent.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps-utmpd.o src/utmps/utmps-utmpd.lo: src/utmps/utmps-utmpd.c src/utmps/utmps-internal.h src/include/utmps/utmpx.h
+src/utmps/utmps-wtmpd.o src/utmps/utmps-wtmpd.lo: src/utmps/utmps-wtmpd.c src/utmps/utmps-internal.h src/include/utmps/utmpx.h
+src/utmps/utmps_end.o src/utmps/utmps_end.lo: src/utmps/utmps_end.c src/include/utmps/utmps.h
+src/utmps/utmps_getent.o src/utmps/utmps_getent.lo: src/utmps/utmps_getent.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps_getid.o src/utmps/utmps_getid.lo: src/utmps/utmps_getid.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps_getline.o src/utmps/utmps_getline.lo: src/utmps/utmps_getline.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps_here.o src/utmps/utmps_here.lo: src/utmps/utmps_here.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps_here_maybe_init.o src/utmps/utmps_here_maybe_init.lo: src/utmps/utmps_here_maybe_init.c src/utmps/utmps-internal.h src/include/utmps/config.h src/include/utmps/utmps.h
+src/utmps/utmps_putline.o src/utmps/utmps_putline.lo: src/utmps/utmps_putline.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps_rewind.o src/utmps/utmps_rewind.lo: src/utmps/utmps_rewind.c src/utmps/utmps-internal.h src/include/utmps/utmps.h
+src/utmps/utmps_start.o src/utmps/utmps_start.lo: src/utmps/utmps_start.c src/include/utmps/utmps.h
+src/utmps/utmps_updwtmpx.o src/utmps/utmps_updwtmpx.lo: src/utmps/utmps_updwtmpx.c src/utmps/utmps-internal.h src/include/utmps/utmps.h src/include/utmps/utmpx.h
+src/utmps/utmps_utmpx_pack.o src/utmps/utmps_utmpx_pack.lo: src/utmps/utmps_utmpx_pack.c src/include/utmps/utmpx.h
+src/utmps/utmps_utmpx_unpack.o src/utmps/utmps_utmpx_unpack.lo: src/utmps/utmps_utmpx_unpack.c src/include/utmps/utmpx.h
+src/utmps/updwtmpx.o src/utmps/updwtmpx.lo: src/utmps/updwtmpx.c src/include/utmps/config.h src/include/utmps/utmps.h
+
+ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
+libutmps.a.xyzzy: src/utmps/endutxent.o src/utmps/getutxent.o src/utmps/getutxid.o src/utmps/getutxline.o src/utmps/logwtmp.o src/utmps/pututxline.o src/utmps/setutxent.o src/utmps/utmps_end.o src/utmps/utmps_getent.o src/utmps/utmps_getid.o src/utmps/utmps_getline.o src/utmps/utmps_here.o src/utmps/utmps_here_maybe_init.o src/utmps/utmps_putline.o src/utmps/utmps_rewind.o src/utmps/utmps_start.o src/utmps/utmps_updwtmpx.o src/utmps/utmps_utmpx_pack.o src/utmps/utmps_utmpx_unpack.o src/utmps/updwtmpx.o
+else
+libutmps.a.xyzzy: src/utmps/endutxent.lo src/utmps/getutxent.lo src/utmps/getutxid.lo src/utmps/getutxline.lo src/utmps/logwtmp.lo src/utmps/pututxline.lo src/utmps/setutxent.lo src/utmps/utmps_end.lo src/utmps/utmps_getent.lo src/utmps/utmps_getid.lo src/utmps/utmps_getline.lo src/utmps/utmps_here.lo src/utmps/utmps_here_maybe_init.lo src/utmps/utmps_putline.lo src/utmps/utmps_rewind.lo src/utmps/utmps_start.lo src/utmps/utmps_updwtmpx.lo src/utmps/utmps_utmpx_pack.lo src/utmps/utmps_utmpx_unpack.lo src/utmps/updwtmpx.lo
+endif
+libutmps.so.xyzzy: EXTRA_LIBS := -lskarnet
+libutmps.so.xyzzy: src/utmps/endutxent.lo src/utmps/getutxent.lo src/utmps/getutxid.lo src/utmps/getutxline.lo src/utmps/logwtmp.lo src/utmps/pututxline.lo src/utmps/setutxent.lo src/utmps/utmps_end.lo src/utmps/utmps_getent.lo src/utmps/utmps_getid.lo src/utmps/utmps_getline.lo src/utmps/utmps_here.lo src/utmps/utmps_here_maybe_init.lo src/utmps/utmps_putline.lo src/utmps/utmps_rewind.lo src/utmps/utmps_start.lo src/utmps/utmps_updwtmpx.lo src/utmps/utmps_utmpx_pack.lo src/utmps/utmps_utmpx_unpack.lo src/utmps/updwtmpx.lo
+utmps-utmpd: EXTRA_LIBS :=
+utmps-utmpd: src/utmps/utmps-utmpd.o libutmps.a.xyzzy -lskarnet
+utmps-wtmpd: EXTRA_LIBS :=
+utmps-wtmpd: src/utmps/utmps-wtmpd.o libutmps.a.xyzzy -lskarnet
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..ef6ce64
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=utmps
+version=0.0.1.0
+category=admin
+package_macro_name=UTMPS
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..39e777c
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,2 @@
+utmps-utmpd	0755
+utmps-wtmpd	0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..ff763be
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,7 @@
+BIN_TARGETS := \
+utmps-utmpd \
+utmps-wtmpd
+
+LIBEXEC_TARGETS :=
+
+LIB_DEFS := UTMPS=utmps
diff --git a/patch-for-solaris b/patch-for-solaris
new file mode 100755
index 0000000..2d1296b
--- /dev/null
+++ b/patch-for-solaris
@@ -0,0 +1,21 @@
+#!/usr/xpg4/bin/sh
+
+patchit () {
+  echo '#!/usr/xpg4/bin/sh' > $1.tmp
+  tail -n +2 $1 >> $1.tmp
+  mv -f $1.tmp $1
+  chmod 755 $1
+}
+
+# Solaris doesn't understand POSIX.1-2008 either.
+sed -e 's/XOPEN_SOURCE=700/XOPEN_SOURCE=600/' < configure > configure.tmp
+mv -f configure.tmp configure
+
+patchit ./configure
+patchit ./tools/install.sh
+patchit ./tools/gen-deps.sh
+
+echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp
+echo >> Makefile.tmp
+cat Makefile >> Makefile.tmp
+mv -f Makefile.tmp Makefile
diff --git a/src/include/utmps/utmps.h b/src/include/utmps/utmps.h
new file mode 100644
index 0000000..e4760f9
--- /dev/null
+++ b/src/include/utmps/utmps.h
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#ifndef UTMPS_H
+#define UTMPS_H
+
+#include <skalibs/tai.h>
+#include <utmps/utmpx.h>
+
+typedef struct utmps_s utmps, *utmps_ref ;
+struct utmps_s
+{
+  int fd ;
+} ;
+#define UTMPS_ZERO { .fd = -1 }
+
+extern int utmps_start (utmps *, char const *, tain_t const *, tain_t *) ;
+#define utmps_start_g(a, s, deadline) utmps_start(a, s, (deadline), &STAMP)
+
+extern void utmps_end (utmps *) ;
+
+extern int utmps_rewind (utmps *, tain_t const *, tain_t *) ;
+#define utmps_rewind_g (a, deadline) utmps_rewind(a, (deadline), &STAMP)
+extern int utmps_getent (utmps *, struct utmpx *, tain_t const *, tain_t *) ;
+#define utmps_getent_g (a, b, deadline) utmps_getent(a, b, (deadline), &STAMP)
+extern int utmps_getid (utmps *, unsigned short, char const *, struct utmpx *, tain_t const *, tain_t *) ;
+#define utmps_getid_g(a, type, id, b, deadline) utmps_getid(a, type, id, b, (deadline), &STAMP)
+extern int utmps_getline (utmps *, char const *, struct utmpx *, tain_t const *, tain_t *) ;
+#define utmps_getline_g(a, line, b, deadline) utmps_getline(a, line, b, (deadline), &STAMP)
+extern int utmps_putline (utmps *, struct utmpx const *, tain_t const *, tain_t *) ;
+#define utmps_putline_g(a, entry, deadline) utmps_putline(a, entry, (deadine), &STAMP)
+
+extern int utmps_updwtmpx (char const *, struct utmpx const *, tain_t const *, tain_t *) ;
+#define utmps_updwtmpx_g(file, b, deadline) utmps_updwtmpx(file, b, (deadline), &STAMP)
+
+#endif
diff --git a/src/include/utmps/utmpx.h b/src/include/utmps/utmpx.h
new file mode 100644
index 0000000..4752964
--- /dev/null
+++ b/src/include/utmps/utmpx.h
@@ -0,0 +1,75 @@
+/* ISC license. */
+
+#ifndef UTMPS_UTMPX_H
+#define UTMPS_UTMPX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#define UTMPS_UT_LINESIZE 32
+#define UTMPS_UT_NAMESIZE 32
+#define UTMPS_UT_HOSTSIZE 256
+#define UTMPS_UT_IDSIZE 4
+
+struct exit_status
+{
+  short e_termination ;
+  short e_exit ;
+} ;
+
+struct utmpx
+{
+  short ut_type ;
+  pid_t ut_pid ;
+  char ut_line[UTMPS_UT_LINESIZE] ;
+  char ut_id[UTMPS_UT_IDSIZE] ;
+  char ut_user[UTMPS_UT_NAMESIZE] ;
+
+  char ut_host[UTMPS_UT_HOSTSIZE] ;
+  struct exit_status ut_exit ;
+  pid_t ut_session ;
+
+  struct timeval ut_tv ;
+
+  uint32_t ut_addr_v6[4] ;
+  char __unused[20] ;
+} ;
+
+#define ut_name ut_user
+
+#define EMPTY 0
+#define BOOT_TIME 2
+#define OLD_TIME 4
+#define NEW_TIME 3
+#define USER_PROCESS 7
+#define INIT_PROCESS 5
+#define LOGIN_PROCESS 6
+#define DEAD_PROCESS 8
+
+#define RUN_LVL 1
+#define ACCOUNTING 9
+
+extern void endutxent (void) ;
+extern void setutxent (void) ;
+extern struct utmpx *getutxent (void) ;
+extern struct utmpx *getutxid (struct utmpx const *) ;
+extern struct utmpx *getutxline (struct utmpx const *) ;
+extern struct utmpx *pututxline (struct utmpx const *) ;
+
+extern void updwtmpx (char const *, struct utmpx const *) ;
+extern void logwtmp (char const *, char const *, char const *) ;
+
+#define UT_LINESIZE UTMPS_UT_LINESIZE
+#define UT_NAMESIZE UTMPS_UT_NAMESIZE
+#define UT_HOSTSIZE UTMPS_UT_HOSTSIZE
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/include/utmpx.h b/src/include/utmpx.h
new file mode 100644
index 0000000..fc61b8b
--- /dev/null
+++ b/src/include/utmpx.h
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+/*
+  This file is part of the utmps package.
+  See https://skarnet.org/software/utmps/
+*/
+
+#ifndef UTMPX_H
+#define UTMPX_H
+
+#include <utmps/utmpx.h>
+
+#endif
diff --git a/src/utmps/deps-exe/utmps-utmpd b/src/utmps/deps-exe/utmps-utmpd
new file mode 100644
index 0000000..60d8fd7
--- /dev/null
+++ b/src/utmps/deps-exe/utmps-utmpd
@@ -0,0 +1,2 @@
+libutmps.a.xyzzy
+-lskarnet
diff --git a/src/utmps/deps-exe/utmps-wtmpd b/src/utmps/deps-exe/utmps-wtmpd
new file mode 100644
index 0000000..60d8fd7
--- /dev/null
+++ b/src/utmps/deps-exe/utmps-wtmpd
@@ -0,0 +1,2 @@
+libutmps.a.xyzzy
+-lskarnet
diff --git a/src/utmps/deps-lib/utmps b/src/utmps/deps-lib/utmps
new file mode 100644
index 0000000..430b7fb
--- /dev/null
+++ b/src/utmps/deps-lib/utmps
@@ -0,0 +1,21 @@
+endutxent.o
+getutxent.o
+getutxid.o
+getutxline.o
+logwtmp.o
+pututxline.o
+setutxent.o
+updwtmpx.o
+utmps_end.o
+utmps_getent.o
+utmps_getid.o
+utmps_getline.o
+utmps_here.o
+utmps_here_maybe_init.o
+utmps_putline.o
+utmps_rewind.o
+utmps_start.o
+utmps_updwtmpx.o
+utmps_utmpx_pack.o
+utmps_utmpx_unpack.o
+-lskarnet
diff --git a/src/utmps/endutxent.c b/src/utmps/endutxent.c
new file mode 100644
index 0000000..bc93778
--- /dev/null
+++ b/src/utmps/endutxent.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+void endutxent (void)
+{
+  utmps_end(&utmps_here) ;
+}
diff --git a/src/utmps/getutxent.c b/src/utmps/getutxent.c
new file mode 100644
index 0000000..97a5917
--- /dev/null
+++ b/src/utmps/getutxent.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *getutxent (void)
+{
+  utmps_here_maybe_init() ;
+  if (!utmps_getent(&utmps_here, &utmps_utmpx_here, 0, 0)) return 0 ;
+  return &utmps_utmpx_here ;
+}
diff --git a/src/utmps/getutxid.c b/src/utmps/getutxid.c
new file mode 100644
index 0000000..262f35c
--- /dev/null
+++ b/src/utmps/getutxid.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *getutxid (struct utmpx const *b)
+{
+  utmps_here_maybe_init() ;
+  if (!utmps_getid(&utmps_here, (unsigned short)b->ut_type, b->ut_id, &utmps_utmpx_here, 0, 0)) return 0 ;
+  return &utmps_utmpx_here ;
+}
diff --git a/src/utmps/getutxline.c b/src/utmps/getutxline.c
new file mode 100644
index 0000000..e950816
--- /dev/null
+++ b/src/utmps/getutxline.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *getutxline (struct utmpx const *b)
+{
+  utmps_here_maybe_init() ;
+  if (!utmps_getline(&utmps_here, b->ut_line, &utmps_utmpx_here, 0, 0)) return 0 ;
+  return &utmps_utmpx_here ;
+}
diff --git a/src/utmps/logwtmp.c b/src/utmps/logwtmp.c
new file mode 100644
index 0000000..e50b68d
--- /dev/null
+++ b/src/utmps/logwtmp.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <string.h>
+#include <skalibs/tai.h>
+#include <utmps/utmpx.h>
+
+void logwtmp (char const *line, char const *name, char const *host)
+{
+  struct utmpx b ;
+  memset(&b, 0, sizeof(struct utmpx)) ;
+  strncpy(b.ut_line, line, UTMPS_UT_LINESIZE - 1) ;
+  strncpy(b.ut_user, name, UTMPS_UT_NAMESIZE - 1) ;
+  strncpy(b.ut_host, host, UTMPS_UT_HOSTSIZE - 1) ;
+  b.ut_pid = getpid() ;
+  {
+    tain_t now ;
+    tain_now(&now) ;
+    timeval_from_tain(&b.ut_tv, &now) ;
+  }
+  updwtmpx("", &b) ;
+}
diff --git a/src/utmps/pututxline.c b/src/utmps/pututxline.c
new file mode 100644
index 0000000..4e149d8
--- /dev/null
+++ b/src/utmps/pututxline.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx *pututxline (struct utmpx const *b)
+{
+  static struct utmpx here ; /* POSIX says we can't use utmps_utmpx_here */
+  utmps_here_maybe_init() ;
+  if (!utmps_putline(&utmps_here, b, 0, 0)) return 0 ;
+  here = *b ;
+  return &here ;
+}
diff --git a/src/utmps/setutxent.c b/src/utmps/setutxent.c
new file mode 100644
index 0000000..b8b8199
--- /dev/null
+++ b/src/utmps/setutxent.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+void setutxent (void)
+{
+  utmps_here_maybe_init() ;
+  utmps_rewind(&utmps_here, 0, 0) ;
+}
diff --git a/src/utmps/updwtmpx.c b/src/utmps/updwtmpx.c
new file mode 100644
index 0000000..0166448
--- /dev/null
+++ b/src/utmps/updwtmpx.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <utmps/config.h>
+#include <utmps/utmps.h>
+
+void updwtmpx (char const *file, struct utmpx const *b)
+{
+  (void)file ;
+  utmps_updwtmpx(UTMPS_WTMPD_PATH, b, 0, 0) ;
+}
diff --git a/src/utmps/utmps-internal.h b/src/utmps/utmps-internal.h
new file mode 100644
index 0000000..476e100
--- /dev/null
+++ b/src/utmps/utmps-internal.h
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#ifndef UTMPS_INTERNAL_H
+#define UTMPS_INTERNAL_H
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+
+extern struct utmpx utmps_utmpx_here ;
+extern utmps utmps_here ;
+extern void utmps_here_maybe_init (void) ;
+
+extern void utmps_utmpx_pack (char *, struct utmpx const *) ;
+extern void utmps_utmpx_unpack (char const *, struct utmpx *) ;
+
+#endif
diff --git a/src/utmps/utmps-utmpd.c b/src/utmps/utmps-utmpd.c
new file mode 100644
index 0000000..e460368
--- /dev/null
+++ b/src/utmps/utmps-utmpd.c
@@ -0,0 +1,216 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/types.h>
+#include <skalibs/env.h>
+#include <skalibs/error.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include "utmps-internal.h"
+
+static int fd = -1 ;
+
+static void get0 (char *s, size_t n)
+{
+  tain_t deadline ;
+  tain_ulong(&deadline, 2) ;
+  tain_add_g(&deadline, &deadline) ;
+  if (buffer_timed_get_g(buffer_0small, s, n, &deadline) < n)
+    strerr_diefu1sys(111, "read from stdin") ;
+}
+
+static void flush1 (void)
+{
+  tain_t deadline ;
+  tain_ulong(&deadline, 2) ;
+  tain_add_g(&deadline, &deadline) ;
+  if (!buffer_timed_flush_g(buffer_1small, &deadline))
+    strerr_diefu1sys(111, "write to stdout") ;
+}
+
+static void answer (int e)
+{
+  char c = e ;
+  buffer_putnoflush(buffer_1small, &c, 1) ;
+  flush1() ;
+}
+
+static void maybe_open (void)
+{
+  if (fd < 0)
+  {
+    fd = open("utmp", O_RDWR | O_CREAT) ;
+    if (fd < 0)
+    {
+      int e = errno ;
+      answer(e) ;
+      errno = e ;
+      strerr_diefu1sys(111, "open utmp file") ;
+    }
+  }
+}
+
+static int read_utmp_entry (char *s)
+{
+  ssize_t r ;
+  int e ;
+  if (lock_sh(fd) < 0) { e = errno ; goto err ; }
+  r = read(fd, s, sizeof(struct utmpx)) ;
+  lock_unx(fd) ;
+  if (r < 0) { e = errno ; goto err ; }
+  if (!r) return 0 ;
+  if (r < sizeof(struct utmpx)) { e = EPIPE ; goto err ; }
+  return 1 ;
+ err:
+  unlink("utmp") ;
+  answer(e) ;
+  errno = e ;
+  strerr_diefu1sys(111, "read utmp file") ;
+}
+
+static int idmatch (unsigned short type, char const *id, struct utmpx const *b)
+{
+  if (type == BOOT_TIME || type == OLD_TIME || type == NEW_TIME)
+  {
+    if (type == (unsigned short)b->ut_type) return 1 ;
+  }
+  else if (type == INIT_PROCESS || type == LOGIN_PROCESS || type == USER_PROCESS || type == DEAD_PROCESS)
+  {
+    if ((b->ut_type == INIT_PROCESS || b->ut_type == LOGIN_PROCESS || b->ut_type == USER_PROCESS || b->ut_type == DEAD_PROCESS)
+      && !strncmp(id, b->ut_id, UTMPS_UT_IDSIZE - 1)) return 1 ;
+  }
+  return 0 ;
+}
+
+static void do_getent (void)
+{
+  struct utmpx b ;
+  char buf[1 + sizeof(struct utmpx)] = "" ;
+  maybe_open() ;
+  if (!read_utmp_entry(buf+1)) { answer(ESRCH) ; return ; }
+  utmps_utmpx_unpack(buf+1, &b) ;
+  utmps_utmpx_pack(buf+1, &b) ;
+  buffer_putnoflush(buffer_1small, buf, 1 + sizeof(struct utmpx)) ;
+  flush1() ;
+}
+
+static void do_getid (void)
+{
+  unsigned short type ;
+  char rbuf[USHORT_PACK + UTMPS_UT_IDSIZE] ;
+  char sbuf[1 + sizeof(struct utmpx)] = "" ;
+  get0(rbuf, USHORT_PACK + UTMPS_UT_IDSIZE) ;
+  ushort_unpack_big(rbuf, &type) ;
+  rbuf[USHORT_PACK + UTMPS_UT_IDSIZE - 1] = 0 ;
+  maybe_open() ;
+  for (;;)
+  {
+    struct utmpx b ;
+    if (!read_utmp_entry(sbuf+1)) { answer(ESRCH) ; return ; }
+    utmps_utmpx_unpack(sbuf+1, &b) ;
+    if (idmatch(type, rbuf + USHORT_PACK, &b)) break ;
+  }
+  buffer_putnoflush(buffer_1small, sbuf, 1 + sizeof(struct utmpx)) ;
+  flush1() ;
+}
+
+static void do_getline (void)
+{
+  char rbuf[UTMPS_UT_LINESIZE] ;
+  char sbuf[1 + sizeof(struct utmpx)] = "" ;
+  get0(rbuf, UTMPS_UT_LINESIZE) ;
+  rbuf[UTMPS_UT_LINESIZE - 1] = 0 ;
+  maybe_open() ;
+  for (;;)
+  {
+    struct utmpx b ;
+    if (!read_utmp_entry(sbuf+1)) { answer(ESRCH) ; return ; }
+    utmps_utmpx_unpack(sbuf+1, &b) ;
+    if ((b.ut_type == LOGIN_PROCESS || b.ut_type == USER_PROCESS)
+      && !strncmp(rbuf, b.ut_line, UTMPS_UT_LINESIZE - 1)) break ;
+  }
+  buffer_putnoflush(buffer_1small, sbuf, 1 + sizeof(struct utmpx)) ;
+  flush1() ;
+}
+
+static void do_putline (uid_t uid)
+{
+  struct utmpx u ;
+  char buf[sizeof(struct utmpx)] ;
+  get0(buf, sizeof(struct utmpx)) ;
+  if (uid) { answer(EPERM) ; return ; }
+  utmps_utmpx_unpack(buf, &u) ;
+  maybe_open() ;
+  for (;;)
+  {
+    struct utmpx b ;
+    char tmp[sizeof(struct utmpx)] ;
+    if (!read_utmp_entry(tmp)) goto writeit ;
+    utmps_utmpx_unpack(tmp, &b) ;
+    if (idmatch(u.ut_type, u.ut_id, &b)) break ;
+  }
+  if (lseek(fd, -sizeof(struct utmpx), SEEK_CUR) < 0)
+  {
+    answer(errno) ;
+    return ;
+  }
+ writeit:
+  utmps_utmpx_pack(buf, &u) ;
+  if (lock_ex(fd) < 0) { answer(errno) ; return ; }
+  if (allwrite(fd, buf, sizeof(struct utmpx)) < sizeof(struct utmpx))
+  {
+    int e = errno ;
+    answer(e) ;
+    errno = e ;
+    strerr_diefu1sys(111, "write to utmp") ;
+  }
+  fsync(fd) ;
+  lock_unx(fd) ;
+  answer(0) ;
+}
+
+static void do_rewind (void)
+{
+  maybe_open() ;
+  if (lseek(fd, 0, SEEK_SET) < 0) { answer(errno) ; return ; }
+  answer(0) ;
+}
+
+int main (void)
+{
+  uid_t uid ;
+  char const *x = ucspi_get("REMOTEEUID") ;
+  if (!x) strerr_diefu1x(100, "get $IPCREMOTEEUID from environment") ;
+  if (!uid0_scan(x, &uid)) strerr_dieinvalid(100, "IPCREMOTEEUID") ;
+  if (ndelay_on(0) < 0) strerr_diefu1sys(111, "set stdin non-blocking") ;
+  tain_now_g() ;
+
+  for (;;)
+  {
+    tain_t deadline ;
+    char c ;
+    tain_add_g(&deadline, &tain_infinite_relative) ;
+    if (!buffer_timed_get_g(buffer_0small, &c, 1, &deadline)) break ;
+    switch (c)
+    {
+      case 'e' : do_getent() ; break ;
+      case 'i' : do_getid() ; break ;
+      case 'l' : do_getline() ; break ;
+      case 'E' : do_putline(uid) ; break ;
+      case 'r' : do_rewind() ; break ;
+      default :
+        errno = EPROTO ;
+        strerr_diefu1sys(1, "interpret stdin") ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/utmps/utmps-wtmpd.c b/src/utmps/utmps-wtmpd.c
new file mode 100644
index 0000000..b8ff5c3
--- /dev/null
+++ b/src/utmps/utmps-wtmpd.c
@@ -0,0 +1,107 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <skalibs/types.h>
+#include <skalibs/error.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include "utmps-internal.h"
+
+static void answer (int e)
+{
+  char c = e ;
+  write(1, &c, 1) ;
+}
+
+int main (void)
+{
+  struct utmpx b ;
+  char const *x ;
+  tain_t deadline ;
+  size_t w ;
+  uid_t uid ;
+  int fd ;
+  char buf[sizeof(struct utmpx)] ;
+  PROG = "utmps-wtmpd" ;
+
+  x = ucspi_get("REMOTEEUID") ;
+  if (!x) strerr_diefu1x(100, "get $IPCREMOTEEUID from environment") ;
+  if (!uid0_scan(x, &uid)) strerr_dieinvalid(100, "IPCREMOTEEUID") ;
+  if (ndelay_on(0) < 0) strerr_diefu1sys(111, "set stdin non-blocking") ;
+  tain_now_g() ;
+  tain_ulong(&deadline, 2) ;
+  tain_add_g(&deadline, &deadline) ;
+
+  w = buffer_timed_get_g(buffer_0small, buf, 1, &deadline) ;
+  if (!w) strerr_diefu1sys(111, "read from stdin") ;
+  if (buf[0] != '+') { errno = EPROTO ; strerr_diefu1sys(111, "read command") ; }
+  w = buffer_timed_get_g(buffer_0small, buf, sizeof(struct utmpx), &deadline) ; 
+  if (w < sizeof(struct utmpx)) strerr_diefu1sys(111, "read from stdin") ;
+  utmps_utmpx_unpack(buf, &b) ;
+  if (uid)
+  {
+    struct passwd *pw ;
+    errno = 0 ;
+    pw = getpwnam(b.ut_user) ;
+    if (!pw)
+    {
+      if (errno)
+      {
+        answer(errno) ;
+        strerr_diefu1sys(111, "read user database") ;
+      }
+      else
+      {
+        answer(EPERM) ;
+        strerr_diefu2x(1, "verify ut_user identity", ": no such user") ;
+      }
+    }
+    if (pw->pw_uid != uid)
+    {
+      answer(EPERM) ;
+      strerr_diefu2x(1, "verify ut_user identity", ": uid mismatch") ;
+    }
+  }
+  
+  fd = open_append("wtmp") ;
+  if (fd < 0)
+  {
+    answer(errno) ;
+    strerr_diefu1sys(111, "open wtmp") ;
+  }
+  if (lock_ex(fd) < 0)
+  {
+    answer(errno) ;
+    strerr_diefu1sys(111, "open wtmp") ;
+  }
+  if (lseek(fd, 0, SEEK_END) < 0)
+  {
+    answer(errno) ;
+    strerr_diefu1sys(111, "lseek on wtmp") ;
+  }
+  w = allwrite(fd, buf + 1, sizeof(struct utmpx)) ;
+  if (w < sizeof(struct utmpx))
+  {
+    int e = errno ;
+    if (w)
+    {
+      struct stat st ;
+      if (!fstat(fd, &st)) ftruncate(fd, st.st_size - w) ;
+    }
+    answer(e) ;
+    strerr_diefu1sys(111, "append to wtmp") ;
+  }
+  fsync(fd) ;
+  answer(0) ;
+  return 0 ;
+}
diff --git a/src/utmps/utmps_end.c b/src/utmps/utmps_end.c
new file mode 100644
index 0000000..141dfb7
--- /dev/null
+++ b/src/utmps/utmps_end.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+#include <utmps/utmps.h>
+
+void utmps_end (utmps *a)
+{
+  fd_close(a->fd) ;
+  a->fd = -1 ;
+}
diff --git a/src/utmps/utmps_getent.c b/src/utmps/utmps_getent.c
new file mode 100644
index 0000000..2b21b04
--- /dev/null
+++ b/src/utmps/utmps_getent.c
@@ -0,0 +1,21 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_getent (utmps *a, struct utmpx *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char buf[1 + sizeof(struct utmpx)] ;
+  if (!ipc_timed_send(a->fd, "e", 1, deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, buf, sizeof(buf), 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (buf[0]) return (errno = buf[0], 0) ;
+  utmps_utmpx_unpack(buf + 1, b) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_getid.c b/src/utmps/utmps_getid.c
new file mode 100644
index 0000000..622fec8
--- /dev/null
+++ b/src/utmps/utmps_getid.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <skalibs/types.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_getid (utmps *a, unsigned short type, char const *id, struct utmpx *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char sbuf[1 + USHORT_PACK + UTMPS_UT_IDSIZE] = "i" ;
+  char rbuf[1 + sizeof(struct utmpx)] ;
+  ushort_pack_big(sbuf + 1, type) ;
+  memset(sbuf + 1 + USHORT_PACK, 0, UTMPS_UT_IDSIZE) ;
+  strncpy(sbuf + 1 + USHORT_PACK, id, UTMPS_UT_IDSIZE - 1) ;
+  if (!ipc_timed_send(a->fd, sbuf, sizeof(sbuf), deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, rbuf, sizeof(rbuf), 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (rbuf[0]) return (errno = rbuf[0], 0) ;
+  utmps_utmpx_unpack(rbuf + 1, b) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_getline.c b/src/utmps/utmps_getline.c
new file mode 100644
index 0000000..4612082
--- /dev/null
+++ b/src/utmps/utmps_getline.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_getline (utmps *a, char const *line, struct utmpx *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char sbuf[1 + UTMPS_UT_LINESIZE] = "l" ;
+  char rbuf[1 + sizeof(struct utmpx)] ;
+  memset(sbuf + 1, 0, UTMPS_UT_LINESIZE) ;
+  strncpy(sbuf + 1, line, UTMPS_UT_LINESIZE - 1) ;
+  if (!ipc_timed_send(a->fd, sbuf, sizeof(sbuf), deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, rbuf, sizeof(rbuf), 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (rbuf[0]) return (errno = rbuf[0], 0) ;
+  utmps_utmpx_unpack(rbuf + 1, b) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_here.c b/src/utmps/utmps_here.c
new file mode 100644
index 0000000..2844c1e
--- /dev/null
+++ b/src/utmps/utmps_here.c
@@ -0,0 +1,8 @@
+/* ISC license. */
+
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+struct utmpx utmps_utmpx_here ;
+utmps utmps_here = UTMPS_ZERO ;
diff --git a/src/utmps/utmps_here_maybe_init.c b/src/utmps/utmps_here_maybe_init.c
new file mode 100644
index 0000000..4f3e207
--- /dev/null
+++ b/src/utmps/utmps_here_maybe_init.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <utmps/config.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+void utmps_here_maybe_init (void)
+{
+  if (utmps_here.fd < 0)
+    utmps_start(&utmps_here, UTMPS_UTMPD_PATH, 0, 0) ;
+}
diff --git a/src/utmps/utmps_putline.c b/src/utmps/utmps_putline.c
new file mode 100644
index 0000000..306fa1b
--- /dev/null
+++ b/src/utmps/utmps_putline.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_putline (utmps *a, struct utmpx const *b, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char buf[1 + sizeof(struct utmpx)] = "E" ;
+  utmps_utmpx_pack(buf + 1, b) ;
+  if (!ipc_timed_send(a->fd, buf, sizeof(buf), deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, buf, 1, 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (buf[0]) return (errno = buf[0], 0) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_rewind.c b/src/utmps/utmps_rewind.c
new file mode 100644
index 0000000..371cb0d
--- /dev/null
+++ b/src/utmps/utmps_rewind.c
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_rewind (utmps *a, tain_t const *deadline, tain_t *stamp)
+{
+  ssize_t r ;
+  char c ;
+  if (!ipc_timed_send(a->fd, "r", 1, deadline, stamp)) return 0 ;
+  r = ipc_timed_recv(a->fd, &c, 1, 0, deadline, stamp) ;
+  if (r < 0) return 0 ;
+  if (!r) return (errno = EPIPE, 0) ;
+  if (c) return (errno = c, 0) ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_start.c b/src/utmps/utmps_start.c
new file mode 100644
index 0000000..095fd2c
--- /dev/null
+++ b/src/utmps/utmps_start.c
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+#include <skalibs/webipc.h>
+#include <utmps/utmps.h>
+
+int utmps_start (utmps *a, char const *path, tain_t const *deadline, tain_t *stamp)
+{
+  int fd = ipc_stream_nbcoe() ;
+  if (fd < 0) return 0 ;
+  if (!ipc_timed_connect(fd, path, deadline, stamp))
+  {
+    fd_close(fd) ;
+    return 0 ;
+  }
+  a->fd = fd ;
+  return 1 ;
+}
diff --git a/src/utmps/utmps_updwtmpx.c b/src/utmps/utmps_updwtmpx.c
new file mode 100644
index 0000000..55811c4
--- /dev/null
+++ b/src/utmps/utmps_updwtmpx.c
@@ -0,0 +1,28 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/unix-timed.h>
+#include <utmps/utmpx.h>
+#include <utmps/utmps.h>
+#include "utmps-internal.h"
+
+int utmps_updwtmpx (char const *path, struct utmpx const *b, tain_t const *deadline, tain_t *stamp)
+{
+  utmps a = UTMPS_ZERO ;
+  ssize_t r ;
+  char buf[1 + sizeof(struct utmpx)] = "+" ;
+  if (!utmps_start(&a, path, deadline, stamp)) return 0 ;
+  utmps_utmpx_pack(buf + 1, b) ;
+  if (!ipc_timed_send(a.fd, buf, 1 + sizeof(struct utmpx), deadline, stamp)) goto err ;
+  r = ipc_timed_recv(a.fd, buf, 1, 0, deadline, stamp) ;
+  if (r < 0) goto err ;
+  if (!r) { errno = EPIPE ; goto err ; }
+  if (buf[0]) { errno = buf[0] ; goto err ; }
+  utmps_end(&a) ;
+  return 1 ;
+
+ err :
+  utmps_end(&a) ;
+  return 0 ;
+}
diff --git a/src/utmps/utmps_utmpx_pack.c b/src/utmps/utmps_utmpx_pack.c
new file mode 100644
index 0000000..c5efecb
--- /dev/null
+++ b/src/utmps/utmps_utmpx_pack.c
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#include <string.h>
+#include <utmps/utmpx.h>
+
+void utmps_utmpx_pack (char *s, struct utmpx const *u)
+{
+  memcpy(s, u, sizeof(struct utmpx)) ;
+}
diff --git a/src/utmps/utmps_utmpx_unpack.c b/src/utmps/utmps_utmpx_unpack.c
new file mode 100644
index 0000000..a774356
--- /dev/null
+++ b/src/utmps/utmps_utmpx_unpack.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <string.h>
+#include <utmps/utmpx.h>
+
+void utmps_utmpx_unpack (char const *s, struct utmpx *b)
+{
+  memcpy(b, s, sizeof(struct utmpx)) ;
+  b->ut_user[UTMPS_UT_NAMESIZE - 1] = 0 ;
+  b->ut_id[UTMPS_UT_IDSIZE - 1] = 0 ;
+  b->ut_line[UTMPS_UT_LINESIZE - 1] = 0 ;
+  b->ut_host[UTMPS_UT_HOSTSIZE - 1] = 0 ;
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..6383ac2
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,93 @@
+#!/bin/sh -e
+
+. package/info
+
+echo '#'
+echo '# This file has been generated by tools/gen-deps.sh'
+echo '#'
+echo
+
+for dir in src/include/${package} src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.h$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      if test -n "$deps" ; then
+        echo "${dir}/${file}:${deps}"
+      fi
+    }
+  done
+done
+
+for dir in src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.c$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=" ${dir}/$file"
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      o=$(echo $file | sed s/\\.c$/.o/)
+      lo=$(echo $file | sed s/\\.c$/.lo/)
+      echo "${dir}/${o} ${dir}/${lo}:${deps}"
+    }
+  done
+done
+echo
+
+for dir in $(ls -1 src | grep -v ^include) ; do
+  for file in $(ls -1 src/$dir/deps-lib) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -e ^-l -e '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps src/$dir/$dep"
+      fi
+    done < src/$dir/deps-lib/$file
+    echo 'ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)'
+    echo "lib${file}.a.xyzzy:$deps"
+    echo else
+    echo "lib${file}.a.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')"
+    echo endif
+    echo "lib${file}.so.xyzzy: EXTRA_LIBS :=$libs"
+    echo "lib${file}.so.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')"
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      if echo $dep | grep -q -- '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps $dep"
+      fi
+    done < src/$dir/deps-exe/$file
+    echo "$file: EXTRA_LIBS :=$libs"
+    echo "$file: src/$dir/$file.o$deps"
+  done
+done
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..89f9428
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+usage() {
+  echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2
+  exit 1
+}
+
+mkdirp=false
+symlink=false
+mode=0755
+
+while getopts Dlm: name ; do
+  case "$name" in
+    D) mkdirp=true ;;
+    l) symlink=true ;;
+    m) mode=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+test "$#" -eq 2 || usage
+src=$1
+dst=$2
+tmp="$dst.tmp.$$"
+
+case "$dst" in
+  */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;;
+esac
+
+set -C
+set -e
+
+if $mkdirp ; then
+  umask 022
+  case "$2" in
+    */*) mkdir -p "${dst%/*}" ;;
+  esac
+fi
+
+trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
+
+umask 077
+
+if $symlink ; then
+  ln -s "$src" "$tmp"
+else
+  cat < "$1" > "$tmp"
+  chmod "$mode" "$tmp"
+fi
+
+mv -f "$tmp" "$dst"
+if test -d "$dst" ; then
+  rm -f "$dst/$(basename $tmp)"
+  if $symlink ; then
+    mkdir "$tmp"
+    ln -s "$src" "$tmp/$(basename $dst)"
+    mv -f "$tmp/$(basename $dst)" "${dst%/*}"
+    rmdir "$tmp"
+  else
+    echo "$0: $dst is a directory" 1>&2
+    exit 1
+  fi
+fi