summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--debian/changelog6
-rwxr-xr-xdebian/rules28
-rw-r--r--doc/index.html2
-rw-r--r--doc/install.html10
-rw-r--r--doc/replaceinit.html4
-rw-r--r--doc/svlogd.8.html175
-rw-r--r--man/runsv.86
-rw-r--r--man/runsvstat.82
-rw-r--r--man/svlogd.8295
-rw-r--r--man/utmpset.82
-rw-r--r--package/CHANGES7
-rwxr-xr-xpackage/upgrade4
-rw-r--r--src/Makefile17
-rw-r--r--src/TARGETS4
-rw-r--r--src/fmt_ptime.c30
-rw-r--r--src/fmt_ptime.h13
-rw-r--r--src/pmatch.c40
-rw-r--r--src/pmatch.h6
-rw-r--r--src/svlogd.c654
20 files changed, 1278 insertions, 31 deletions
diff --git a/Makefile b/Makefile
index d30086c..1c30511 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
 DESTDIR=
 
-PACKAGE=runit-0.8.1
+PACKAGE=runit-0.8.4
 DIRS=doc man etc package src
 MANPAGES=runit.8 runit-init.8 runsvdir.8 runsv.8 svwaitdown.8 svwaitup.8 \
-utmpset.8 runsvchdir.8 runsvstat.8 runsvctrl.8
+utmpset.8 runsvchdir.8 runsvstat.8 runsvctrl.8 svlogd.8
 
 all: clean .manpages $(PACKAGE).tar.gz
 
diff --git a/debian/changelog b/debian/changelog
index bbfdd7d..e06e604 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+runit (0.8.4-1) unstable; urgency=low
+
+  * new upstream version.
+
+ -- Gerrit Pape <pape@smarden.org>  Sun, 20 Apr 2003 19:34:37 +0200
+
 runit (0.8.1-1) unstable; urgency=low
 
   * new upstream version.
diff --git a/debian/rules b/debian/rules
index 98d72b6..f1d59a9 100755
--- a/debian/rules
+++ b/debian/rules
@@ -26,11 +26,11 @@ build-arch-stamp:
 	# Add here command to compile/build the package.
 	#	$(MAKE)
 
-	tar xfzvp runit-0.8.1.tar.gz
+	tar xfzvp runit-0.8.4.tar.gz
 	( \
 	set -e; \
-	cd admin/runit-0.8.1/src; \
-	ln -s runit-0.8.1 runit; mv runit ../..; \
+	cd admin/runit-0.8.4/src; \
+	ln -s runit-0.8.4 runit; mv runit ../..; \
 	echo 'diet -v -Os gcc -O2 -Wall' >conf-cc; \
 	echo 'diet -v -Os gcc -s -Os -pipe' >conf-ld; \
 	$(MAKE); \
@@ -74,26 +74,26 @@ install: build
 	#	$(MAKE) install DESTDIR=$(CURDIR)/debian/runit
 
 	# runit
-	install -m0500 admin/runit-0.8.1/src/runit \
+	install -m0500 admin/runit-0.8.4/src/runit \
 	  $(CURDIR)/debian/runit/sbin/runit
-	install -m0500 admin/runit-0.8.1/src/runit-init \
+	install -m0500 admin/runit-0.8.4/src/runit-init \
 	  $(CURDIR)/debian/runit/sbin/runit-init
-	install -m0755 admin/runit-0.8.1/src/runsvdir \
+	install -m0755 admin/runit-0.8.4/src/runsvdir \
 	  $(CURDIR)/debian/runit/usr/bin/runsvdir
-	install -m0755 admin/runit-0.8.1/src/runsv \
+	install -m0755 admin/runit-0.8.4/src/runsv \
 	  $(CURDIR)/debian/runit/usr/bin/runsv
-	install -m0755 admin/runit-0.8.1/src/runsvchdir \
+	install -m0755 admin/runit-0.8.4/src/runsvchdir \
 	  $(CURDIR)/debian/runit/usr/sbin/runsvchdir
-	install -m0755 admin/runit-0.8.1/src/svwaitdown \
+	install -m0755 admin/runit-0.8.4/src/svwaitdown \
 	  $(CURDIR)/debian/runit/usr/bin/svwaitdown
-	install -m0755 admin/runit-0.8.1/src/svwaitup \
+	install -m0755 admin/runit-0.8.4/src/svwaitup \
 	  $(CURDIR)/debian/runit/usr/bin/svwaitup
-	install -m0755 admin/runit-0.8.1/src/utmpset \
+	install -m0755 admin/runit-0.8.4/src/utmpset \
 	  $(CURDIR)/debian/runit/usr/sbin/utmpset
 
-	install -m0755 admin/runit-0.8.1/src/runsvstat \
+	install -m0755 admin/runit-0.8.4/src/runsvstat \
 	  $(CURDIR)/debian/runit/usr/bin/runsvstat
-	install -m0755 admin/runit-0.8.1/src/runsvctrl \
+	install -m0755 admin/runit-0.8.4/src/runsvctrl \
 	  $(CURDIR)/debian/runit/usr/bin/runsvctrl
 
 #	install -m0700 debian/1 \
@@ -146,7 +146,7 @@ binary-arch: build install
 #	dh_installexamples -a
 	dh_installman -a admin/runit/man/*.8
 #	dh_undocumented -a
-	dh_installchangelogs -a admin/runit-0.8.1/package/CHANGES
+	dh_installchangelogs -a admin/runit-0.8.4/package/CHANGES
 	dh_strip -a
 #	dh_link -a
 	dh_compress -a
diff --git a/doc/index.html b/doc/index.html
index ac42af5..5a0e7b6 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -27,6 +27,8 @@ Dependencies</a><br>
 <a href="runsv.8.html">The <tt>runsv</tt> program</a><br>
 <a href="runsvchdir.8.html">The <tt>runsvchdir</tt> program</a><br>
 <br>
+<a href="svlogd.8.html">The <tt>svlogd</tt> program</a><br>
+<br>
 <a href="utmpset.8.html">The <tt>utmpset</tt> program</a><br>
 <a href="svwaitdown.8.html">The <tt>svwaitdown</tt> program</a><br>
 <a href="svwaitup.8.html">The <tt>svwaitup</tt> program</a><br>
diff --git a/doc/install.html b/doc/install.html
index d812248..6a13b34 100644
--- a/doc/install.html
+++ b/doc/install.html
@@ -13,14 +13,14 @@ Check that you have the recent version of
 <a href="http://cr.yp.to/daemontools.html">daemontools</a> installed.
 <p>
 Download
-<a href="runit-0.8.1.tar.gz">runit-0.8.1.tar.gz</a> into <tt>/package</tt>
+<a href="runit-0.8.4.tar.gz">runit-0.8.4.tar.gz</a> into <tt>/package</tt>
 and unpack the archive
 <pre>
   # cd /package
-  # gunzip runit-0.8.1.tar
-  # tar -xpf runit-0.8.1.tar
-  # rm runit-0.8.1.tar
-  # cd admin/runit-0.8.1
+  # gunzip runit-0.8.4.tar
+  # tar -xpf runit-0.8.4.tar
+  # rm runit-0.8.4.tar
+  # cd admin/runit-0.8.4
 </pre>
 Compile and install the <i>runit</i> programs
 <pre>
diff --git a/doc/replaceinit.html b/doc/replaceinit.html
index 2e004f3..4e9cef2 100644
--- a/doc/replaceinit.html
+++ b/doc/replaceinit.html
@@ -107,7 +107,7 @@ default Unix process no 1 <i>runit</i>.
 </pre>
 To report success:
 <pre>
-  # ( uname -a ; cat /etc/runit/[123] ) | mail pape-runit-0.8.1@smarden.org
+  # ( uname -a ; cat /etc/runit/[123] ) | mail pape-runit-0.8.4@smarden.org
 </pre>
 <hr>
 
@@ -188,7 +188,7 @@ Use <b>init 6</b> to reboot and <b>init 0</b> to halt a system that runs
 <p>
 To report success:
 <pre>
-  # ( uname -a ; cat /etc/runit/[123] ) | mail pape-runit-0.8.1@smarden.org
+  # ( uname -a ; cat /etc/runit/[123] ) | mail pape-runit-0.8.4@smarden.org
 </pre>
 <h3>Step 5: Service migration</h3>
 The goal is to migrate all services from <i>/etc/rc.*</i> scheme to the
diff --git a/doc/svlogd.8.html b/doc/svlogd.8.html
new file mode 100644
index 0000000..8026b7d
--- /dev/null
+++ b/doc/svlogd.8.html
@@ -0,0 +1,175 @@
+
+
+
+<HTML>
+<HEAD>
+<TITLE>svlogd(8) manual page</TITLE>
+</HEAD>
+<BODY bgcolor=white>
+<a href="http://smarden.org/pape/">G. Pape</a><br><A HREF="index.html">runit</A><hr><P>
+
+<H2><A NAME="sect0">Name</A></H2>
+svlogd - runit's service logging daemon 
+<H2><A NAME="sect1">Synopsis</A></H2>
+<B>svlogd</B> [-tv] [-r <I>c]</I> [-R
+<I>abc]</I> [-l <I>n]</I> [-b <I>n]</I> <I>logs</I> 
+<H2><A NAME="sect2">Description</A></H2>
+<I>logs</I> consists of one or more arguments,
+each specifying a directory. <P>
+<B>svlogd</B> continuously reads log data from its
+standard input, optionally filters log messages, and writes the data to
+one or more automatically rotated <I>logs</I>. <P>
+Recent log files can automatically
+be processed by an arbitrary processor program when they are rotated, and
+<B>svlogd</B> can be told to alert selected log messages to standard error. <P>
+<B>svlogd</B>
+runs until it sees end-of-file on standard input or is sent a TERM signal,
+see below. <P>
+ 
+<H3><A NAME="sect3">Log Directory</A></H3>
+A log directory <I>log</I> contains some number of old
+log files, and the current log file <I>current</I>. Old log files have a file name
+starting with <I>@</I> followed by a precise timestamp (see <B><I>tai64n</B>(8)</I>), indicating
+when <I>current</I> was rotated and renamed to this file. <P>
+A log directory additionally
+contains the lock file <I>lock</I>, maybe <I>state</I> and <I>newstate</I>, and optionally the
+file <I>config</I>. <B>svlogd</B> creates necessary files if they don't exist. 
+<H3><A NAME="sect4">Log File
+Rotation</A></H3>
+<B>svlogd</B> appends selected log messages to the <I>current</I> log file. If
+<I>current</I> has <I>size</I> bytes or more size (or there is a new-line withing the
+last <I>len</I> of <I>size</I> bytes) <I>current</I> is rotated: <P>
+<B>svlogd</B> closes <I>current</I>, changes
+permission of <I>current</I> to 0755, renames <I>current</I> to @<I>timestamp.s,</I> and starts
+with a new empty <I>current</I>. If <B>svlogd</B> sees <I>num</I> or more old log files in <I>dir</I>,
+it removes the oldest. 
+<H3><A NAME="sect5">Processor</A></H3>
+If <B>svlogd</B> is told to process recent log
+files, it saves <I>current</I> to @<I>timestamp.u,</I> feeds @<I>timestamp.u</I> through ``sh -c
+"<I>processor</I>"'' and writes the output to @<I>timestamp.t.</I> If the <I>processor</I> finishes
+successfully, @<I>timestamp.u</I> is deleted and @<I>timestamp.t</I> is renamed to @<I>timestamp.s,</I>
+otherwise @<I>timestamp.t</I> is deleted and the <I>processor</I> is started again. <B>svlogd</B>
+also saves any output that the <I>processor</I> writes to filedescriptor 5, and
+make that output available on filedescriptor 4 when it runs <I>processor</I> on
+the next log file rotation. <P>
+A <I>processor</I> is run in the background. If <B>svlogd</B>
+sees a previously started <I>processor</I> still running when trying to start
+a new one for the same <I>log</I>, it blocks until the currently running <I>processor</I>
+has finished successfully. Only the HUP signal works in that situation. Note
+that this may block any program feeding its log data to <B>svlogd.</B> <P>
+ 
+<H3><A NAME="sect6">Config</A></H3>
+On
+startup, and after receiving a HUP signal, <B>svlogd</B> checks for each <I>log</I> if
+the configuration file <I>log/config</I> exists, and if so, reads the file line
+by line and adjusts configuration for <I>log</I> as follows: <P>
+If the line is empty,
+less than two characters long, or starts with a ``#'', it is ignored. A line
+of the form 
+<DL>
+
+<DT>s<I>size</I> </DT>
+<DD>sets the maximum file size of <I>current</I> when <B>svlogd</B> should
+rotate the current log file to <I>size</I> bytes. Default is 1000000. </DD>
+
+<DT>n<I>num</I> </DT>
+<DD>sets
+the maximum number of old log files <B>svlogd</B> should maintain to <I>num</I>. If <B>svlogd</B>
+sees more that <I>num</I> old log files in <I>log</I> after log file rotation, it deletes
+the oldest one. Default is 10. </DD>
+
+<DT>!<I>processor</I> </DT>
+<DD>tells <B>svlogd</B> to feed each recent
+log file through <I>processor</I> (see above) on log file rotation. By default
+log files are not processed. </DD>
+</DL>
+<P>
+If a line starts with a <I>-</I>, <I>+</I>, <I>e</I>, or <I>E</I>, <B>svlogd</B>
+matches the first <I>len</I> characters of each log message against <I>pattern</I> and
+acts accordingly: 
+<DL>
+
+<DT>-<I>pattern</I> </DT>
+<DD>the log message is deselected. </DD>
+
+<DT>+<I>pattern</I> </DT>
+<DD>the log
+message is selected </DD>
+
+<DT>e<I>pattern</I> </DT>
+<DD>log messages matching <I>pattern</I> are printed
+to standard error. </DD>
+
+<DT>E<I>pattern</I> </DT>
+<DD>log messages not matching <I>pattern</I> are printed
+to standard error. </DD>
+</DL>
+<P>
+Initially each line is selected. Deselected log messages
+are discarded from <I>log</I>. 
+<H2><A NAME="sect7">Options</A></H2>
+
+<DL>
+
+<DT><B>-t</B> </DT>
+<DD>timestamp. Prefix each selected line with
+a precise timestamp (see <B><I>tai64n</B>(8)</I>) when writing to <I>log</I> or to standard
+error. </DD>
+
+<DT><B>-r <I>c</B> </I></DT>
+<DD>replace. <I>c</I> must be a single character. Replace non-printable characters
+in log messages with <I>c</I>. Character are replaced before pattern matching is
+applied. </DD>
+
+<DT><B>-R <I>abc</B> </I></DT>
+<DD>replace characters. Additionally to non-printable characters,
+replace all characters found in <I>abc</I> with <I>c</I> (default ``_''). </DD>
+
+<DT><B>-l <I>len</B> </I></DT>
+<DD>line length.
+Pattern matching applies to the first <I>len</I> characters of a log message only.
+Default is 1000. </DD>
+
+<DT><B>-b <I>buflen</B> </I></DT>
+<DD>buffer size. Set the size of the buffer <B>svlogd</B>
+uses when reading from standard input and writing to <I>logs</I> to <I>buflen</I>. Default
+is 1024. <I>buflen</I> must be greater than <I>len</I>. </DD>
+</DL>
+
+<H2><A NAME="sect8">Signals</A></H2>
+If <B>svlogd</B> is sent a HUP
+signal, it closes and reopens all <I>logs</I>, and updates their configuration
+according to <I>log/config</I>. <P>
+If <B>svlogd</B> is sent a TERM signal, or if it sees
+end-of-file on standard input, it closes standard input, waits for all <I>processor</I>
+subprocesses to finish if any, and exits 0 as soon as possible. <P>
+If <B>svlogd</B>
+is sent an ALRM signal, it tries for all <I>logs</I> to rotate the current log
+file, if it is not empty. 
+<H2><A NAME="sect9">See Also</A></H2>
+<I>runsvstat(8)</I>, <I>runit(8)</I>, <I>runit-init(8)</I>,
+<I>runsv(8)</I>, <I>runsvstat(8)</I>, <I>runsvctrl(8)</I>, <I>runsvdir(8)</I>, <I>runsvchdir(8)</I>, <I>svwaitdown(8)</I>,
+<I>svwaitup(8)</I> <P>
+<I>http://smarden.org/runit/</I> 
+<H2><A NAME="sect10">Author</A></H2>
+Gerrit Pape &lt;pape@smarden.org&gt;
+<P>
+
+<HR><P>
+<A NAME="toc"><B>Table of Contents</B></A><P>
+<UL>
+<LI><A NAME="toc0" HREF="#sect0">Name</A></LI>
+<LI><A NAME="toc1" HREF="#sect1">Synopsis</A></LI>
+<LI><A NAME="toc2" HREF="#sect2">Description</A></LI>
+<UL>
+<LI><A NAME="toc3" HREF="#sect3">Log Directory</A></LI>
+<LI><A NAME="toc4" HREF="#sect4">Log File Rotation</A></LI>
+<LI><A NAME="toc5" HREF="#sect5">Processor</A></LI>
+<LI><A NAME="toc6" HREF="#sect6">Config</A></LI>
+</UL>
+<LI><A NAME="toc7" HREF="#sect7">Options</A></LI>
+<LI><A NAME="toc8" HREF="#sect8">Signals</A></LI>
+<LI><A NAME="toc9" HREF="#sect9">See Also</A></LI>
+<LI><A NAME="toc10" HREF="#sect10">Author</A></LI>
+</UL>
+</BODY></HTML>
diff --git a/man/runsv.8 b/man/runsv.8
index ef68788..67acb93 100644
--- a/man/runsv.8
+++ b/man/runsv.8
@@ -125,13 +125,13 @@ This command is ignored if it is given to
 .IR service /log/supervise/control.
 .P
 For example, to send a TERM signal to /service/socklog-unix, either do
-  # svc -t /service/socklog-unix
+  # svc \-t /service/socklog-unix
  or
-  # echo -n t >/service/socklog-unix/supervise/control
+  # echo \-n t >/service/socklog-unix/supervise/control
 .P
 If
 .BR echo (1)
-on your systems does not provide the -n option, leave it out,
+on your systems does not provide the \-n option, leave it out,
 .B runsv
 ignores unknown characters written to the control pipe.
 .BR echo (1)
diff --git a/man/runsvstat.8 b/man/runsvstat.8
index 8491826..cd995ef 100644
--- a/man/runsvstat.8
+++ b/man/runsvstat.8
@@ -27,7 +27,7 @@ of the corresponding
 .IR service .
 .SH OPTIONS
 .TP
-.B -l
+.B \-l
 log service. Check for each
 .I service
 if there exists a corresponding
diff --git a/man/svlogd.8 b/man/svlogd.8
new file mode 100644
index 0000000..ffaf090
--- /dev/null
+++ b/man/svlogd.8
@@ -0,0 +1,295 @@
+.TH svlogd 8
+.SH NAME
+svlogd \- runit's service logging daemon
+.SH SYNOPSIS
+.B svlogd
+[\-tv] [\-r
+.I c\fR] [\-R
+.I abc\fR] [\-l
+.I n\fR] [\-b
+.I n\fR]
+.I logs
+.SH DESCRIPTION
+.I logs
+consists of one or more arguments, each specifying a directory.
+.P
+.B svlogd
+continuously reads log data from its standard input, optionally filters log
+messages, and writes the data to one or more automatically rotated
+.IR logs .
+.P
+Recent log files can automatically be processed by an arbitrary processor
+program when they are rotated, and
+.B svlogd
+can be told to alert selected log messages to standard error.
+.P
+.B svlogd
+runs until it sees end-of-file on standard input or is sent a TERM signal,
+see below.
+
+.SS LOG DIRECTORY
+A log directory
+.I log
+contains some number of old log files, and the current log file
+.IR current .
+Old log files have a file name starting with
+.I @
+followed by a precise timestamp (see
+.BR tai64n (8)),
+indicating when
+.I current
+was rotated and renamed to this file.
+.P
+A log directory additionally contains the lock file
+.IR lock ,
+maybe
+.I state
+and
+.IR newstate ,
+and optionally the file
+.IR config .
+.B svlogd
+creates necessary files if they don't exist.
+.SS LOG FILE ROTATION
+.B svlogd
+appends selected log messages to the
+.I current
+log file.
+If
+.I current
+has
+.I size
+bytes or more size (or there is a new-line withing the last
+.I len
+of
+.I size
+bytes)
+.I current
+is rotated:
+.P
+.B svlogd
+closes
+.IR current ,
+changes permission of
+.I current
+to 0755, renames
+.I current
+to
+.RI @ timestamp\fR.s,
+and starts with a new empty
+.IR current .
+If
+.B svlogd
+sees
+.I num
+or more old log files in
+.IR dir ,
+it removes the oldest.
+.SS PROCESSOR
+If
+.B svlogd
+is told to process recent log files, it saves
+.I current
+to
+.RI @ timestamp\fR.u,
+feeds
+.RI @ timestamp\fR.u
+through ``sh \-c "\fIprocessor\fR"''
+and writes the output to
+.RI @ timestamp\fR.t.
+If the
+.I processor
+finishes successfully,
+.RI @ timestamp\fR.u
+is deleted and
+.RI @ timestamp\fR.t
+is renamed to
+.RI @ timestamp\fR.s,
+otherwise
+.RI @ timestamp\fR.t
+is deleted and the
+.I processor
+is started again.
+.B svlogd
+also saves any output that the
+.I processor
+writes to filedescriptor 5, and make that output available on
+filedescriptor 4 when it runs
+.I processor
+on the next log file rotation.
+.P
+A
+.I processor
+is run in the background.
+If
+.B svlogd
+sees a previously started
+.I processor
+still running when trying to start a new one for the same
+.IR log ,
+it blocks until the currently running
+.I processor
+has finished successfully.
+Only the HUP signal works in that situation.
+Note that this may block any program feeding its log data to
+.BR svlogd.
+
+.SS CONFIG
+On startup, and after receiving a HUP signal,
+.B svlogd
+checks for each
+.I log
+if the configuration file
+.I log/config
+exists, and if so, reads the file line by line and adjusts configuration for
+.I log
+as follows:
+.P
+If the line is empty, less than two characters long, or starts with a ``#'',
+it is ignored.
+A line of the form
+.TP
+.RI s size
+sets the maximum file size of
+.I current
+when
+.B svlogd
+should rotate the current log file to
+.I size
+bytes.
+Default is 1000000.
+.TP
+.RI n num
+sets the maximum number of old log files
+.B svlogd
+should maintain to
+.IR num .
+If
+.B svlogd
+sees more that
+.I num
+old log files in
+.I log
+after log file rotation, it deletes the oldest one.
+Default is 10.
+.TP
+.RI ! processor
+tells
+.B svlogd
+to feed each recent log file through
+.I processor
+(see above) on log file rotation.
+By default log files are not processed.
+.P
+If a line starts with a
+.IR \- ,
+.IR + ,
+.IR e ,
+or
+.IR E ,
+.B svlogd
+matches the first
+.I len
+characters of each log message against
+.I pattern
+and acts accordingly:
+.TP
+.RI \- pattern
+the log message is deselected.
+.TP
+.RI + pattern
+the log message is selected
+.TP
+.RI e pattern
+log messages matching
+.I pattern
+are printed to standard error.
+.TP
+.RI E pattern
+log messages not matching
+.I pattern
+are printed to standard error.
+.P
+Initially each line is selected.
+Deselected log messages are discarded from
+.IR log .
+.SH OPTIONS
+.TP
+.B \-t
+timestamp.
+Prefix each selected line with a precise timestamp (see
+.BR tai64n (8))
+when writing to
+.I log
+or to standard error.
+.TP
+.B \-r \fIc
+replace.
+.I c
+must be a single character.
+Replace non-printable characters in log messages with
+.IR c .
+Character are replaced before pattern matching is applied.
+.TP
+.B \-R \fIabc
+replace characters.
+Additionally to non-printable characters, replace all characters found in
+.I abc
+with
+.I c
+(default ``_'').
+.TP
+.B \-l \fIlen
+line length.
+Pattern matching applies to the first
+.I len
+characters of a log message only.
+Default is 1000.
+.TP
+.B \-b \fIbuflen
+buffer size.
+Set the size of the buffer
+.B svlogd
+uses when reading from standard input and writing to
+.I logs
+to
+.IR buflen .
+Default is 1024.
+.I buflen
+must be greater than
+.IR len .
+.SH SIGNALS
+If
+.B svlogd
+is sent a HUP signal, it closes and reopens all
+.IR logs ,
+and updates their configuration according to
+.IR log/config .
+.P
+If
+.B svlogd
+is sent a TERM signal, or if it sees end-of-file on standard input, it
+closes standard input, waits for all
+.I processor
+subprocesses to finish if any, and exits 0 as soon as possible.
+.P
+If
+.B svlogd
+is sent an ALRM signal, it tries for all
+.I logs
+to rotate the current log file, if it is not empty.
+.SH SEE ALSO
+runsvstat(8),
+runit(8),
+runit-init(8),
+runsv(8),
+runsvstat(8),
+runsvctrl(8),
+runsvdir(8),
+runsvchdir(8),
+svwaitdown(8),
+svwaitup(8)
+.P
+http://smarden.org/runit/
+.SH AUTHOR
+Gerrit Pape <pape@smarden.org>
diff --git a/man/utmpset.8 b/man/utmpset.8
index 4cd424e..f6a9d6e 100644
--- a/man/utmpset.8
+++ b/man/utmpset.8
@@ -33,7 +33,7 @@ to the
 run scripts, e.g.:
 .P
  #!/bin/sh
- /command/utmpset -w tty5
+ /command/utmpset \-w tty5
  exec /sbin/getty 38400 tty5 linux
 .SH OPTIONS
 .TP
diff --git a/package/CHANGES b/package/CHANGES
index 6bc17de..c4b52fa 100644
--- a/package/CHANGES
+++ b/package/CHANGES
@@ -1,3 +1,10 @@
+runit 0.8.4
+Sun, 20 Apr 2003 19:31:24 +0200
+  * svlogd.c: new; runit's service logging daemon.
+  * fmt_ptime.h, fmt_ptime.c, pmatch.h, pmatch.c: new.
+  * man/svlogd.8, doc/svlogd.8.html: new.
+  * man/runsv.8, man/runsvstat.8, man/utmpset.8: minor cleanup.
+
 runit 0.8.1
 Wed, 12 Mar 2003 15:10:04 +0100
   * runsvdir.c, runsv.c: close-on-exec file descriptors of current dir and
diff --git a/package/upgrade b/package/upgrade
index 1d9f5cb..5459a7b 100755
--- a/package/upgrade
+++ b/package/upgrade
@@ -7,9 +7,9 @@ test -d src || ( echo 'Wrong working directory.'; exit 1 )
 here=`env - PATH=$PATH pwd`
 parent=`dirname $here`
 
-echo 'Creating symlink runit -> runit-0.8.1...'
+echo 'Creating symlink runit -> runit-0.8.4...'
 rm -f runit
-ln -s runit-0.8.1 runit
+ln -s runit-0.8.4 runit
 mv -f runit ..
 
 echo 'Making command links in /command...'
diff --git a/src/Makefile b/src/Makefile
index 02c2a58..a9fd9e5 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,5 +1,5 @@
 IT=sysdeps runit runit-init runsv runsvdir runsvchdir svwaitup svwaitdown \
-utmpset
+utmpset svlogd
 
 default: $(IT)
 
@@ -33,6 +33,9 @@ utmpset: load utmpset.o unix.a byte.a
 runsvchdir: load runsvchdir.o unix.a byte.a
 	./load runsvchdir unix.a byte.a
 
+svlogd: load svlogd.o pmatch.o fmt_ptime.o unix.a byte.a time.a
+	./load svlogd pmatch.o fmt_ptime.o unix.a byte.a time.a
+
 runit.o: compile sysdeps runit.c
 	./compile runit.c
 
@@ -63,6 +66,17 @@ utmpset.o: compile utmpset.c
 runsvchdir.o: compile runsvchdir.c
 	./compile runsvchdir.c
 
+svlogd.o: compile svlogd.c
+	./compile svlogd.c
+
+
+pmatch.o: compile pmatch.c
+	./compile pmatch.c
+
+fmt_ptime.o: compile fmt_ptime.c
+	./compile fmt_ptime.c
+
+
 clean:
 	find . -name \*~ -exec rm -f {} \;
 	find . -name .??*~ -exec rm -f {} \;
@@ -337,6 +351,7 @@ uint64.h
 	grep sysdep hasmkffo.h >> sysdeps
 	grep sysdep hasflock.h >> sysdeps
 	grep sysdep hasshsgr.h >> sysdeps
+	cat sysdeps
 
 systype: find-systype.sh trycpp.c x86cpuid.c
 	sh find-systype.sh > systype
diff --git a/src/TARGETS b/src/TARGETS
index 493dd4b..f20b523 100644
--- a/src/TARGETS
+++ b/src/TARGETS
@@ -18,6 +18,10 @@ utmpset
 utmpset.o
 runsvchdir
 runsvchdir.o
+svlogd
+svlogd.o
+pmatch.o
+fmt_ptime.o
 alloc.o
 alloc_re.o
 buffer.o
diff --git a/src/fmt_ptime.c b/src/fmt_ptime.c
new file mode 100644
index 0000000..86fdcc2
--- /dev/null
+++ b/src/fmt_ptime.c
@@ -0,0 +1,30 @@
+#include "fmt_ptime.h"
+#include "fmt.h"
+
+unsigned int fmt_ptime(char *s, struct taia *ta) {
+  struct tm *t;
+
+  if (! (t =localtime((time_t*)&ta->sec.x))) return(0);
+  fmt_ulong(s, 1900 +t->tm_year);
+  s[4] ='-'; fmt_uint0(&s[5], t->tm_mon +1, 2);
+  s[7] ='-'; fmt_uint0(&s[8], t->tm_mday, 2);
+  s[10] ='_'; fmt_uint0(&s[11], t->tm_hour, 2);
+  s[13] =':'; fmt_uint0(&s[14], t->tm_min, 2);
+  s[16] =':'; fmt_uint0(&s[17], t->tm_sec, 2);
+  s[19] ='.'; fmt_uint0(&s[20], ta->nano, 9);
+  return(25);
+}
+
+unsigned int fmt_taia(char *s, struct taia *t) {
+  static char hex[16] ="0123456789abcdef";
+  static char pack[TAIA_PACK];
+  int i;
+
+  taia_pack(pack, t);
+  s[0] ='@';
+  for (i =0; i < 12; ++i) {
+    s[i *2 +1] =hex[(pack[i] >>4) &15];
+    s[i *2 +2] =hex[pack[i] &15];
+  }
+  return(25);
+}
diff --git a/src/fmt_ptime.h b/src/fmt_ptime.h
new file mode 100644
index 0000000..fe027b8
--- /dev/null
+++ b/src/fmt_ptime.h
@@ -0,0 +1,13 @@
+#ifndef FMT_PTIME_H
+#define FMT_PTIME_H
+
+#define FMT_PTIME 30
+
+#include <time.h>
+#include <sys/time.h>
+#include "taia.h"
+
+extern unsigned int fmt_ptime(char *, struct taia *);
+extern unsigned int fmt_taia(char *, struct taia *);
+
+#endif
diff --git a/src/pmatch.c b/src/pmatch.c
new file mode 100644
index 0000000..a362264
--- /dev/null
+++ b/src/pmatch.c
@@ -0,0 +1,40 @@
+
+int pmatch(const char *p, const char *s, unsigned int len) {
+  for (;;) {
+    char c =*p++;
+    if (! c) return(! len);
+    switch(c) {
+    case '*':
+      if (! (c =*p)) return(1);
+      for (;;) {
+	if (! len) return(0);
+	if (*s == c) break;
+	++s; --len;
+      }
+      continue;
+    case '+':
+      if ((c =*p++) != *s) return(0);
+      for (;;) {
+	if (! len) return(1);
+	if (*s != c) break;
+	++s; --len;
+      }
+      continue;
+      /*
+    case '?':
+      if (*p == '?') {
+	if (*s != '?') return(0);
+	++p;
+      }
+      ++s; --len;
+      continue;
+      */
+    default:
+      if (! len) return(0);
+      if (*s != c) return(0);
+      ++s; --len;
+      continue;
+    }
+  }
+  return(0);
+}
diff --git a/src/pmatch.h b/src/pmatch.h
new file mode 100644
index 0000000..8d4eb8b
--- /dev/null
+++ b/src/pmatch.h
@@ -0,0 +1,6 @@
+#ifndef PMATCH_H
+#define PMATCH_H
+
+extern unsigned int pmatch(const char *, const char *, unsigned int);
+
+#endif
diff --git a/src/svlogd.c b/src/svlogd.c
new file mode 100644
index 0000000..1c3b26b
--- /dev/null
+++ b/src/svlogd.c
@@ -0,0 +1,654 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdio.h>
+#include "pmatch.h"
+#include "fmt_ptime.h"
+#include "alloc.h"
+#include "stralloc.h"
+#include "strerr.h"
+#include "buffer.h"
+#include "sig.h"
+#include "env.h"
+#include "fd.h"
+#include "wait.h"
+#include "error.h"
+#include "sgetopt.h"
+#include "open.h"
+#include "openreadclose.h"
+#include "coe.h"
+#include "lock.h"
+#include "str.h"
+#include "byte.h"
+#include "scan.h"
+#include "direntry.h"
+#include "taia.h"
+#include "fmt.h"
+
+#define USAGE " [-tv] [-r c] [-R abc] [-l n ] [-b n] dir ..."
+#define VERSION "$Id$"
+
+#define FATAL "svlogd: fatal: "
+#define WARNING "svlogd: warning: "
+#define PAUSE "svlogd: pausing: "
+#define INFO "svlogd: info: "
+
+const char *progname;
+
+unsigned int verbose =0;
+unsigned int timestamp =0;
+unsigned long linelen =1000;
+unsigned long buflen =1024;
+const char *replace ="";
+char repl =0;
+
+const char **fndir;
+int fdwdir;
+struct stat st;
+int i;
+stralloc sa;
+int wstat;
+struct taia now;
+
+char *databuf;
+buffer data;
+char *line;
+char stamp[FMT_PTIME];
+unsigned int exitasap =0;
+
+struct logdir {
+  int fddir;
+  char *btmp;
+  buffer b;
+  stralloc inst;
+  unsigned long size;
+  unsigned long sizemax;
+  unsigned long nmax;
+  stralloc processor;
+  int ppid;
+  char fnsave[FMT_PTIME];
+  char *name;
+  int fdcur;
+  int fdlock;
+  unsigned int match;
+} *dir;
+unsigned int dirn =0;
+
+void usage() { strerr_die4x(111, "usage: ", progname, USAGE, "\n"); }
+void die_nomem() { strerr_die2x(111, FATAL, "out of memory."); }
+void fatal(char *m0) { strerr_die3sys(111, FATAL, m0, ": "); }
+void fatal2(char *m0, char *m1) {
+  strerr_die5sys(111, FATAL, m0, ": ", m1, ": ");
+}
+void warn(char *m0) { strerr_warn3(WARNING, m0, ": ", &strerr_sys); }
+void warn2(char *m0, char *m1) {
+  strerr_warn5(WARNING, m0, ": ", m1, ": ", &strerr_sys);
+}
+void pause_nomem() { strerr_warn2(PAUSE, "out of memory.", 0); sleep(3); }
+void pause1(char *m0) { strerr_warn3(PAUSE, m0, ": ", &strerr_sys); sleep(3); }
+void pause2(char *m0, char *m1) {
+  strerr_warn5(PAUSE, m0, ": ", m1, ": ", &strerr_sys);
+  sleep(3);
+}
+
+int processorstart(struct logdir *ld) {
+  int pid;
+
+  if (! ld->processor.len) return(0);
+  if (ld->ppid) {
+    warn2("processor already running", ld->name);
+    return(0);
+  }
+  while ((pid =fork()) == -1)
+    pause2("unable to fork for processor", ld->name);
+  if (! pid) {
+    char *prog[4];
+    int fd;
+
+    /* child */
+    sig_uncatch(sig_term);
+    sig_uncatch(sig_alarm);
+    sig_uncatch(sig_hangup);
+    sig_unblock(sig_term);
+    sig_unblock(sig_alarm);
+    sig_unblock(sig_hangup);
+    
+    if ((fd =open_read(ld->fnsave)) == -1)
+      fatal2("unable to open input for processor", ld->name);
+    if (fd_move(0, fd) == -1)
+      fatal2("unable to move filedescriptor for processor", ld->name);
+    ld->fnsave[26] ='t';
+    if ((fd =open_trunc(ld->fnsave)) == -1)
+      fatal2("unable to open output for processor", ld->name);
+    if (fd_move(1, fd) == -1)
+      fatal2("unable to move filedescriptor for processor", ld->name);
+    if ((fd =open_read("state")) == -1)
+      fatal2("unable to open state for processor", ld->name);
+    if (fd_move(4, fd) == -1)
+      fatal2("unable to move filedescriptor for processor", ld->name);
+    if ((fd =open_trunc("newstate")) == -1)
+      fatal2("unable to open newstate for processor", ld->name);
+    if (fd_move(5, fd) == -1)
+      fatal2("unable to move filedescriptor for processor", ld->name);
+
+    prog[0] = "sh";
+    prog[1] = "-c";
+    prog[2] = ld->processor.s;
+    prog[3] = 0;
+    execve("/bin/sh", prog, environ);
+    fatal2("unable to run processor", ld->name);
+  }
+  ld->ppid =pid;
+  return(1);  
+}
+int processorstop(struct logdir *ld) {
+  char f[28];
+
+  if (ld->ppid) {
+    sig_unblock(sig_hangup);
+    while (wait_pid(&wstat, ld->ppid) == -1)
+      pause2("error waiting for processor", ld->name);
+    sig_block(sig_hangup);
+    ld->ppid =0;
+  }
+  while (fchdir(ld->fddir) == -1)
+    pause2("unable to change directory, want processor", ld->name);
+  if (wait_exitcode(wstat) != 0) {
+    warn2("processor crashed, restart", ld->name);
+    ld->fnsave[26] ='t';
+    unlink(ld->fnsave);
+    ld->fnsave[26] ='u';
+    if (ld->processor.len) processorstart(ld);
+    while (fchdir(fdwdir) == -1)
+      pause1("unable to change to initial working directory");
+    return(ld->processor.len ? 0 : 1);
+  }
+  if (unlink(ld->fnsave) == -1)
+    strerr_warn5(WARNING, "unable to unlink: ", ld->name, "/", ld->fnsave, 0);
+  ld->fnsave[26] ='t';
+  byte_copy(f, 26, ld->fnsave);
+  f[26] ='s'; f[27] =0;
+  while (rename(ld->fnsave, f) == .1)
+    pause2("unable to rename processed", ld->name);
+  while (chmod(f, 0744) == -1)
+    pause2("unable to set mode of processed", ld->name);
+  while (fchdir(fdwdir) == -1)
+    pause1("unable to change to initial working directory");
+  return(1);
+}
+
+int rotate(struct logdir *ld) {
+  DIR *d;
+  direntry *f;
+  int n =0;
+  char tmp[FMT_ULONG +1];
+  char oldest[FMT_PTIME];
+
+  if (ld->size <= 0) return(1);
+  if (ld->ppid) while(! processorstop(ld));
+
+  while (fchdir(ld->fddir) == -1)
+    pause2("unable to change directory, want rotate", ld->name);
+
+  /* create new filename */
+  ld->fnsave[25] ='.';
+  if (ld->processor.len)
+    ld->fnsave[26] ='u';
+  else
+    ld->fnsave[26] ='s';
+  ld->fnsave[27] =0;
+  do {
+    taia_now(&now);
+    fmt_taia(ld->fnsave, &now);
+    errno =0;
+  } while ((stat(ld->fnsave, &st) != -1) || (errno != error_noent));
+
+  buffer_flush(&ld->b);
+  while (fsync(ld->fdcur) == -1)
+    pause2("unable to fsync current logfile", ld->name);
+  while (fchmod(ld->fdcur, 0744) == -1)
+    pause2("unable to set mode of current", ld->name);
+  close(ld->fdcur);
+  if (verbose) {
+    tmp[0] =' '; tmp[fmt_ulong(tmp +1, ld->size) +1] =0;
+    strerr_warn6(INFO, "rename: ", ld->name, "/current ",
+		 ld->fnsave, tmp, 0);
+  }
+  while (rename("current", ld->fnsave) == -1)
+    pause2("unable to rename current", ld->name);
+  while ((ld->fdcur =open_append("current")) == -1)
+    pause2("unable to create new current", ld->name);
+  coe(ld->fdcur);
+  ld->size =0;
+  while (fchmod(ld->fdcur, 0644) == -1)
+    pause2("unable to set mode of current", ld->name);
+  buffer_init(&ld->b, buffer_unixwrite, ld->fdcur, ld->btmp, sizeof ld->btmp);
+
+  oldest[0] ='A'; oldest[1] =oldest[27] =0; errno =0;
+  while (! (d =opendir(".")))
+    pause2("unable to open directory, want rotate", ld->name);
+  while ((f =readdir(d)))
+    if ((f->d_name[0] == '@') && (str_len(f->d_name) == 27)) {
+      ++n;
+      if (str_diff(f->d_name, oldest) < 0)
+	byte_copy(oldest, 27, f->d_name);
+    }
+  if (errno) warn2("unable to read directory", ld->name);
+  closedir(d);
+  
+  if (ld->nmax && (n >= ld->nmax)) {
+    if (verbose)
+      strerr_warn5(INFO, "delete: ", ld->name, "/", oldest, 0);
+    if (*oldest && (unlink(oldest) == -1))
+      warn2("unable to unlink oldest logfile", ld->name);
+  }
+
+  /*
+    start processor
+  */
+  if (ld->processor.len) {
+    processorstart(ld);
+  }
+  
+  while (fchdir(fdwdir) == -1)
+    pause1("unable to change to initial working directory");
+  return(1);
+}
+
+void logdir_close(struct logdir *ld) {
+  if (ld->fddir == -1) return;
+  if (verbose) strerr_warn3(INFO, "close: ", ld->name, 0);
+  close(ld->fddir);
+  ld->fddir =-1;
+  if (ld->fdcur) {
+    buffer_flush(&ld->b);
+    while (fsync(ld->fdcur) == -1)
+      pause2("unable to fsync current logfile", ld->name);
+    while (fchmod(ld->fdcur, 0744) == -1)
+      pause2("unable to set mode of current", ld->name);
+    close(ld->fdcur);
+  }
+  if (ld->fdlock) close(ld->fdlock);
+  ld->fdlock =-1;
+}
+
+int logdir_open(struct logdir *ld, const char *fn) {
+  if ((ld->fddir =open_read(fn)) == -1) {
+    warn2("unable to open log directory", (char*)fn);
+    return(0);
+  }
+  coe(ld->fddir);
+  if (fchdir(ld->fddir) == -1) {
+    logdir_close(ld);
+    warn2("unable to change directory", (char*)fn);
+    return(0);
+  }
+  ld->fdlock =open_append("lock");
+  if ((ld->fdlock == -1) || (lock_exnb(ld->fdlock) == -1)) {
+    logdir_close(ld);
+    warn2("unable to lock directory", (char*)fn);
+    while (fchdir(fdwdir) == -1)
+      pause1("unable to change to initial working directory");
+    return(0);
+  }
+  coe(ld->fdlock);
+
+  ld->size =0;
+  ld->sizemax =9000;
+  ld->nmax =10;
+  ld->name =(char*)fn;
+  ld->match =0;
+  while (! stralloc_copys(&ld->inst, "")) pause_nomem();
+  while (! stralloc_copys(&ld->processor, "")) pause_nomem();
+
+  /* read config */
+  if ((i =openreadclose("config", &sa, 128)) == -1)
+    warn2("unable to read config", ld->name);
+  if (i != 0) {
+    int len;
+    
+    if (verbose) strerr_warn4(INFO, "read: ", ld->name, "/config", 0);
+    for (i =0; i < sa.len -1; ++i) {
+      if ((len =byte_chr(&sa.s[i], sa.len -i, '\n')) == 1) {
+	++i; continue;
+      }
+      sa.s[len +i] =0;
+      switch(sa.s[i]) {
+      case '\n':
+      case '#':
+	 break;
+      case '+':
+      case '-':
+      case 'e':
+      case 'E':
+	while (! stralloc_catb(&ld->inst, &sa.s[i], len)) pause_nomem();
+	while (! stralloc_0(&ld->inst)) pause_nomem();
+	break;
+      case 's':
+	scan_ulong(&sa.s[i +1], &ld->sizemax);
+	if (ld->sizemax < 2048) ld->sizemax =2048;
+	break;
+      case 'n':
+	scan_ulong(&sa.s[i +1], &ld->nmax);
+	break;
+      case '!':
+	while (! stralloc_copys(&ld->processor, &sa.s[i +1]))
+	  pause_nomem();
+	while (! stralloc_0(&ld->processor)) pause_nomem();
+	break;
+      }
+      i +=len;
+    }
+  }
+
+  /* open current */
+  if ((i =stat("current", &st)) != -1) {
+    if (st.st_size && ((st.st_mode & S_IRWXU) != S_IXUSR)) {
+      ld->fnsave[25] ='.'; ld->fnsave[26] ='u'; ld->fnsave[27] =0;
+      do {
+	taia_now(&now);
+	fmt_taia(ld->fnsave, &now);
+	errno =0;
+      } while ((stat(ld->fnsave, &st) != -1) || (errno != error_noent));
+      while (rename("current", ld->fnsave) == -1)
+	pause2("unable to rename current", ld->name);
+    }
+    else
+      ld->size =st.st_size;
+  }
+  else
+    if (errno != error_noent) {
+      logdir_close(ld);
+      warn2("unable to stat current", ld->name);
+      while (fchdir(fdwdir) == -1)
+	pause1("unable to change to initial working directory");
+      return(0);
+    }
+  while ((ld->fdcur =open_append("current")) == -1)
+    pause2("unable to open current", ld->name);
+  coe(ld->fdcur);
+  while (fchmod(ld->fdcur, 0644) == -1)
+    pause2("unable to set mode of current", ld->name);
+  if (! ld->btmp) 
+    while (! (ld->btmp =(char*)alloc(buflen *sizeof(char)))) pause_nomem();
+  buffer_init(&ld->b, buffer_unixwrite, ld->fdcur, ld->btmp, sizeof ld->btmp);
+  
+  if (verbose) {
+    if (i == 0) strerr_warn4(INFO, "append: ", ld->name, "/current", 0);
+    else strerr_warn4(INFO, "new: ", ld->name, "/current", 0);
+  }
+  
+  while (fchdir(fdwdir) == -1)
+    pause1("unable to change to initial working directory");
+  return(1);
+}
+
+void logdirs_reopen(void) {
+  int l;
+
+  for (l =0; l < dirn; ++l) {
+    logdir_close(&dir[l]);    
+    logdir_open(&dir[l], fndir[l]);
+  }
+}
+
+int linestart(struct logdir *ld, char *s, int len) {
+  /* check inst, set match */
+  ld->match ='+';
+  if (ld->inst.len) {
+    for (i =0; i < ld->inst.len; ++i) {
+      switch(ld->inst.s[i]) {
+      case '+':
+      case '-':
+	if (pmatch(&ld->inst.s[i +1], s, len))
+	  ld->match =ld->inst.s[i];
+	break;
+      case 'e':
+	if (pmatch(&ld->inst.s[i +1], s, len)) {
+	  if (timestamp) buffer_puts(buffer_2, stamp);
+	  buffer_put(buffer_2, s, len);
+	  if (len == linelen) buffer_puts(buffer_2, "...");
+	  buffer_putflush(buffer_2, "\n", 1);
+	}
+	break;
+      case 'E':
+	if (! pmatch(&ld->inst.s[i +1], s, len)) {
+	  if (timestamp) buffer_puts(buffer_2, stamp);
+	  buffer_put(buffer_2, s, len);
+	  if (len == linelen) buffer_puts(buffer_2, "...");
+	  buffer_putflush(buffer_2, "\n", 1);
+	}
+	break;
+      }
+      i +=byte_chr(&ld->inst.s[i], ld->inst.len -i, 0);
+    }
+  }
+  if (ld->match == '-') return(0);
+  if (timestamp) {
+    buffer_puts(&ld->b, stamp);
+    ld->size +=26;
+  }
+  buffer_put(&ld->b, s, len);
+  ld->size +=len;
+  return(1);
+}
+int lineadd(struct logdir *ld, char *s, int len) {
+  if (ld->match != '+') return(0);
+  buffer_put(&ld->b, s, len);
+  ld->size +=len;
+  if (ld->sizemax && (ld->size >= ld->sizemax)) rotate(ld);
+  return(1);
+}
+int lineflush(struct logdir *ld, char *s, int len) {
+  switch(ld->match) {
+  case '-':
+    return(0);
+  case 0:
+    linestart(ld, s, len);
+    break;
+  case '+':
+    buffer_put(&ld->b, s, len);
+    ld->size +=len;
+    break;
+  }
+  if (ld->match == '+') {
+    buffer_putflush(&ld->b, "\n", 1);
+    ld->size +=1;
+    ld->match =0;
+    if (ld->sizemax && (ld->size >= (ld->sizemax -linelen))) rotate(ld);
+    return(1);
+  }
+  return(0);
+}
+int buffer_pread(int fd, char *s, unsigned int len) {
+  len =read(fd, s, len);
+  if ((len == -1) && (errno == error_intr)) return(0);
+  return(len);
+}
+void sig_term_handler(void) {
+  exitasap =1;
+  strerr_warn2(INFO, "sigterm received.", 0);
+}
+void sig_child_handler(void) {
+  int pid, l;
+
+  while ((pid =wait_nohang(&wstat)) > 0)
+    for (l =0; l < dirn; ++l)
+      if (dir[l].ppid == pid) {
+	dir[l].ppid =0;
+	processorstop(&dir[l]);
+	break;
+      }
+  strerr_warn2(INFO, "sigchild received.", 0);
+}
+void sig_alarm_handler(void) {
+  int l;
+  
+  for (l =0; l < dirn; ++l)
+    if (dir[l].fddir != -1)
+      if (dir[l].size > 0)
+	rotate(&dir[l]);
+  strerr_warn2(INFO, "sigalarm received.", 0);
+}
+void sig_hangup_handler(void) {
+  logdirs_reopen();
+  strerr_warn2(INFO, "sighangup received.", 0);
+}
+
+int main(int argc, const char **argv) {
+  int opt;
+  unsigned int eol;
+
+  progname =*argv;
+
+  while ((opt =getopt(argc, argv, "R:r:l:b:tvV")) != opteof) {
+    switch(opt) {
+    case 'R':
+      replace =optarg;
+      if (! repl) repl ='_';
+      break;
+    case 'r':
+      repl =*optarg;
+      if (! repl || *(optarg +1)) usage();
+      break;
+    case 'l':
+      scan_ulong(optarg, &linelen);
+      if (linelen == 0) linelen =1000;
+      break;
+    case 'b':
+      scan_ulong(optarg, &buflen);
+      if (buflen == 0) buflen =1024;
+      break;
+    case 't':
+      ++timestamp;
+      break;
+    case 'v':
+      ++verbose;
+      break;
+    case 'V': strerr_warn1(VERSION, 0);
+    case '?': usage();
+    }
+  }
+  argv +=optind;
+
+  dirn =argc -optind;
+  if (dirn <= 0) usage();
+  if (buflen <= linelen) usage();
+  if ((fdwdir =open_read(".")) == -1)
+    fatal("unable to open current working directory");
+  coe(fdwdir);
+  dir =(struct logdir*)alloc(dirn *sizeof(struct logdir));
+  if (! dir) die_nomem();
+  for (i =0; i < dirn; ++i) {
+    dir[i].fddir =-1;
+    dir[i].btmp =0; /* grm */
+  }
+  databuf =(char*)alloc(buflen *sizeof(char));
+  if (! databuf) die_nomem();
+  buffer_init(&data, buffer_pread, 0, databuf, buflen);
+  line =(char*)alloc(linelen *sizeof(char));
+  if (! line) die_nomem();
+  fndir =argv;
+
+  sig_block(sig_term);
+  sig_block(sig_child);
+  sig_block(sig_alarm);
+  sig_block(sig_hangup);
+  sig_catch(sig_term, sig_term_handler);
+  sig_catch(sig_child, sig_child_handler);
+  sig_catch(sig_alarm, sig_alarm_handler);
+  sig_catch(sig_hangup, sig_hangup_handler);
+
+  logdirs_reopen();
+
+  for(eol =0;;) {
+    int r, len;
+    char *ch;
+
+    if (exitasap) {
+      /* check for processors */
+      for (i =0; i < dirn; ++i)
+	if (dir[i].ppid) while (! processorstop(&dir[i]));
+      break;
+    }
+
+    sig_unblock(sig_term);
+    sig_unblock(sig_child);
+    sig_unblock(sig_alarm);
+    sig_unblock(sig_hangup);
+    errno =0;
+    r =buffer_feed(&data);
+    sig_block(sig_term);
+    sig_block(sig_child);
+    sig_block(sig_alarm);
+    sig_block(sig_hangup);
+
+    if (r == -1) {
+      warn("unable to read standard input");
+      continue;
+    }
+    if (r == 0) {
+      if (errno == error_intr) continue;
+      break; /* eof */
+    }
+    if (r > linelen) r =linelen;
+    if (timestamp && (eol == 0)) {
+      taia_now(&now);
+      switch (timestamp) {
+      case 1:
+	stamp[fmt_taia(stamp, &now)] =' ';
+	stamp[26] =0;
+	break;
+      case 2:
+	stamp[fmt_ptime(stamp, &now)] =' ';
+	stamp[26] =0;
+	break;
+      case 3:
+	stamp[fmt_ptime(stamp, &now)] =' ';
+	stamp[19] =' '; stamp[20] =0;
+	break;
+      }
+    }
+    ch =buffer_peek(&data);
+    for (len =0; len < r; ++len, ++ch) {
+      if (*ch == '\n') {
+	eol =2;
+	break;
+      }
+      line[len] =*ch;
+      
+      if (repl) {
+	if ((line[len] < 33) || (line[len] > 126))
+	  line[len] =repl;
+	else
+	  for (i =0; replace[i]; ++i)
+	    if (line[len] == replace[i]) {
+	      line[len] =repl;
+	      break;
+	    }
+      }
+    }
+    buffer_seek(&data, len);
+    for (i =0; i < dirn; ++i)
+      if (dir[i].fddir != -1) {
+	switch(eol) {
+	case 0: linestart(&dir[i], line, len); break;
+	case 1: lineadd(&dir[i], line, len); break;
+	case 2: lineflush(&dir[i], line, len); break;
+	}
+      }
+    if (eol == 0) eol =1;
+    if (eol == 2) { eol =0; buffer_seek(&data, 1); }
+  }
+  
+  for (i =0; i < dirn; ++i) {
+    if (dir[i].fddir != -1)
+      buffer_flush(&dir[i].b);
+    logdir_close(&dir[i]);
+  }
+  _exit(0);
+}