aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2008-02-26 13:42:41 +0000
committerSergey Poznyakoff <gray@gnu.org.ua>2008-02-26 13:42:41 +0000
commitf439e7de17824c8c22c6cbbd80ac78eb6877f2cd (patch)
tree8ef85bbcc6da98f378ea2d99ed27271204db0772
downloadpies-f439e7de17824c8c22c6cbbd80ac78eb6877f2cd.tar.gz
pies-f439e7de17824c8c22c6cbbd80ac78eb6877f2cd.tar.bz2
* pies/pies.c: New configuration statement "chdir".
* pies/progman.c (struct prog.v.p): New member `dir'. (register_prog): Initialize newp->v.p.dir; (prog_start): Change to the working directory, if required by config.
-rw-r--r--AUTHORS1
-rwxr-xr-xbootstrap312
-rw-r--r--doc/Makefile.am141
-rwxr-xr-xdoc/check-docs.sh74
-rw-r--r--doc/fdl.texi450
-rwxr-xr-xdoc/gendocs_template123
-rw-r--r--doc/macros.texi31
-rw-r--r--doc/mastermenu.el90
-rw-r--r--doc/rendition.texi85
-rw-r--r--doc/untabify.el13
-rw-r--r--pies/Makefile.am39
-rw-r--r--pies/depmap.c170
-rw-r--r--pies/pies.c1010
-rw-r--r--pies/pies.h119
-rw-r--r--pies/pies.rcin25
-rw-r--r--pies/progman.c971
16 files changed, 3654 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..89f81e3
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Sergey Poznyakoff gray@gnu.org
diff --git a/bootstrap b/bootstrap
new file mode 100755
index 0000000..51d94b2
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,312 @@
+#! /bin/sh
+############################
+## Configuration
+############################
+
+GNULIB_DATE=2007-05-12
+GNU_SOURCE_BASE=gnu
+
+MODLIST="argp\
+ fprintftime\
+ getline\
+ gettext\
+ malloc\
+ memrchr\
+ mkdtemp\
+ obstack\
+ realloc\
+ regex\
+ save-cwd\
+ snprintf\
+ strtok_r\
+ vasprintf"
+
+MODAVOID="xalloc-die openat-die"
+
+# Other locale categories that need message catalogs.
+EXTRA_LOCALE_CATEGORIES=
+
+NLS_MARKERS="\
+ MF_THROW:2\
+ MF_ASSERT:3\
+ parse_error:1\
+ parse_error_locus:2\
+ mu_error:1\
+ gacopyz_io_log:3\
+ gacopyz_logmsg:2\
+ gacopyz_log:3"
+
+# Additional xgettext options to use. Use "\\\newline" to break lines.
+XGETTEXT_OPTIONS='\\\
+ --flag=_:1:pass-c-format\\\
+ --flag=N_:1:pass-c-format\\\
+ --flag=error:3:c-format --flag=error_at_line:5:c-format\\\
+'
+
+COPYRIGHT_HOLDER="Sergey Poznyakoff"
+
+# Translation Project URL, for the registry of all projects
+# and for the translation-team master directory.
+# (for future use)
+tp_url() {
+ echo "http://translationproject.org/domain/$1.html"
+}
+
+############################
+## Implementation
+############################
+
+usage() {
+ echo >&2 "\
+Usage: $0 [OPTION]... GNULIB_DIR
+Bootstrap this package from the checked-out sources.
+
+ --copy Copy files instead of creating symbolic links.
+ --force Attempt to bootstrap even if the sources seem
+ not to have been checked out.
+ --skip-po Do not download po files.
+ --update-po[=LANG] Update po file(s) and exit.
+
+The only argument specifes the file name of the directory were gnulib
+sources are located. You will have to check out them beforehand, \`bootstrap'
+won't do this for you.
+
+Local defaults can be provided by placing the file \`.bootstrap' in the
+current working directory. The file is read, comments and empty lines are
+removed, shell variables expanded and the result is prepended to the command
+line options.
+
+Running without arguments will suffice in most cases.
+"
+}
+
+# Read local configuration file
+if [ -r .bootstrap ]; then
+ echo "$0: Reading configuration file .bootstrap"
+ eval set -- "`sed 's/#.*$//;/^$/d' .bootstrap | tr '\n' ' '` $*"
+fi
+
+extract_package_name='
+ /^AC_INIT(/{
+ /.*,.*,.*,/{
+ s///
+ s/[][]//g
+ p
+ q
+ }
+ s/AC_INIT(\[*//
+ s/]*,.*//
+ s/^GNU //
+ y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
+ s/[^A-Za-z0-9_]/-/g
+ p
+ }
+'
+package=`sed -n "$extract_package_name" configure.ac` || exit
+
+extract_bug_address='
+ /^AC-INIT-BUG-ADDRESS/{
+ s/[][]//g
+ s/.*=//p
+ }
+'
+bug_address=`m4 -D'AC_INIT=AC-INIT-BUG-ADDRESS=$3' configure.ac | sed -n "$extract_bug_address"`
+if test -z "$bug_address"; then
+ bug_address="bug-${package}@gnu.org.ua"
+fi
+
+# Whether to use copies instead of symlinks.
+copy=false
+
+GNULIB_OPTION=
+DOWNLOAD_PO=
+
+for option
+do
+ case $option in
+ --help)
+ usage
+ exit;;
+ --skip-po | --no-po) # --no-po is for compatibility with 'tar' tradition.
+ DOWNLOAD_PO=skip;;
+ --update-po=*)
+ DOWNLOAD_PO=`expr "$option" : '--update-po=\(.*\)'`;;
+ --update-po)
+ DOWNLOAD_PO=only;;
+ --copy)
+ copy=true;;
+ -*)
+ echo >&2 "$0: $option: unknown option"
+ exit 1;;
+ *)
+ if [ -n "$NULIB_OPTION" ]; then
+ echo "$0: gnulib directory specified twice">&2
+ exit 1
+ fi
+ GNULIB_DIR=$option;;
+ esac
+done
+
+datecmp() {
+ test `echo "1 $1
+2 $2" | tr '-' ' ' | sort -n -k2 -k3 -k4 -k1 | sed -n '1s/ .*//p'` -eq 1
+}
+
+if [ -n "$GNULIB_DIR" ]; then
+ if [ -d "$GNULIB_DIR" ]; then
+ PATH="$GNULIB_DIR:$PATH"
+ if [ -r "$GNULIB_DIR/ChangeLog" ]; then
+ GNULIB_REV=`sed '/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/q' "$GNULIB_DIR/ChangeLog"`
+ if datecmp "$GNULIB_DATE" "$GNULIB_REV"; then :;
+ else
+ echo "$0: Gnulib version is too old: $GNULIB_REV">&2
+ echo "$0: It should not be older than $GNULIB_DATE">&2
+ echo "$0: Please, update gnulib and retry">&2
+ exit
+ fi
+ else
+ echo "$0: No $GNULIB_DIR/ChangeLog file found">&2
+ fi
+ else
+ echo "$0: specified gnulib location is not a directory">&2
+ fi
+else
+ echo "$0: warning: gnulib source directory not specified">&2
+ echo "$0: warning: looking for gnulib-tool in PATH...">&2
+fi
+
+gnulib-tool --version >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ echo "$0: Cannot run gnulib-tool. Try \`$0 --help' for more info.">&2
+ exit 1
+fi
+
+echo "$0: Bootstrapping $package"
+
+echo "$0: Running autopoint"
+autopoint -f || exit 1
+
+# Create gettext configuration.
+echo "$0: Creating po/Makevars from po/Makevars.template ..."
+rm -f po/Makevars
+
+if [ -n "$NLS_MARKERS" ]; then
+ XGETTEXT_OPTIONS="$XGETTEXT_OPTIONS "`echo $NLS_MARKERS | tr ' ' '\n' | sed 's/.*/ --flag=&:pass-c-format\\\\\\\\\\\\/'`"
+"
+fi
+
+sed '
+ 1i\
+# -*- buffer-read-only: t -*- vi: set ro:\
+# DO NOT EDIT! GENERATED AUTOMATICALLY!
+
+ /^COPYRIGHT_HOLDER *=/s/=.*/= '"$COPYRIGHT_HOLDER"'/
+ /^EXTRA_LOCALE_CATEGORIES *=/s/=.*/= '"$EXTRA_LOCALE_CATEGORIES"'/
+ /^MSGID_BUGS_ADDRESS *=/s/=.*/= '"$bug_address"'/
+ /^XGETTEXT_OPTIONS *=/{
+ s/$/ \\/
+ a\
+ '"$XGETTEXT_OPTIONS"' $${end_of_xgettext_options+}
+ }
+' po/Makevars.template >po/Makevars
+
+# Get translations.
+
+get_translations() {
+ subdir=$1
+ domain=$2
+ po_file=$3
+
+ case $WGET_COMMAND in
+ '')
+ echo "$0: wget not available; skipping translations";;
+ ?*)
+ url=`tp_url $domain`
+ baseurl=`expr "$url" : '\(.*\)/.*'`
+ echo "$0: getting translations into $subdir for $domain..." &&
+ case $po_file in
+ '') (cd $subdir && rm -f dummy `ls | sed -n '/\.gmo$/p; /\.po/p'`);;
+ esac &&
+
+ $WGET_COMMAND -O "$subdir/$domain.html" "$url" &&
+
+ sed -n 's|.*href="\(.*\)/\([^/][^/]*\)/'"$domain"'-\([^/"]*\)\.[^."]*\.po".*|\2:\3:\1|p' <"$subdir/$domain.html" |
+ sort -t: -k 1,1 -k 2,2n -k2,2 -k3,3n -k3,3 -k4,4n -k4,4 -k5,5n -k5.5 |
+ awk -F: '
+ { if (lang && $1 != lang) print lang, ver, $3 }
+ { lang = $1; ver = $2 }
+ END { if (lang) print lang, ver, $3 }
+ ' | awk -v domain="$domain" -v baseurl="$baseurl" -v subdir="$subdir" \
+ -v po_file="$po_file" '
+ {
+ lang = $1
+ if (po_file && po_file != (lang ".po")) next
+ ver = $2
+ printf "{ $WGET_COMMAND -O %s/%s.po %s/%s/%s/%s-%s.%s.po &&\n", subdir, lang, baseurl, $3, lang, domain, ver, lang
+ printf " msgfmt -c -o /dev/null %s/%s.po || {\n", subdir, lang
+ printf " echo >&2 '\'"$0"': omitting translation for %s'\''\n", lang
+ printf " rm -f %s/%s.po; }; } &&\n", subdir, lang
+ }
+ END { print ":" }
+ ' | WGET_COMMAND="$WGET_COMMAND" sh
+ ;;
+ esac &&
+ ls "$subdir"/*.po 2>/dev/null |
+ sed 's|.*/||; s|\.po$||' >"$subdir/LINGUAS" &&
+ rm -f "$subdir/$domain.html"
+}
+
+save_LC_ALL="$LC_ALL"
+LC_ALL=C
+case `wget --help` in
+*'--no-cache'*)
+ WGET_COMMAND='wget -nv --no-cache';;
+*'--cache=on/off'*)
+ WGET_COMMAND='wget -nv --cache=off';;
+*'--non-verbose'*)
+ WGET_COMMAND='wget -nv';;
+*)
+ WGET_COMMAND='';;
+esac
+LC_ALL="$save_LC_ALL"
+
+case $DOWNLOAD_PO in
+'skip')
+ ;;
+'')
+ get_translations po $package || exit
+ ;;
+'only')
+ get_translations po $package
+ exit
+ ;;
+*.po)
+ get_translations po $package "$DOWNLOAD_PO"
+ exit
+ ;;
+*)
+ get_translations po $package "${DOWNLOAD_PO}.po"
+ exit
+esac
+
+echo "$0: Importing gnulib files ..."
+if [ -n "$MODAVOID" ]; then
+ gnulib_tool_options="`echo $MODAVOID | sed 's/\([^ ][^ ]*\)/--avoid &/g'`"
+fi
+
+test -d $GNU_SOURCE_BASE || mkdir $GNU_SOURCE_BASE
+gnulib-tool --import --source-base=$GNU_SOURCE_BASE $gnulib_tool_options $MODLIST
+
+echo "$0: Reconfiguring ..."
+
+for command in \
+ 'aclocal --force -I m4' \
+ 'autoconf --force' \
+ 'autoheader --force' \
+ 'automake --add-missing --copy --force-missing';
+do
+ echo "$0: $command ..."
+ $command || exit
+done
+
+echo "$0: done. Now you can run './configure'."
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..0394d05
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,141 @@
+# This file is part of Mailfromd.
+# Copyright (C) 2005, 2006, 2007, 2008 Sergey Poznyakoff
+#
+# Mailfromd is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Mailfromd is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mailfromd. If not, see <http://www.gnu.org/licenses/>.
+
+info_TEXINFOS=mailfromd.texi
+mailfromd_TEXINFOS=\
+ fdl.texi\
+ gacopyz.texi\
+ macros.texi\
+ mtasim.texi\
+ rendition.texi\
+ strftime.texi\
+ values.texi
+
+EXTRA_DIST = \
+ check-docs.sh\
+ gendocs_template\
+ mastermenu.el\
+ untabify.el
+
+clean-local:
+ rm -rf manual
+
+# Checking
+check-format:
+ @if test -n "`cat $(info_TEXINFOS) $(mailfromd_TEXINFOS) | tr -d -c '\t'`"; then \
+ echo "Sources contain tabs; run make untabify"; \
+ false; \
+ fi
+
+check-pragmas:
+ @check-docs.sh pragmas \
+ '/} option_cache\[\] = {/,/^}/s/[ \t]*{ *"\(.*\)".*/\1/pg' \
+ 's/@deffnx* {pragma option} *\([^@, ]*\) .*/\1/p' \
+ $(top_srcdir)/mfd/main.c -- \
+ $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) -E - \
+ $(info_TEXINFOS)
+
+check-options:
+ @check-docs.sh options \
+ '/argp_option options\[\] = /,/^}/s/[ \t]*{ *"\([^,"]*\)".*/\1/pg' \
+ 's/@opindex *\([^@,]*\).*/\1/p' \
+ $(top_srcdir)/mfd/main.c -- \
+ $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) -E - \
+ $(info_TEXINFOS)
+
+check-builtins:
+ @check-docs.sh builtins \
+ '/MF_DEFUN/{s/[ \t]*MF_DEFUN *(\([^,][^,]*\),.*/\1/p;\
+ s/[ \t]*MF_DEFUN_VARARGS *(\([^,][^,]*\),.*/\1/p}' \
+ 's/@deftypefn {Built-in Function} *[^ ][^ ]* *\([^ ]*\).*/\1/p' \
+ $(top_srcdir)/mfd/bi_*.m4 -- \
+ $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) -E - \
+ $(info_TEXINFOS)
+
+check-mflib:
+ @check-docs.sh "library functions" \
+ '/^[ \t]*func[ \t][ \t]*__/b;\
+ /^[ \t]*func/s/[ \t]*func[ \t][ \t]*\(.[^ \t(]*\).*/\1/p' \
+ 's/@deftypefn {Library Function} *[^ ][^ ]* *\([^ ]*\).*/\1/p' \
+ $(top_srcdir)/mflib/*.mf -- \
+ $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) -E - \
+ $(info_TEXINFOS)
+
+check-exceptions:
+ @check-docs.sh exceptions \
+ '/typedef enum mf_status_code {/,/^};/s/[ \t]*mf_\(.*\),.*/\1/p' \
+ 's/@cindex \([^,][^,]*\), exception type/\1/p' \
+ $(top_srcdir)/mfd/mailfromd.h -- \
+ $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) -E - \
+ $(info_TEXINFOS)
+
+check-refs:
+ @sed -e = $(info_TEXINFOS) $(mailfromd_TEXINFOS) | \
+ sed -n 'N;/@FIXME-.*ref/{s/\(^[0-9][0-9]*\).*@FIXME-.*ref{\([^}]*\)}.*/$(info_TEXINFOS):\1: \2/gp}' > $@-t; \
+ if [ -s $@-t ]; then echo "Unresolved cross-references:"; cat $@-t;\
+ fi
+ rm -f $@-t
+
+check-fixmes:
+ @sed -e = $(info_TEXINFOS) | \
+ sed -n 'N;/@FIXME{/{s/\(^[0-9][0-9]*\).*@FIXME{\([^}]*\).*/$(info_TEXINFOS):\1: \2/gp}' > $@-t; \
+ if [ -s $@-t ]; then echo "Unresolved FIXMEs:"; cat $@-t;\
+ fi
+ rm -f $@-t
+
+check-unrevised:
+ @grep -Hn @UNREVISED $(info_TEXINFOS) > $@-t; \
+ if [ -s $@-t ]; then \
+ echo "Unrevised nodes:"; \
+ cat $@-t; \
+ rm $@-t; \
+ false;\
+ else \
+ rm $@-t; \
+ fi
+
+all-check-docs: check-format check-options check-pragmas check-builtins check-mflib check-exceptions check-refs check-fixmes check-unrevised
+
+check-docs:
+ $(MAKE) -k all-check-docs
+
+#
+
+master-menu:
+ emacs -batch -l mastermenu.el -f make-master-menu $(info_TEXINFOS)
+
+untabify:
+ emacs -batch -l untabify.el $(info_TEXINFOS) $(mailfromd_TEXINFOS)
+
+final: untabify master-menu
+
+# The rendering level is one of PUBLISH, DISTRIB or PROOF.
+# Just call `make RENDITION=PROOF [target]' if you want PROOF rendition.
+
+MAKEINFOFLAGS=-D$(RENDITION)
+
+GENDOCS=gendocs.sh
+
+TEXI2DVI=texi2dvi -t '@set $(RENDITION)' -E
+
+# Make sure you set TEXINPUTS.
+# TEXINPUTS=/usr/share/texmf/pdftex/plain/misc/ is ok for most distributions
+manual:
+ TEXINPUTS=$(srcdir):$(top_srcdir)/build-aux:$(TEXINPUTS) \
+ MAKEINFO="$(MAKEINFO) $(MAKEINFOFLAGS)" \
+ TEXI2DVI="$(TEXI2DVI) -t @finalout" \
+ $(GENDOCS) --texi2html $(PACKAGE) '$(PACKAGE_NAME) manual'
+
diff --git a/doc/check-docs.sh b/doc/check-docs.sh
new file mode 100755
index 0000000..c0b0487
--- /dev/null
+++ b/doc/check-docs.sh
@@ -0,0 +1,74 @@
+#! /bin/sh
+# This file is part of mailfrom filter.
+# Copyright (C) 2006, 2007 Sergey Poznyakoff
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+usage() {
+ cat <<EOT
+usage: $0 item code-sed doc-sed sources -- args...
+EOT
+}
+
+if [ $# -le 4 ]; then
+ usage
+ exit 2
+fi
+
+item=$1
+shift
+codesexp="$1"
+shift
+docsexp=$1
+shift
+
+source=
+while [ $# -ne 0 ]
+do
+ if [ "$1" = "--" ]; then
+ shift
+ break;
+ fi
+ source="$source $1"
+ shift
+done
+
+TEMPDIR=/tmp/mfck.$$
+mkdir $TEMPDIR || exit 1
+trap 'rm -rf $TEMPDIR' 1 2 13 15
+
+sed -n "$codesexp" $source | sort | uniq > $TEMPDIR/src
+$* | \
+ sed -n '/^@macro/,/^@end macro/d;'"$docsexp" \
+ | sort | uniq > $TEMPDIR/doc
+
+join -v1 $TEMPDIR/src $TEMPDIR/doc > $TEMPDIR/src-doc
+join -v2 $TEMPDIR/src $TEMPDIR/doc > $TEMPDIR/doc-src
+(if [ -s $TEMPDIR/src-doc ]; then
+ echo "Not documented $item:"
+ cat $TEMPDIR/src-doc
+ fi
+ if [ -s $TEMPDIR/doc-src ]; then
+ echo "Non-existing $item:"
+ cat $TEMPDIR/doc-src
+ fi) > $TEMPDIR/report
+
+if [ -s $TEMPDIR/report ]; then
+ cat $TEMPDIR/report
+ rm -rf $TEMPDIR
+ exit 1
+else
+ rm -rf $TEMPDIR
+ exit 0
+fi
diff --git a/doc/fdl.texi b/doc/fdl.texi
new file mode 100644
index 0000000..a3c8c6f
--- /dev/null
+++ b/doc/fdl.texi
@@ -0,0 +1,450 @@
+@setfilename fdl.info
+@appendix GNU Free Documentation License
+@cindex FDL, GNU Free Documentation License
+@center Version 1.2, November 2002
+
+@display
+Copyright @copyright{} 2000,2001,2002 Free Software Foundation, Inc.
+59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+@end display
+
+@enumerate 0
+@item
+PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document @dfn{free} in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of ``copyleft'', which means that derivative
+works of the document must themselves be free in the same sense. It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does. But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book. We recommend this License
+principally for works whose purpose is instruction or reference.
+
+@item
+APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License. Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein. The ``Document'', below,
+refers to any such manual or work. Any member of the public is a
+licensee, and is addressed as ``you''. You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A ``Modified Version'' of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A ``Secondary Section'' is a named appendix or a front-matter section
+of the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject. (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.) The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The ``Invariant Sections'' are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License. If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant. The Document may contain zero
+Invariant Sections. If the Document does not identify any Invariant
+Sections then there are none.
+
+The ``Cover Texts'' are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License. A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A ``Transparent'' copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters. A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text. A copy that is not ``Transparent'' is called ``Opaque''.
+
+Examples of suitable formats for Transparent copies include plain
+@sc{ascii} without markup, Texinfo input format, La@TeX{} input
+format, @acronym{SGML} or @acronym{XML} using a publicly available
+@acronym{DTD}, and standard-conforming simple @acronym{HTML},
+PostScript or @acronym{PDF} designed for human modification. Examples
+of transparent image formats include @acronym{PNG}, @acronym{XCF} and
+@acronym{JPG}. Opaque formats include proprietary formats that can be
+read and edited only by proprietary word processors, @acronym{SGML} or
+@acronym{XML} for which the @acronym{DTD} and/or processing tools are
+not generally available, and the machine-generated @acronym{HTML},
+PostScript or @acronym{PDF} produced by some word processors for
+output purposes only.
+
+The ``Title Page'' means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page. For works in
+formats which do not have any title page as such, ``Title Page'' means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+A section ``Entitled XYZ'' means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language. (Here XYZ stands for a
+specific section name mentioned below, such as ``Acknowledgements'',
+``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title''
+of such a section when you modify the Document means that it remains a
+section ``Entitled XYZ'' according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document. These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+@item
+VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no other
+conditions whatsoever to those of this License. You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute. However, you may accept
+compensation in exchange for copies. If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+@item
+COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover. Both covers must also clearly and legibly identify
+you as the publisher of these copies. The front cover must present
+the full title with all words of the title equally prominent and
+visible. You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to give
+them a chance to provide you with an updated version of the Document.
+
+@item
+MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it. In addition, you must do these things in the Modified Version:
+
+@enumerate A
+@item
+Use in the Title Page (and on the covers, if any) a title distinct
+from that of the Document, and from those of previous versions
+(which should, if there were any, be listed in the History section
+of the Document). You may use the same title as a previous version
+if the original publisher of that version gives permission.
+
+@item
+List on the Title Page, as authors, one or more persons or entities
+responsible for authorship of the modifications in the Modified
+Version, together with at least five of the principal authors of the
+Document (all of its principal authors, if it has fewer than five),
+unless they release you from this requirement.
+
+@item
+State on the Title page the name of the publisher of the
+Modified Version, as the publisher.
+
+@item
+Preserve all the copyright notices of the Document.
+
+@item
+Add an appropriate copyright notice for your modifications
+adjacent to the other copyright notices.
+
+@item
+Include, immediately after the copyright notices, a license notice
+giving the public permission to use the Modified Version under the
+terms of this License, in the form shown in the Addendum below.
+
+@item
+Preserve in that license notice the full lists of Invariant Sections
+and required Cover Texts given in the Document's license notice.
+
+@item
+Include an unaltered copy of this License.
+
+@item
+Preserve the section Entitled ``History'', Preserve its Title, and add
+to it an item stating at least the title, year, new authors, and
+publisher of the Modified Version as given on the Title Page. If
+there is no section Entitled ``History'' in the Document, create one
+stating the title, year, authors, and publisher of the Document as
+given on its Title Page, then add an item describing the Modified
+Version as stated in the previous sentence.
+
+@item
+Preserve the network location, if any, given in the Document for
+public access to a Transparent copy of the Document, and likewise
+the network locations given in the Document for previous versions
+it was based on. These may be placed in the ``History'' section.
+You may omit a network location for a work that was published at
+least four years before the Document itself, or if the original
+publisher of the version it refers to gives permission.
+
+@item
+For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve
+the Title of the section, and preserve in the section all the
+substance and tone of each of the contributor acknowledgements and/or
+dedications given therein.
+
+@item
+Preserve all the Invariant Sections of the Document,
+unaltered in their text and in their titles. Section numbers
+or the equivalent are not considered part of the section titles.
+
+@item
+Delete any section Entitled ``Endorsements''. Such a section
+may not be included in the Modified Version.
+
+@item
+Do not retitle any existing section to be Entitled ``Endorsements'' or
+to conflict in title with any Invariant Section.
+
+@item
+Preserve any Warranty Disclaimers.
+@end enumerate
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant. To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled ``Endorsements'', provided it contains
+nothing but endorsements of your Modified Version by various
+parties---for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version. Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity. If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+@item
+COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy. If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled ``History''
+in the various original documents, forming one section Entitled
+``History''; likewise combine any sections Entitled ``Acknowledgements'',
+and any sections Entitled ``Dedications''. You must delete all
+sections Entitled ``Endorsements.''
+
+@item
+COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other documents
+released under this License, and replace the individual copies of this
+License in the various documents with a single copy that is included in
+the collection, provided that you follow the rules of this License for
+verbatim copying of each of the documents in all other respects.
+
+You may extract a single document from such a collection, and distribute
+it individually under this License, provided you insert a copy of this
+License into the extracted document, and follow this License in all
+other respects regarding verbatim copying of that document.
+
+@item
+AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an ``aggregate'' if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+@item
+TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections. You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers. In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled ``Acknowledgements'',
+``Dedications'', or ``History'', the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+@item
+TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document except
+as expressly provided for under this License. Any other attempt to
+copy, modify, sublicense or distribute the Document is void, and will
+automatically terminate your rights under this License. However,
+parties who have received copies, or rights, from you under this
+License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+@item
+FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions
+of the GNU Free Documentation License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns. See
+@uref{http://www.gnu.org/copyleft/}.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License ``or any later version'' applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation. If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation.
+@end enumerate
+
+@page
+@appendixsec ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+@smallexample
+@group
+ Copyright (C) @var{year} @var{your name}.
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.2
+ or any later version published by the Free Software Foundation;
+ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
+ Texts. A copy of the license is included in the section entitled ``GNU
+ Free Documentation License''.
+@end group
+@end smallexample
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the ``with...Texts.'' line with this:
+
+@smallexample
+@group
+ with the Invariant Sections being @var{list their titles}, with
+ the Front-Cover Texts being @var{list}, and with the Back-Cover Texts
+ being @var{list}.
+@end group
+@end smallexample
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
+
+@c Local Variables:
+@c ispell-local-pdict: "ispell-dict"
+@c End:
+
diff --git a/doc/gendocs_template b/doc/gendocs_template
new file mode 100755
index 0000000..c8b7c05
--- /dev/null
+++ b/doc/gendocs_template
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!-- $Id$ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+
+<head>
+<title>%%TITLE%% - Free Software - puszcza.gnu.org.ua</title>
+<meta http-equiv="content-type" content='text/html; charset=utf-8' />
+<link rel="stylesheet" type="text/css" href="/local/css/gnu.css" />
+<link rev="made" href="mailto:gray@gnu.org" />
+ <link rel="icon" type="image/png" href="/graphics/gnu-head-icon.png" />
+</head>
+
+<!-- This document is in XML, and xhtml 1.0 -->
+<!-- Please make sure to properly nest your tags -->
+<!-- and ensure that your final document validates -->
+<!-- consistent with W3C xhtml 1.0 and CSS standards -->
+<!-- See validator.w3.org -->
+
+<body>
+
+<h3>%%TITLE%%</h3>
+
+<address>Sergey Poznyakoff</address>
+<address>last updated %%DATE%%</address>
+<p>
+<a href="/graphics/gnu-head.jpg">
+ <img src="/graphics/gnu-head-sm.jpg"
+ alt=" [image of the head of a GNU] "
+ width="129" height="122" />
+</a>
+
+</p>
+<hr />
+
+<p>The manual for %%PACKAGE%% is available in the following formats:</p>
+
+<ul>
+ <li><a href="%%PACKAGE%%.html">HTML
+ (%%HTML_MONO_SIZE%%K bytes)</a> - entirely on one web page.</li>
+ <li><a href="html_node/index.html">HTML</a> - with one web page per
+ node.</li>
+%%IF HTML_SECTION%%
+ <li><a href="html_section/index.html">HTML</a> - with one web page per
+ section.</li>
+%%ENDIF HTML_SECTION%%
+%%IF HTML_CHAPTER%%
+ <li><a href="html_chapter/index.html">HTML</a> - with one web page per
+ chapter.</li>
+%%ENDIF HTML_CHAPTER%%
+ <li><a href="%%PACKAGE%%.html.gz">HTML compressed
+ (%%HTML_MONO_GZ_SIZE%%K gzipped characters)</a> - entirely on
+ one web page.</li>
+ <li><a href="%%PACKAGE%%.html_node.tar.gz">HTML compressed
+ (%%HTML_NODE_TGZ_SIZE%%K gzipped tar file)</a> -
+ with one web page per node.</li>
+%%IF HTML_SECTION%%
+ <li><a href="%%PACKAGE%%.html_section.tar.gz">HTML compressed
+ (%%HTML_SECTION_TGZ_SIZE%%K gzipped tar file)</a> -
+ with one web page per section.</li>
+%%ENDIF HTML_SECTION%%
+%%IF HTML_CHAPTER%%
+ <li><a href="%%PACKAGE%%.html_chapter.tar.gz">HTML compressed
+ (%%HTML_CHAPTER_TGZ_SIZE%%K gzipped tar file)</a> -
+ with one web page per chapter.</li>
+%%ENDIF HTML_CHAPTER%%
+ <li><a href="%%PACKAGE%%.info.tar.gz">Info document
+ (%%INFO_TGZ_SIZE%%K characters gzipped tar file)</a>.</li>
+ <li><a href="%%PACKAGE%%.txt">ASCII text
+ (%%ASCII_SIZE%%K characters)</a>.</li>
+ <li><a href="%%PACKAGE%%.txt.gz">ASCII text compressed
+ (%%ASCII_GZ_SIZE%%K gzipped characters)</a>.</li>
+ <li><a href="%%PACKAGE%%.dvi.gz">TeX dvi file
+ (%%DVI_GZ_SIZE%%K characters gzipped)</a>.</li>
+ <li><a href="%%PACKAGE%%.ps.gz">PostScript file
+ (%%PS_GZ_SIZE%%K characters gzipped)</a>.</li>
+ <li><a href="%%PACKAGE%%.pdf">PDF file
+ (%%PDF_SIZE%%K characters)</a>.</li>
+ <li><a href="%%PACKAGE%%.texi.tar.gz">Texinfo source
+ (%%TEXI_TGZ_SIZE%%K characters gzipped tar file)</a></li>
+</ul>
+
+<p>(This page generated by the <a
+href="%%SCRIPTURL%%">%%SCRIPTNAME%%</a> script.)
+</p>
+
+<p>
+<a href="http://validator.w3.org/check?uri=referer"><img
+ src="http://www.w3.org/Icons/valid-xhtml10"
+ alt="Valid XHTML 1.0!" height="31" width="88" /></a>
+</p>
+
+<div class="copyright">
+<p>
+Return to <a href="http://gray.gnu.org.ua">Sergey Poznyakoff home page</a>.
+</p>
+<p>
+Return to the <a href="http://puszcza.gnu.org.ua">Puszcza home page</a>.
+</p>
+
+<p>
+Please send broken links and other corrections (or suggestions) to
+<a href="mailto:webmaster@gnu.org.ua"><em>webmaster at gnu dot org dot ua</em></a>.
+</p>
+
+<p>
+Copyright (C) 2005 Sergey Poznyakoff
+<br />
+Verbatim copying and distribution of this entire article is
+permitted in any medium, provided this notice is preserved.
+</p>
+
+<p>
+Updated:
+<!-- timestamp start -->
+$Date$ $Author$
+<!-- timestamp end -->
+</p>
+</div>
+
+</body>
+</html>
diff --git a/doc/macros.texi b/doc/macros.texi
new file mode 100644
index 0000000..a26ad0c
--- /dev/null
+++ b/doc/macros.texi
@@ -0,0 +1,31 @@
+@macro xopindex{option,text}
+@opindex \option\, --\option\ @r{option, \text\}
+@end macro
+
+@macro opsummary{option}
+@ifclear ANCHOR--\option\
+@set ANCHOR--\option\ 1
+@anchor{--\option\}
+@end ifclear
+@xopindex{\option\, summary}
+@end macro
+
+@macro xprindex{option}
+@ifclear ANCHOR-PR-\option\
+@set ANCHOR-PR-\option\ 1
+@anchor{pragma \option\}
+@end ifclear
+@end macro
+
+@macro example-output{text}
+@smallexample
+\text\
+@end smallexample
+@end macro
+
+@macro mtasimopt{option,text}
+@mtindex \option\, --\option\, @r{@command{mtasim} option, \text\}
+@end macro
+
+
+
diff --git a/doc/mastermenu.el b/doc/mastermenu.el
new file mode 100644
index 0000000..3ab3341
--- /dev/null
+++ b/doc/mastermenu.el
@@ -0,0 +1,90 @@
+;;; mastermenu.el --- Redefinition of texinfo-master-menu-list
+
+;; Copyright (C) 2006, 2007 Free Software Foundation, Inc.
+
+;; Author: Sergey Poznyakoff
+;; Maintainer: bug-tar@gnu.org
+;; Keywords: maint, tex, docs
+
+;; This file is part of GNU tar documentation suite
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file redefines texinfo-master-menu-list so that it takes into
+;; account included files.
+
+;; Known bugs: @menu without previous sectioning command will inherit
+;; documentation string from the previous menu. However, since such a
+;; menu is illegal in a texinfo file, we can live with it.
+
+(require 'texinfo)
+(require 'texnfo-upd)
+
+(defun texinfo-master-menu-list-recursive (title)
+ "Auxiliary function used by `texinfo-master-menu-list'."
+ (save-excursion
+ (let (master-menu-list)
+ (while (re-search-forward "\\(^@menu\\|^@include\\)" nil t)
+ (cond
+ ((string= (match-string 0) "@include")
+ (skip-chars-forward " \t")
+ (let ((included-name (let ((start (point)))
+ (end-of-line)
+ (skip-chars-backward " \t")
+ (buffer-substring start (point)))))
+ (end-of-line)
+ (let ((prev-title (texinfo-copy-menu-title)))
+ (save-excursion
+ (set-buffer (find-file-noselect included-name))
+ (setq master-menu-list
+ (append (texinfo-master-menu-list-recursive prev-title)
+ master-menu-list))))))
+ (t
+ (setq master-menu-list
+ (cons (list
+ (texinfo-copy-menu)
+ (let ((menu-title (texinfo-copy-menu-title)))
+ (if (string= menu-title "")
+ title
+ menu-title)))
+ master-menu-list)))))
+ master-menu-list)))
+
+(defun texinfo-master-menu-list ()
+ "Return a list of menu entries and header lines for the master menu,
+recursing into included files.
+
+Start with the menu for chapters and indices and then find each
+following menu and the title of the node preceding that menu.
+
+The master menu list has this form:
+
+ \(\(\(... \"entry-1-2\" \"entry-1\"\) \"title-1\"\)
+ \(\(... \"entry-2-2\" \"entry-2-1\"\) \"title-2\"\)
+ ...\)
+
+However, there does not need to be a title field."
+
+ (reverse (texinfo-master-menu-list-recursive "")))
+
+(defun make-master-menu ()
+ "Create master menu in the first Emacs argument."
+ (find-file (car command-line-args-left))
+ (texinfo-master-menu nil)
+ (save-buffer))
+
+
+;;; mastermenu.el ends here
diff --git a/doc/rendition.texi b/doc/rendition.texi
new file mode 100644
index 0000000..016583d
--- /dev/null
+++ b/doc/rendition.texi
@@ -0,0 +1,85 @@
+@c Let's use the concept of 'renditions' by Fra@,{c}ois Pinard
+@c I extended it by adding a FIXME_FOOTNOTE variable, which controls
+@c whether FIXME information should be placed in footnotes or
+@c inlined.
+
+@c ======================================================================
+@c This document has three levels of rendition: PUBLISH, DISTRIB or PROOF,
+@c as decided by @set symbols. The PUBLISH rendition does not show
+@c notes or marks asking for revision. Most users will prefer having more
+@c information, even if this information is not fully revised for adequacy,
+@c so DISTRIB is the default for distributions. The PROOF rendition
+@c show all marks to the point of ugliness, but is nevertheless useful to
+@c those working on the manual itself.
+@c ======================================================================
+
+@c Set this symbol if you wish FIXMEs to appear in footnotes, instead
+@c of being inserted into the text.
+@c @set PROOF_FOOTNOTED
+
+@ifclear PUBLISH
+@ifclear DISTRIB
+@ifclear PROOF
+@set DISTRIB
+@end ifclear
+@end ifclear
+@end ifclear
+
+@ifset PUBLISH
+@set RENDITION The book, version
+@end ifset
+
+@ifset DISTRIB
+@set RENDITION FTP release, version
+@end ifset
+
+@ifset PROOF
+@set RENDITION Proof reading version
+@end ifset
+
+@c Output marks for nodes needing revision, but not in PUBLISH rendition.
+
+@macro UNREVISED
+@ifclear PUBLISH
+@quotation
+@emph{(This message will disappear, once this node revised.)}
+@end quotation
+@end ifclear
+@end macro
+
+@c Output various FIXME information only in PROOF rendition.
+
+@macro FIXME{string}
+@ifset PROOF
+@ifset PROOF_FOOTNOTED
+@footnote{@strong{FIXME:} \string\}
+@end ifset
+@ifclear PROOF_FOOTNOTED
+@cartouche
+@strong{<FIXME>} \string\ @strong{</>}
+@end cartouche
+@end ifclear
+@end ifset
+
+@end macro
+
+@macro FIXME-ref{string}
+@ifset PROOF
+@strong{<REF>} \string\ @strong{</>}
+@end ifset
+
+@end macro
+
+@macro FIXME-pxref{string}
+@ifset PROOF
+@strong{<PXREF>} \string\ @strong{</>}
+@end ifset
+
+@end macro
+
+@macro FIXME-xref{string}
+@ifset PROOF
+@strong{<XREF>} \string\ @strong{</>}
+@end ifset
+
+@end macro
diff --git a/doc/untabify.el b/doc/untabify.el
new file mode 100644
index 0000000..77dd5c0
--- /dev/null
+++ b/doc/untabify.el
@@ -0,0 +1,13 @@
+;;;; Untabify the sources.
+;;;; Usage: emacs -batch -l untabify.el [file ...]
+
+(defun global-untabify (buflist)
+ (mapcar
+ (lambda (bufname)
+ (set-buffer (find-file bufname))
+ (untabify (point-min) (point-max))
+ (save-buffer)
+ (kill-buffer (current-buffer)))
+ buflist))
+
+(global-untabify command-line-args-left)
diff --git a/pies/Makefile.am b/pies/Makefile.am
new file mode 100644
index 0000000..2c65baf
--- /dev/null
+++ b/pies/Makefile.am
@@ -0,0 +1,39 @@
+# This file is part of mailfrom filter.
+# Copyright (C) 2008 Sergey Poznyakoff
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+sbin_PROGRAMS = pies
+
+pies_SOURCES = depmap.c pies.c progman.c pies.h
+EXTRA_DIST = pies.rcin
+noinst_DATA = pies.rc
+DISTCLEANFILES = pies.rc
+.rcin.rc:
+ sed 's|SBINDIR|$(sbindir)|g' $< > $@
+
+INCLUDES = \
+ $(MAILUTILS_INCLUDES)\
+ $(MU_COMMON_INCLUDES)\
+ -I$(top_srcdir)/lib\
+ -I$(top_srcdir)/gnu\
+ -I../gnu
+
+LDADD = \
+ ../lib/libmf.a\
+ $(MAILUTILS_LIBS)\
+ ../gnu/libgnu.a
+
+AM_CPPFLAGS=-DSYSCONFDIR=\"$(sysconfdir)\"\
+ -DSTATEDIR=\"$(localstatedir)\"
diff --git a/pies/depmap.c b/pies/depmap.c
new file mode 100644
index 0000000..76b492f
--- /dev/null
+++ b/pies/depmap.c
@@ -0,0 +1,170 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+
+#ifndef CHAR_BIT
+# define CHAR_BIT 8
+#endif
+#define BITS_PER_WORD (sizeof(unsigned)*CHAR_BIT)
+#define MAXTABLE 32767
+
+#define WORDSIZE(n) (((n) + BITS_PER_WORD - 1) / BITS_PER_WORD)
+#define SETBIT(x, i) ((x)[(i)/BITS_PER_WORD] |= (1<<((i) % BITS_PER_WORD)))
+#define RESETBIT(x, i) ((x)[(i)/BITS_PER_WORD] &= ~(1<<((i) % BITS_PER_WORD)))
+#define BITISSET(x, i) (((x)[(i)/BITS_PER_WORD] & (1<<((i) % BITS_PER_WORD))) != 0)
+
+void
+TC (unsigned *R, int n)
+{
+ register int rowsize;
+ register unsigned mask;
+ register unsigned *rowj;
+ register unsigned *rp;
+ register unsigned *rend;
+ register unsigned *ccol;
+
+ unsigned *relend;
+ unsigned *cword;
+ unsigned *rowi;
+
+ rowsize = WORDSIZE (n) * sizeof (unsigned);
+ relend = (unsigned *) ((char *) R + (n * rowsize));
+
+ cword = R;
+ mask = 1;
+ rowi = R;
+ while (rowi < relend)
+ {
+ ccol = cword;
+ rowj = R;
+
+ while (rowj < relend)
+ {
+ if (*ccol & mask)
+ {
+ rp = rowi;
+ rend = (unsigned *) ((char *) rowj + rowsize);
+
+ while (rowj < rend)
+ *rowj++ |= *rp++;
+ }
+ else
+ {
+ rowj = (unsigned *) ((char *) rowj + rowsize);
+ }
+
+ ccol = (unsigned *) ((char *) ccol + rowsize);
+ }
+
+ mask <<= 1;
+ if (mask == 0)
+ {
+ mask = 1;
+ cword++;
+ }
+ rowi = (unsigned *) ((char *) rowi + rowsize);
+ }
+}
+
+struct pies_depmap
+{
+ unsigned nrows;
+ unsigned rowlen;
+ unsigned r[1];
+};
+
+pies_depmap_t
+depmap_alloc (unsigned count)
+{
+ size_t size = (count + BITS_PER_WORD - 1) / BITS_PER_WORD;
+ pies_depmap_t dmap = xzalloc (sizeof (*dmap) - 1
+ + count * size * sizeof (unsigned));
+ dmap->nrows = count;
+ dmap->rowlen = size;
+ return dmap;
+}
+
+pies_depmap_t
+depmap_copy (pies_depmap_t dpm)
+{
+ pies_depmap_t copy = depmap_alloc (dpm->nrows);
+ memcpy (copy->r, dpm->r, dpm->nrows * dpm->rowlen * sizeof (unsigned));
+ return copy;
+}
+
+static unsigned *
+depmap_rowptr (pies_depmap_t dmap, unsigned row)
+{
+ return dmap->r + dmap->rowlen * row;
+}
+
+void
+depmap_set (pies_depmap_t dmap, unsigned row, unsigned col)
+{
+ unsigned *rptr = depmap_rowptr (dmap, row);
+ SETBIT (rptr, col);
+}
+
+int
+depmap_isset (pies_depmap_t dmap, unsigned row, unsigned col)
+{
+ unsigned *rptr = depmap_rowptr (dmap, row);
+ return BITISSET (rptr, col);
+}
+
+void
+depmap_tc (pies_depmap_t dmap)
+{
+ TC (dmap->r, dmap->nrows);
+}
+
+struct pies_depmap_pos
+{
+ enum pies_depmap_direction dir;
+ unsigned coord[2];
+};
+
+unsigned
+depmap_next (pies_depmap_t dmap, pies_depmap_pos_t pos)
+{
+ for (pos->coord[pos->dir]++; pos->coord[pos->dir] < dmap->nrows;
+ pos->coord[pos->dir]++)
+ if (depmap_isset (dmap, pos->coord[0], pos->coord[1]))
+ break;
+
+ return pos->coord[pos->dir] == dmap->nrows ?
+ (unsigned) -1 : pos->coord[pos->dir];
+}
+
+unsigned
+depmap_first (pies_depmap_t dmap, enum pies_depmap_direction dir,
+ unsigned coord, pies_depmap_pos_t *ppos)
+{
+ pies_depmap_pos_t pos = xmalloc (sizeof *pos);
+ *ppos = pos;
+ pos->dir = dir;
+ pos->coord[!pos->dir] = coord;
+ pos->coord[pos->dir] = -1;
+ return depmap_next (dmap, pos);
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */
diff --git a/pies/pies.c b/pies/pies.c
new file mode 100644
index 0000000..bdca2a4
--- /dev/null
+++ b/pies/pies.c
@@ -0,0 +1,1010 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+
+int log_to_stderr; /* Use stderr for logging */
+char *log_tag; /* override mu_log_tag */
+mu_log_level_t debug_level;
+mu_debug_t pmult_debug;
+struct pies_privs_data pies_user;
+int foreground;
+int command;
+char *pidfile = STATEDIR "/pies.pid";
+char *ctlfile = STATEDIR "/pies.ctl";
+char *statfile = STATEDIR "/pies.stat";
+mode_t pies_umask = 0;
+unsigned long shutdown_timeout = 5;
+
+
+/* Logging */
+void
+log_setup (int want_stderr)
+{
+ mu_debug_t debug;
+
+ mu_diag_get_debug (&debug);
+
+ if (log_tag)
+ mu_log_tag = log_tag;
+
+ if (!want_stderr)
+ {
+ openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility);
+ mu_debug_set_print (debug, mu_diag_syslog_printer, NULL);
+ mu_debug_default_printer = mu_debug_syslog_printer;
+ }
+ else
+ mu_debug_default_printer = mu_debug_stderr_printer;
+}
+
+static int
+stderr_closed_p()
+{
+ int fd = dup (0);
+ if (fd < 0)
+ return 1;
+ close (fd);
+ return fd <= 2;
+}
+
+
+static int
+_cb_action (mu_debug_t debug, void *data, char *arg)
+{
+ enum return_action *pact = data;
+ static struct mu_kwd actab[] = {
+ { "disable", action_disable },
+ { "restart", action_restart },
+ { NULL }
+ };
+ int res;
+
+ if (mu_kwd_xlat_name (actab, arg, &res))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("unknown action code: %s"), arg);
+ return 0;
+ }
+ *pact = res;
+ return 0;
+}
+
+static int
+_cb_notify_addr (mu_debug_t debug, void *data, char *arg)
+{
+ mu_address_t *paddr = data;
+ mu_address_t addr;
+ int rc;
+
+ rc = mu_address_create (&addr, arg);
+ if (rc)
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("%s: invalid e-mail address: %s"),
+ arg, mu_strerror (rc));
+ if (*paddr)
+ {
+ mu_address_union (paddr, addr);
+ mu_address_destroy (&addr);
+ }
+ else
+ *paddr = addr;
+ return 0;
+}
+
+struct mu_cfg_param return_code_cfg_param[] = {
+ { "action", mu_cfg_callback, NULL,
+ mu_offsetof (struct action, act), _cb_action,
+ N_("Specifies action to take when a component finishes with this "
+ "return code."),
+ N_("arg: {disable | restart}") },
+ { "notify", mu_cfg_callback, NULL,
+ mu_offsetof (struct action, addr), _cb_notify_addr,
+ N_("Notify this address when then component terminates."),
+ N_("arg: email-list") },
+ { "message", mu_cfg_string, NULL,
+ mu_offsetof (struct action, message), NULL,
+ N_("Notification message text (with headers).") },
+ { NULL }
+};
+
+static struct mu_kwd ex_kwtab[] = {
+#define S(s) { #s, s }
+ S (EX_OK),
+ S (EX_USAGE),
+ S (EX_DATAERR),
+ S (EX_NOINPUT),
+ S (EX_NOUSER),
+ S (EX_NOHOST),
+ S (EX_UNAVAILABLE),
+ S (EX_SOFTWARE),
+ S (EX_OSERR),
+ S (EX_OSFILE),
+ S (EX_CANTCREAT),
+ S (EX_IOERR),
+ S (EX_TEMPFAIL),
+ S (EX_PROTOCOL),
+ S (EX_NOPERM),
+ S (EX_CONFIG),
+ { NULL }
+};
+
+static int
+return_code_section_parser (enum mu_cfg_section_stage stage,
+ const mu_cfg_node_t *node,
+ const char *section_label, void **section_data,
+ void *call_data,
+ mu_cfg_tree_t *tree)
+{
+ struct component *comp = *section_data;
+ struct action *act;
+ int argc, i;
+ char **argv;
+ int *retv;
+ int retc = 0;
+ int allflag = 0;
+ int rc;
+
+ switch (stage)
+ {
+ case mu_cfg_section_start:
+ rc = mu_argcv_get (node->tag_label, NULL, NULL, &argc, &argv);
+ if (rc)
+ {
+ mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR,
+ _("cannot parse return codes: %s"),
+ mu_strerror (rc));
+ return 1;
+ }
+ retv = xcalloc (argc, sizeof *retv);
+ if (argc == 0 || (argc == 1 && strcmp (argv[0], "*") == 0))
+ allflag = 1;
+ else
+ {
+ for (i = 0; i < argc; i++)
+ {
+ int n;
+
+ if (isdigit (argv[i][0]))
+ {
+ char *p;
+ n = strtoul (argv[i], &p, 0);
+ if (*p)
+ {
+ mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR,
+ _("%s: not a number"), p);
+ continue;
+ }
+ }
+ else if (mu_kwd_xlat_name_ci (ex_kwtab, argv[i], &n))
+ {
+ mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR,
+ _("%s: not a return code"), argv[i]);
+ continue;
+ }
+
+ if (n > MAX_RETURN_CODE)
+ mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR,
+ _("%s: invalid return code"), argv[i]);
+ else
+ {
+ /* Alles in ordnung */
+ retv[retc++] = n;
+ }
+ }
+ }
+
+ mu_argcv_free (argc, argv);
+ if (retc == 0 && !allflag)
+ {
+ free (retv);
+ return 1;
+ }
+
+ act = xzalloc (sizeof *act);
+ if (allflag)
+ {
+ for (i = 0; i <= MAX_RETURN_CODE; i++)
+ if (comp->act[i] == NULL)
+ comp->act[i] = act;
+ }
+ else
+ for (i = 0; i < retc; i++)
+ comp->act[retv[i]] = act;
+ *section_data = act;
+ break;
+
+ case mu_cfg_section_end:
+ break;
+ }
+ return 0;
+}
+
+void
+return_code_cfg_init ()
+{
+ struct mu_cfg_section *section;
+
+ if (mu_create_canned_section ("return-code", &section))
+ exit (EX_SOFTWARE);
+ section->parser = return_code_section_parser;
+ section->label = N_("<tag: exit-code-list>");
+ mu_cfg_section_add_params (section, return_code_cfg_param);
+}
+
+
+static int
+_cb_group (mu_debug_t debug, void *data, char *arg)
+{
+ int argc, i;
+ char **argv;
+ mu_list_t *plist = data, list;
+ int rc;
+
+ rc = mu_argcv_get_np (arg, strlen (arg), ",", NULL, 0, &argc, &argv, NULL);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+ if (*plist)
+ list = *plist;
+ else
+ {
+ mu_list_create (&list);
+ *plist = list;
+ }
+ for (i = 0; i < argc; i++)
+ {
+ struct group *group = getgrnam (argv[i]);
+ if (!group)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Unknown group: %s"),
+ argv[i]);
+ continue;
+ }
+ mu_list_append (list, (void*)group->gr_gid);
+ }
+ mu_argcv_free (argc, argv);
+ return 0;
+}
+
+
+static int
+_cb_command (mu_debug_t debug, void *data, char *arg)
+{
+ int argc;
+ char **argv, ***pargv = data;
+ int rc;
+
+ rc = mu_argcv_get (arg, "", NULL, &argc, &argv);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+ *pargv = argv;
+ return 0;
+}
+
+static int
+_cb_depend (mu_debug_t debug, void *data, char *arg)
+{
+ int argc;
+ char **argv, ***pargv = data;
+ int rc;
+
+ rc = mu_argcv_get_np (arg, strlen (arg), ",", NULL, 0, &argc, &argv, NULL);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+ *pargv = argv;
+ return 0;
+}
+
+static int
+_cb_umask (mu_debug_t debug, void *data, char *arg)
+{
+ mode_t *pmode = data;
+ char *p;
+ unsigned long n = strtoul (arg, &p, 8);
+ if (*p)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("Invalid octal number"));
+ return 1;
+ }
+ *pmode = n;
+ return 0;
+}
+
+static int
+_cb_env (mu_debug_t debug, void *data, char *arg)
+{
+ int rc;
+ int argc;
+ char **argv;
+ char ***penv = data;
+
+ rc = mu_argcv_get_np (arg, strlen (arg), "", NULL, 0, &argc, &argv, NULL);
+ if (rc)
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ "mu_argcv_get: %s", mu_strerror (rc));
+ return 1;
+ }
+ *penv = argv;
+ return 0;
+}
+
+static int
+_cb_retr (mu_debug_t debug, void *data, char *arg)
+{
+ if (mu_string_to_syslog_priority (arg, data))
+ {
+ mu_cfg_format_error (debug, MU_DEBUG_ERROR,
+ _("Unknown syslog facility `%s'"),
+ arg);
+ return 1;
+ }
+ return 0;
+}
+
+struct mu_cfg_param component_cfg_param[] = {
+ { "command", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, argv), _cb_command,
+ N_("Command line.") },
+ { "depend", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, depend), _cb_depend,
+ N_("List of dependenices (whitespace separated)"),
+ N_("list") },
+ { "disable", mu_cfg_bool, NULL,
+ mu_offsetof (struct component, disabled), NULL,
+ N_("Disable this entry.") },
+ { "remove-file", mu_cfg_string, NULL,
+ mu_offsetof (struct component, rmfile), NULL,
+ N_("Remove file before starting the component.")
+ N_("file") },
+ { "stdout", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, retr[RETR_OUT]), _cb_retr,
+ N_("Redirect program's standard output to the given syslog priority."),
+ N_("prio: {emerg | alert | crit | err | warning | notice | info | debug}")
+ },
+ { "stderr", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, retr[RETR_ERR]), _cb_retr,
+ N_("Redirect program's standard output to the given syslog priority."),
+ N_("prio: {emerg | alert | crit | err | warning | notice | info | debug}")
+ },
+ { "user", mu_cfg_string, NULL,
+ mu_offsetof (struct component, privs.user), NULL,
+ N_("Run with this user privileges.") },
+ { "group", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, privs.groups), _cb_group,
+ N_("Retain supplementary group.") },
+ { "umask", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, umask), _cb_umask,
+ N_("Force this umask."),
+ N_("arg: number") },
+ { "env", mu_cfg_callback, NULL,
+ mu_offsetof (struct component, env), _cb_env,
+ N_("Set program environment. Argument is a list of assignments "
+ "separated by white space."),
+ N_("arg: list") },
+ { "chdir", mu_cfg_string, NULL,
+ mu_offsetof (struct component, dir), NULL,
+ N_("Change to this directory before executing the component."),
+ N_("dir") },
+ { "return-code", mu_cfg_section },
+ { NULL }
+};
+
+static int
+component_section_parser (enum mu_cfg_section_stage stage,
+ const mu_cfg_node_t *node,
+ const char *section_label, void **section_data,
+ void *call_data,
+ mu_cfg_tree_t *tree)
+{
+ static struct component comp;
+ switch (stage)
+ {
+ case mu_cfg_section_start:
+ memset (&comp, 0, sizeof comp);
+ comp.retr[RETR_OUT] = comp.retr[RETR_ERR] = -1;
+ comp.tag = node->tag_label ? xstrdup (node->tag_label) : NULL;
+ *section_data = &comp;
+ break;
+
+ case mu_cfg_section_end:
+ register_prog (&comp);
+ }
+ return 0;
+}
+
+void
+component_cfg_init ()
+{
+ struct mu_cfg_section *section;
+
+ if (mu_create_canned_section ("component", &section))
+ exit (EX_SOFTWARE);
+ section->parser = component_section_parser;
+ section->label = N_("<tag: string>");
+ mu_cfg_section_add_params (section, component_cfg_param);
+}
+
+
+
+struct component default_component;
+
+static int
+_cb_debug (mu_debug_t debug, void *data, char *arg)
+{
+ int rc;
+
+ rc = mu_debug_level_from_string (arg, &debug_level, debug);
+ if (rc)
+ return 0;
+ if (!pmult_debug)
+ mu_debug_create (&pmult_debug, NULL);
+ mu_debug_set_level (pmult_debug, debug_level);
+ return 0;
+}
+
+struct mu_cfg_param pies_cfg_param[] = {
+ { "component", mu_cfg_section },
+ { "debug", mu_cfg_callback, NULL, 0, _cb_debug,
+ N_("Set debug verbosity level.") },
+ { "pidfile", mu_cfg_string, &pidfile, 0, NULL,
+ N_("Write PID to this file.") },
+ { "control-file", mu_cfg_string, &ctlfile, 0, NULL,
+ N_("Set location of the control file.") },
+ { "stat-file", mu_cfg_string, &statfile, 0, NULL,
+ N_("Set location of the statistics output file.") },
+ { "user", mu_cfg_string, &pies_user.user, 0, NULL,
+ N_("Run with this user privileges.") },
+ { "group", mu_cfg_callback, &pies_user.groups, 0, _cb_group,
+ N_("Retain supplementary group.") },
+ { "umask", mu_cfg_callback, &pies_umask, 0, _cb_umask,
+ N_("Force this umask."),
+ N_("arg: number") },
+ { "shutdown-timeout", mu_cfg_uint, &shutdown_timeout, 0, NULL,
+ N_("Wait <n> seconds for all children to shut down."),
+ "n" },
+ { "return-code", mu_cfg_section, &default_component },
+ { NULL }
+};
+
+
+const char *program_version = "pies (" PACKAGE_STRING ")";
+const char *package_bugreport = "<" PACKAGE_BUGREPORT ">";
+static char doc[] = N_("pies -- process inspector and execution supervisor");
+static char args_doc[] = "";
+
+static const char *capa[] = {
+ "common",
+ "logging",
+ "mailer",
+ "debug",
+ NULL
+};
+
+enum {
+ OPT_FOREGROUND=256,
+ OPTION_LOG_TAG,
+ OPT_SYSLOG,
+ OPT_STDERR
+};
+
+#define OPT_RESTART 'R'
+#define OPT_RELOAD 'r'
+#define OPT_STATUS 's'
+#define OPT_STOP 'S'
+
+static struct argp_option options[] = {
+#define GRP 0
+ { "foreground", OPT_FOREGROUND, 0, 0, N_("Remain in foreground."), GRP+1},
+ { "stderr", OPT_STDERR, NULL, 0, N_("Log to stderr"), },
+ { "syslog", OPT_SYSLOG, NULL, 0, N_("Log to syslog"), },
+ { "log-tag", OPTION_LOG_TAG, N_("STRING"), 0,
+ N_("Set the identifier used in syslog messages to STRING"), GRP+1 },
+ { "debug", 'x', N_("LEVEL"), 0,
+ N_("Set debug verbosity level."), GRP+1 },
+#undef GRP
+
+#define GRP 10
+ { "restart-component", OPT_RESTART, NULL, 0,
+ N_("Restart components named in the command line."), GRP+1 },
+ { "reload", OPT_RELOAD, NULL, 0,
+ N_("Reload the running instance of pies."), GRP+1 },
+ { "hup", 0, NULL, OPTION_ALIAS },
+ { "status", OPT_STATUS, NULL, 0,
+ N_("Display info about the running instance."), GRP+1 },
+ { "stop", OPT_STOP, NULL, 0,
+ N_("Stop the running instance."), GRP+1 },
+#undef GRP
+ { NULL }
+};
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ static struct mu_argp_node_list lst;
+
+ switch (key)
+ {
+ case OPT_FOREGROUND:
+ foreground = 1;
+ break;
+
+ case OPT_RELOAD:
+ case OPT_STATUS:
+ case OPT_STOP:
+ case OPT_RESTART:
+ log_to_stderr = 1;
+ command = key;
+ break;
+
+ case OPT_SYSLOG:
+ log_to_stderr = 0;
+ break;
+
+ case OPT_STDERR:
+ log_to_stderr = 1;
+ break;
+
+ case 'x':
+ mu_argp_node_list_new (&lst, "debug", arg);
+ break;
+
+ case OPTION_LOG_TAG:
+ log_tag = arg;
+ break;
+
+ case ARGP_KEY_INIT:
+ mu_argp_node_list_init (&lst);
+ break;
+
+ case ARGP_KEY_FINI:
+ mu_argp_node_list_finish (&lst, NULL, NULL);
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+static struct argp argp = {
+ options,
+ parse_opt,
+ args_doc,
+ doc,
+ NULL,
+ NULL,
+ NULL
+};
+
+
+static void
+version (FILE *stream, struct argp_state *state)
+{
+ mailfromd_version ("pies", stream);
+}
+
+
+void
+priv_setup (struct pies_privs_data *pr)
+{
+ if (pr->user)
+ {
+ struct passwd *pw = getpwnam (pr->user);
+ if (!pw)
+ {
+ mu_error (_("No such user: %s"), pr->user);
+ exit (EX_CONFIG);
+ }
+ if (pw && switch_to_privs (pw->pw_uid, pw->pw_gid, pr->groups))
+ exit (EX_SOFTWARE);
+ }
+}
+
+
+#define ACTION_CONT 0
+#define ACTION_STOP 1
+#define ACTION_RESTART 2
+#define ACTION_COMPRELOAD 3
+#define ACTION_DUMPSTATS 4
+
+int action = ACTION_CONT;
+int children_cleanup = 0;
+int got_alarm = 0;
+
+RETSIGTYPE
+sig_handler (int sig)
+{
+ switch (sig)
+ {
+ case SIGCHLD:
+ children_cleanup = 1;
+ break;
+
+ case SIGTERM:
+ case SIGINT:
+ case SIGQUIT:
+ action = ACTION_STOP;
+ mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig);
+ break;
+
+ case SIGHUP:
+ mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig);
+ action = ACTION_RESTART;
+ break;
+
+ case SIGALRM:
+ got_alarm = 1;
+ break;
+
+ case SIGUSR1:
+ action = ACTION_COMPRELOAD;
+ break;
+
+ case SIGUSR2:
+ action = ACTION_DUMPSTATS;
+ break;
+ }
+ signal (sig, sig_handler);
+}
+
+void
+signal_setup (RETSIGTYPE (*sf)(int))
+{
+ signal (SIGCHLD, sf);
+ signal (SIGTERM, sf);
+ signal (SIGQUIT, sf);
+ signal (SIGINT, sf);
+ signal (SIGHUP, sf);
+ signal (SIGALRM, sf);
+ signal (SIGUSR1, sf);
+ signal (SIGUSR2, sf);
+}
+
+
+pid_t
+pidfile_read (int must_exist)
+{
+ int c;
+ pid_t n = 0;
+ FILE *fp = fopen (pidfile, "r");
+ if (!fp)
+ {
+ if (must_exist && errno != ENOENT)
+ mu_diag_output (MU_DIAG_ERR,
+ _("cannot open pid file `%s': %s"),
+ pidfile,
+ mu_strerror (errno));
+ return -1;
+ }
+
+ while ((c = fgetc (fp)) != EOF)
+ {
+ if (isdigit (c))
+ n = n * 10 + c - '0';
+ else if (c == '\n')
+ break;
+ else
+ {
+ mu_diag_output (MU_DIAG_ERR,
+ _("unexpected character %#03o in pidfile `%s'"),
+ c, pidfile);
+ return -1;
+ }
+ }
+ fclose (fp);
+ if (kill (n, 0))
+ {
+ mu_diag_output (MU_DIAG_ERR,
+ _("cannot signal master process %lu: %s"),
+ (unsigned long) n, mu_strerror (errno));
+ if (errno == EPERM)
+ return n; /* be on the safe side */
+ return -1;
+ }
+ return n;
+}
+
+
+void
+stop_components ()
+{
+ FILE *fp;
+ size_t size = 0;
+ char *buf = NULL;
+
+ mu_diag_output (MU_DIAG_INFO, _("stopping components"));
+
+ fp = fopen (ctlfile, "r");
+ if (!fp)
+ {
+ mu_error (_("cannot open control file `%s': %s"),
+ ctlfile, mu_strerror (errno));
+ return;
+ }
+ if (unlink (ctlfile))
+ {
+ mu_error (_("cannot unlink control file `%s': %s"),
+ ctlfile, mu_strerror (errno));
+ fclose (fp);
+ return;
+ }
+
+ while (getline (&buf, &size, fp) > 0)
+ {
+ size_t len = strlen (buf);
+ if (len == 0)
+ continue;
+ if (buf[len-1] == '\n')
+ buf[len-1] = 0;
+ progman_stop_component (buf);
+ }
+
+ free (buf);
+ fclose (fp);
+}
+
+int
+request_restart_components (char **argv)
+{
+ FILE *fp;
+ pid_t pid = pidfile_read (1);
+
+ if (pid == -1)
+ return 1;
+
+ fp = fopen (ctlfile, "w");
+ if (!fp)
+ {
+ mu_error (_("cannot open control file `%s': %s"),
+ ctlfile, mu_strerror (errno));
+ return 1;
+ }
+ for (; *argv; argv++)
+ fprintf (fp, "%s\n", *argv);
+ fclose (fp);
+
+ kill (pid, SIGUSR1);
+ return 0;
+}
+
+
+int
+pies_reload ()
+{
+ pid_t pid = pidfile_read (1);
+
+ if (pid == -1)
+ {
+ mu_diag_output (MU_DIAG_CRIT, _("pies is not running"));
+ return 1;
+ }
+
+ mu_diag_output (MU_DIAG_INFO, _("reloading pies at PID %lu"),
+ (unsigned long) pid);
+ return kill (pid, SIGHUP) ? EX_SOFTWARE : 0;
+}
+
+int
+pies_status ()
+{
+ FILE *fp;
+ pid_t pid = pidfile_read (0);
+ int i;
+
+ if (pid == -1)
+ {
+ mu_diag_output (MU_DIAG_INFO, _("pies is not running"));
+ return EX_USAGE;
+ }
+
+ if (kill (pid, SIGUSR2))
+ {
+ mu_diag_output (MU_DIAG_INFO,
+ _("pies is not running, but a pidfile "
+ "is found (pid %lu)"),
+ (unsigned long) pid);
+ return EX_USAGE;
+ }
+
+ mu_diag_output (MU_DIAG_INFO,
+ _("pies is running; PID %lu"),
+ (unsigned long) pid);
+
+ for (i = 0; i < 4 && access (statfile, R_OK); i++)
+ sleep (1);
+
+ fp = fopen (statfile, "r");
+ if (!fp)
+ mu_diag_output (MU_DIAG_ERR, _("cannot open statfile `%s': %s"),
+ statfile, mu_strerror (errno));
+ else
+ {
+ char c;
+
+ if (unlink (statfile))
+ mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
+ statfile, mu_strerror (errno));
+ while ((c = fgetc (fp)) != EOF)
+ fputc (c, stdout);
+ fclose (fp);
+ }
+ return 0;
+}
+
+int
+pies_stop ()
+{
+ pid_t pid = pidfile_read (1);
+
+ if (pid == -1)
+ {
+ mu_diag_output (MU_DIAG_CRIT, _("pies is not running"));
+ return EX_USAGE;
+ }
+
+ mu_diag_output (MU_DIAG_INFO,
+ _("stopping pies at PID %lu"), (unsigned long) pid);
+ return kill (pid, SIGTERM) ? EX_SOFTWARE : 0;
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int rc;
+ int index;
+
+ mf_init_nls ();
+ if (!program_invocation_short_name)
+ program_invocation_short_name = argv[0];
+ argp_program_version_hook = version;
+ /* Set default logging */
+ log_setup (!stderr_closed_p ());
+ return_code_cfg_init ();
+ component_cfg_init ();
+ mu_argp_init (program_version, package_bugreport);
+ mu_register_all_mailer_formats ();
+ rc = mu_app_init (&argp, capa, pies_cfg_param, argc, argv, 0, &index, NULL);
+ if (rc)
+ exit (EX_CONFIG);
+
+ log_setup (log_to_stderr);
+
+ if (argc != index && command != 'R')
+ {
+ mu_error ("extra command line arguments");
+ exit (EX_CONFIG);
+ }
+
+ switch (command)
+ {
+ case OPT_RESTART:
+ priv_setup (&pies_user);
+ if (pies_umask)
+ umask (pies_umask);
+ exit (request_restart_components (argv + index));
+
+ case OPT_RELOAD:
+ exit (pies_reload ());
+
+ case OPT_STATUS:
+ exit (pies_status ());
+
+ case OPT_STOP:
+ exit (pies_stop ());
+
+ default:
+ priv_setup (&pies_user);
+ if (pies_umask)
+ umask (pies_umask);
+ }
+
+ mu_diag_output (MU_DIAG_INFO, _("%s starting"), program_version);
+
+ if (!foreground && daemon (0, 0) == -1)
+ {
+ mu_error (_("cannot become a daemon: %s"),
+ mu_strerror (errno));
+ exit (EX_SOFTWARE);
+ }
+
+ rc = mu_daemon_create_pidfile (pidfile);
+ if (rc)
+ mu_error (_("Cannot create PID file `%s': %s"),
+ pidfile, mu_strerror (rc));
+
+ if (argv[0][0] != '/')
+ mu_diag_output (MU_DIAG_NOTICE,
+ N_("not started as an absolute pathname; "
+ "SIGHUP will not work"));
+
+ signal_setup (sig_handler);
+
+ progman_start ();
+
+ while (action == ACTION_CONT)
+ {
+ if (!children_cleanup)
+ pause ();
+ if (children_cleanup)
+ {
+ children_cleanup = 0;
+ progman_cleanup (0);
+ }
+ if (got_alarm)
+ {
+ got_alarm = 0;
+ progman_wake_sleeping ();
+ }
+ switch (action)
+ {
+ case ACTION_COMPRELOAD:
+ stop_components ();
+ action = ACTION_CONT;
+ break;
+
+ case ACTION_DUMPSTATS:
+ progman_dump_stats (statfile);
+ action = ACTION_CONT;
+ break;
+ }
+ }
+ progman_stop ();
+
+ if (action == ACTION_RESTART && argv[0][0] == '/')
+ {
+ int i;
+
+ for (i = getmaxfd (); i > 0; i--)
+ close (i);
+
+ mu_daemon_remove_pidfile ();
+ signal_setup (SIG_DFL);
+
+ execv (argv[0], argv);
+ }
+
+ mu_diag_output (MU_DIAG_INFO, _("%s terminated"), program_version);
+ exit (EX_OK);
+}
+
+void
+xalloc_die ()
+{
+ mu_error ("not enough memory");
+ abort ();
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */
diff --git a/pies/pies.h b/pies/pies.h
new file mode 100644
index 0000000..8145f51
--- /dev/null
+++ b/pies/pies.h
@@ -0,0 +1,119 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <signal.h>
+#include <sysexits.h>
+#include <mailutils/mailutils.h>
+#include <mailutils/daemon.h>
+#include <mailutils/libargp.h>
+#include <mailutils/syslog.h>
+
+#include "xalloc.h"
+#include "libmf.h"
+
+#define RETR_OUT 0
+#define RETR_ERR 1
+
+#define TESTTIME 2*60
+#define SLEEPTIME 5*60
+#define MAXSPAWN 10
+
+struct pies_privs_data
+{
+ char *user;
+ mu_list_t groups;
+};
+
+#define MAX_RETURN_CODE 127
+
+enum return_action
+{
+ action_restart,
+ action_disable,
+};
+
+struct action
+{
+ enum return_action act; /* Action to take when the component terminates */
+ mu_address_t addr; /* Addresses to notify about it. */
+ char *message; /* Notification mail. */
+};
+
+struct component
+{
+ char *tag; /* Entry tag (for diagnostics purposes) */
+ char **argv; /* Program command line */
+ char **env; /* Program environment */
+ char *dir; /* Working directory */
+ char **depend; /* Dependencies */
+ int disabled;
+ char *rmfile;
+ int retr[2];
+ struct pies_privs_data privs;
+ mode_t umask;
+ struct action *act[MAX_RETURN_CODE+1];
+};
+
+void register_prog (struct component *comp);
+size_t progman_running_count (void);
+void progman_start (void);
+void progman_wake_sleeping (void);
+void progman_stop (void);
+void progman_cleanup (int expect_term);
+void progman_stop_component (const char *name);
+void progman_dump_stats (const char *filename);
+
+void log_setup (int want_stderr);
+void signal_setup (RETSIGTYPE (*sf)(int));
+void priv_setup (struct pies_privs_data *pr);
+
+typedef struct pies_depmap *pies_depmap_t;
+typedef struct pies_depmap_pos *pies_depmap_pos_t;
+enum pies_depmap_direction
+ {
+ depmap_row = 0,
+ depmap_col = !depmap_row
+ };
+
+pies_depmap_t depmap_alloc (unsigned count);
+pies_depmap_t depmap_copy (pies_depmap_t dpm);
+void depmap_set (pies_depmap_t dmap, unsigned row, unsigned col);
+int depmap_isset (pies_depmap_t dmap, unsigned row, unsigned col);
+void depmap_tc (pies_depmap_t dmap);
+unsigned depmap_first (pies_depmap_t dmap, enum pies_depmap_direction dir,
+ unsigned coord, pies_depmap_pos_t *ppos);
+unsigned depmap_next (pies_depmap_t dmap, pies_depmap_pos_t pos);
+
+extern char *syslog_tag;
+extern unsigned long shutdown_timeout;
+extern struct component default_component;
diff --git a/pies/pies.rcin b/pies/pies.rcin
new file mode 100644
index 0000000..d1ae197
--- /dev/null
+++ b/pies/pies.rcin
@@ -0,0 +1,25 @@
+# Sample pies configuration for running pmult.
+
+# Special handling for exit codes that mean the program was used incorrectly
+# or misconfigured.
+return-code EX_USAGE EX_CONFIG {
+ action disable;
+ notify root;
+ message
+ "From: <>\n"
+ "X-Agent: ${canonical-program-name} (${package} ${version})\n"
+ "Subject: Component ${component} disabled.\n"
+ "\n"
+ "Component \"${component}\" terminated with code ${retcode}, which means\n"
+ "some configuration problem.\n"
+ "It will not be restarted automatically. Please fix its configuration\n"
+ "and restart it at your earliest convenience.";
+};
+
+component pmult {
+ command "SBINDIR/pmult";
+ user meta1s;
+ stderr info;
+ stdout info;
+};
+
diff --git a/pies/progman.c b/pies/progman.c
new file mode 100644
index 0000000..d2bb6f0
--- /dev/null
+++ b/pies/progman.c
@@ -0,0 +1,971 @@
+/* This file is part of Mailfromd.
+ Copyright (C) 2007, 2008 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+/* FIXME */
+const char *mu_umaxtostr (unsigned slot, uintmax_t val);
+
+#define TYPE_COMPONENT 0
+#define TYPE_RETR 1
+
+enum prog_status
+ {
+ status_enabled, /* Component enabled. prog->pid!=0 shows if it is
+ actually running */
+ status_disabled, /* Component is disabled. */
+ status_sleeping, /* Component is sleeping. An attempt to start it will
+ be made at prog->v.p.timestamp + SLEEPTIME */
+ status_stopping, /* Component is being stopped */
+ };
+
+struct prog
+{
+ struct prog *next;
+ int type;
+ pid_t pid; /* PID */
+ char *tag; /* Entry tag (for diagnostics purposes) */
+ int idx; /* Numeric identifier */
+ char **depend;
+ union
+ {
+ struct
+ {
+ int argc;
+ char **argv; /* Command line arguments */
+ char **env; /* Environment */
+ char *dir; /* Working directory */
+ int retr[2];
+ char *rmfile; /* Rmfile location */
+ struct pies_privs_data privs;
+ mode_t umask;
+ time_t timestamp; /* Time of last startup */
+ size_t count; /* Number of failed starts since timestamp */
+ enum prog_status status; /* Current component status */
+ struct action *act[MAX_RETURN_CODE+1];
+ } p;
+
+ struct
+ {
+ struct prog *master;
+ } r;
+ } v;
+};
+
+#define IS_PROG(p) ((p)->type == TYPE_COMPONENT)
+
+static int numprog;
+static struct prog *proghead, *progtail;
+static pies_depmap_t depmap;
+
+static struct prog *
+prog_lookup_by_pid (pid_t pid)
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->pid == pid)
+ break;
+ return prog;
+}
+
+struct prog *
+prog_lookup_by_tag (const char *tag)
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (strcmp (prog->tag, tag) == 0)
+ break;
+ return prog;
+}
+
+struct prog *
+prog_lookup_by_idx (unsigned idx)
+{
+ struct prog *prog;
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->idx == idx)
+ break;
+ return prog;
+}
+
+static void prog_stop (struct prog *prog, int sig);
+void prog_start_dependencies (struct prog *prog);
+
+void
+link_prog (struct prog *pp, int prepend)
+{
+ if (prepend)
+ {
+ pp->next = proghead;
+ proghead = pp;
+ if (!progtail)
+ progtail = pp;
+ }
+ else
+ {
+ pp->next = NULL;
+ if (progtail)
+ progtail->next = pp;
+ else
+ proghead = pp;
+ progtail = pp;
+ }
+}
+
+static char *
+retr_tag (const char *tag, int type)
+{
+ static char *retrstr[2] = { "stdout", "stderr" };
+ size_t taglen = strlen (tag) + 1 + strlen (retrstr[type]);
+ char *retr_tag = xmalloc (taglen + 1);
+
+ strcpy (retr_tag, tag);
+ strcat (retr_tag, "/");
+ strcat (retr_tag, retrstr[type]);
+ return retr_tag;
+}
+
+void
+register_retr (int type, struct prog *master)
+{
+ char *tag = retr_tag (master->tag, type);
+ char *pstr;
+ struct prog *pp = xzalloc (sizeof (*pp) + strlen (tag) + 1 +
+ 2 * sizeof(char**));
+
+ pp->type = TYPE_RETR;
+ pstr = (char *) (pp + 1);
+ pp->tag = pstr;
+ strcpy (pp->tag, tag);
+ pstr += strlen (pp->tag) + 1;
+ free (tag);
+ pp->v.r.master = master;
+ pp->idx = numprog++;
+ pp->depend = (char**)pstr;
+ pp->depend[0] = master->tag;
+ pp->depend[1] = NULL;
+ link_prog (pp, 1);
+}
+
+void
+update_retr (int type, struct prog *master, pid_t pid)
+{
+ char *tag = retr_tag (master->tag, type);
+ struct prog *pp = prog_lookup_by_tag (tag);
+
+ free (tag);
+ prog_stop (pp, SIGKILL); /* Just in case */
+ pp->pid = pid;
+}
+
+void
+register_prog (struct component *comp)
+{
+ struct prog *p, *newp;
+ char *pstr;
+ int i;
+ size_t size = 0;
+ int dep_all = 0;
+ int depc = 0;
+
+ if (comp->depend)
+ {
+ if (comp->depend[0] && comp->depend[1] == NULL)
+ {
+ if (strcmp (comp->depend[0], "all") == 0)
+ {
+ dep_all = 1;
+ for (p = proghead; p; p = p->next)
+ if (p->type == TYPE_COMPONENT)
+ depc++;
+ }
+ else if (strcmp (comp->depend[0], "none") == 0)
+ comp->depend = NULL;
+ }
+
+ if (depc == 0)
+ for (depc = 0; comp->depend[depc]; depc++)
+ ;
+ }
+
+ if (depc)
+ size += sizeof (comp->depend[0]) * (depc + 1);
+
+ if (comp->dir)
+ size += strlen (comp->dir) + 1;
+
+ size += sizeof (*newp)
+ + (comp->tag ? (strlen (comp->tag) + 1) : 0);
+ + (comp->rmfile ? (strlen (comp->rmfile) + 1) : 0);
+
+ for (i = 0; comp->argv[i]; i++)
+ ;
+
+ newp = xzalloc (size);
+ newp->type = TYPE_COMPONENT;
+ newp->pid = 0;
+ newp->idx = numprog++;
+ newp->v.p.argc = i;
+ newp->v.p.argv = comp->argv;
+ newp->v.p.env = comp->env;
+ if (comp->disabled)
+ newp->v.p.status = status_disabled;
+ for (i = 0; i <= MAX_RETURN_CODE; i++)
+ newp->v.p.act[i] = comp->act[i] ? comp->act[i] : default_component.act[i];
+
+ pstr = (char*) (newp + 1);
+
+ if (comp->tag)
+ {
+ newp->tag = strcpy (pstr, comp->tag);
+ pstr += strlen (pstr) + 1;
+ }
+ else
+ newp->tag = newp->v.p.argv[0];
+
+ if (comp->dir)
+ {
+ newp->v.p.dir = strcpy (pstr, comp->dir);
+ pstr += strlen (pstr) + 1;
+ }
+ else
+ newp->v.p.dir = NULL;
+
+ if (comp->rmfile)
+ {
+ newp->v.p.rmfile = strcpy (pstr, comp->rmfile);
+ pstr += strlen (pstr) + 1;
+ }
+ else
+ newp->v.p.rmfile = NULL;
+
+ if (depc)
+ {
+ newp->depend = (char**) pstr;
+ pstr = (char*) (newp->depend + depc + 1);
+
+ depc = 0;
+ if (comp->depend)
+ {
+ if (dep_all)
+ {
+ for (p = proghead; p; p = p->next)
+ if (p->type == TYPE_COMPONENT)
+ newp->depend[depc++] = p->tag;
+ }
+ else
+ {
+ for (; comp->depend[depc]; depc++)
+ newp->depend[depc] = comp->depend[depc];
+ }
+ }
+ newp->depend[depc] = NULL;
+ }
+ else
+ newp->depend = NULL;
+
+ if ((newp->v.p.retr[0] = comp->retr[0]) != -1)
+ register_retr (0, newp);
+ if ((newp->v.p.retr[1] = comp->retr[1]) != -1)
+ register_retr (1, newp);
+
+ newp->v.p.privs = comp->privs;
+ newp->v.p.umask = comp->umask;
+
+ link_prog (newp, 0);
+}
+
+size_t
+progman_running_count ()
+{
+ size_t size = 0;
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->pid > 0)
+ size++;
+ return size;
+}
+
+RETSIGTYPE
+retr_exit (int sig)
+{
+ exit (0);
+}
+
+int
+open_retranslator (struct prog *master, int type)
+{
+ int p[2];
+ FILE *fp;
+ char *buf = NULL;
+ size_t size = 0;
+ pid_t pid;
+
+ if (master->v.p.retr[type] == -1)
+ return -1;
+ pipe (p);
+ switch (pid = fork ())
+ {
+ case 0:
+ /* Retranslator process */
+
+ log_setup (0);
+ signal_setup (retr_exit);
+
+ close (p[1]);
+ fp = fdopen (p[0], "r");
+ if (fp == NULL)
+ exit (1);
+
+ openlog (master->tag, LOG_PID, mu_log_facility);
+
+ while (getline (&buf, &size, fp) > 0)
+ syslog (master->v.p.retr[type], "%s", buf);
+ exit (0);
+
+ case -1:
+ mu_diag_output (MU_DIAG_CRIT,
+ _("cannot run retranslator `%s': fork failed: %s"),
+ master->tag, mu_strerror (errno));
+ return -1;
+
+ default:
+ update_retr (type, master, pid);
+ close (p[0]);
+ return p[1];
+ }
+}
+
+extern char **environ;
+
+static char *
+find_env (char *name)
+{
+ int nlen = strlen (name);
+ int i;
+
+ for (i = 0; environ[i]; i++)
+ {
+ char *p = strchr (environ[i], '=');
+ int elen = p - environ[i];
+ if (elen == nlen && memcmp (name, environ[i], nlen) == 0)
+ return environ[i];
+ }
+ return NULL;
+}
+
+static void
+env_setup (char **env)
+{
+ char **old_env = environ;
+ char **new_env;
+ int count, i, n;
+
+ if (!env)
+ return;
+
+ if (strcmp (env[0], "-") == 0)
+ {
+ old_env = NULL;
+ env++;
+ }
+
+ /* Count new environment size */
+ count = 0;
+ if (old_env)
+ for (; old_env[count]; count++)
+ ;
+
+ for (; env[count]; count++)
+ ;
+
+ /* Allocate the new environment. */
+ new_env = xcalloc (count + 1, sizeof new_env[0]);
+
+ /* Populate the environment. */
+ n = 0;
+
+ if (old_env)
+ for (i = 0; old_env[i]; i++)
+ new_env[n++] = old_env[i];
+
+ for (i = 0; env[i]; i++)
+ {
+ if (strchr (env[i], '='))
+ new_env[n++] = env[i];
+ else
+ {
+ char *p = find_env (env[i]);
+ if (p)
+ new_env[n++] = p;
+ }
+ }
+ new_env[n] = NULL;
+ /* FIXME: The old environment is not freed. Should it be? */
+ environ = new_env;
+}
+
+static void
+prog_start (struct prog *prog)
+{
+ int i;
+ pid_t pid;
+ time_t now;
+ int retr[2];
+
+ if (prog->pid > 0 || !IS_PROG (prog))
+ return;
+
+ time (&now);
+
+ if (prog->v.p.timestamp + TESTTIME > now)
+ prog->v.p.count++;
+ else
+ {
+ prog->v.p.count = 0;
+ prog->v.p.timestamp = now;
+ }
+
+ if (prog->v.p.count > MAXSPAWN)
+ {
+ int old_alarm;
+
+ mu_error (_("%s is respawning too fast, disabled for %d minutes"),
+ prog->tag, SLEEPTIME / 60);
+ prog->v.p.timestamp = now;
+ prog->v.p.status = status_disabled;
+
+ old_alarm = alarm (0);
+ if (old_alarm > SLEEPTIME || old_alarm <= 0)
+ old_alarm = SLEEPTIME;
+ alarm (old_alarm);
+ return;
+ }
+
+ prog_start_dependencies (prog);
+ if (prog->v.p.status == status_disabled)
+ return; /* FIXME: other statuses */
+ mu_diag_output (MU_DIAG_DEBUG, _("starting %s"), prog->tag);
+
+ if (prog->v.p.rmfile)
+ {
+ if (unlink (prog->v.p.rmfile) && errno != ENOENT)
+ mu_error (_("%s: cannot remove file `%s': %s"),
+ prog->tag, prog->v.p.rmfile, mu_strerror (errno));
+ }
+
+ retr[RETR_OUT] = open_retranslator (prog, RETR_OUT);
+ retr[RETR_ERR] = open_retranslator (prog, RETR_ERR);
+
+ switch (pid = fork ())
+ {
+ /* The child branch. */
+ case 0:
+ if (retr[RETR_OUT] == -1)
+ {
+ close (1);
+ open ("/dev/null", O_RDWR);
+ }
+ else if (retr[RETR_OUT] != 1)
+ {
+ close (1);
+ dup2 (retr[RETR_OUT], 1);
+ }
+
+ if (retr[RETR_ERR] == -1)
+ {
+ close (2);
+ open ("/dev/null", O_RDWR);
+ }
+ else if (retr[RETR_ERR] != 1)
+ {
+ close (2);
+ dup2 (retr[RETR_ERR], 2);
+ }
+
+ /* Close unneded descripitors */
+ for (i = getmaxfd (); i > 2; i--)
+ close (i);
+
+ signal_setup (SIG_DFL);
+ if (prog->v.p.dir)
+ {
+ if (chdir (prog->v.p.dir))
+ mu_error (_("%s: cannot change to directory %s: %s"),
+ prog->tag, prog->v.p.dir, mu_strerror (errno));
+ }
+
+ env_setup (prog->v.p.env);
+ priv_setup (&prog->v.p.privs);
+ if (prog->v.p.umask)
+ umask (prog->v.p.umask);
+
+ execvp (prog->v.p.argv[0], prog->v.p.argv);
+ openlog (MU_LOG_TAG(), LOG_PID, mu_log_facility);
+ syslog (LOG_CRIT, _("cannot start `%s': %s"), prog->tag,
+ mu_strerror (errno));
+ exit (EX_SOFTWARE);
+
+ case -1:
+ mu_diag_output (MU_DIAG_CRIT,
+ _("cannot run `%s': fork failed: %s"),
+ prog->tag, mu_strerror (errno));
+ break;
+
+ default:
+ prog->pid = pid;
+ }
+}
+
+static void
+build_depmap ()
+{
+ unsigned i;
+ struct prog *prog;
+ pies_depmap_t dp;
+
+ depmap = depmap_alloc (numprog);
+ for (prog = proghead; prog; prog = prog->next)
+ if (prog->depend)
+ {
+ for (i = 0; prog->depend[i]; i++)
+ {
+ struct prog *dep = prog_lookup_by_tag (prog->depend[i]);
+ if (!dep)
+ {
+ prog->v.p.status = status_disabled;
+ mu_error (_("component %s depends on %s, "
+ "which is not declared"),
+ prog->tag, prog->depend[i]);
+ }
+ else
+ depmap_set (depmap, prog->idx, dep->idx);
+ }
+ }
+ dp = depmap_copy (depmap);
+ depmap_tc (dp);
+ for (i = 0; i < numprog; i++)
+ if (depmap_isset (dp, i, i))
+ {
+ prog = prog_lookup_by_idx (i);
+ mu_error (_("component %s depends on itself"), prog->tag);
+ prog->v.p.status = status_disabled;
+ }
+ free (dp);
+}
+
+void
+progman_start ()
+{
+ struct prog *prog;
+
+ build_depmap ();
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && prog->v.p.status != status_disabled)
+ prog_start (prog);
+}
+
+void
+progman_wake_sleeping ()
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && prog->v.p.status == status_sleeping)
+ {
+ prog->v.p.status = status_enabled;
+ prog->v.p.count = 0;
+ prog->v.p.timestamp = 0;
+ prog_start (prog);
+ }
+}
+
+void
+prog_start_dependencies (struct prog *prog)
+{
+ int i;
+
+ if (!prog->depend)
+ return;
+ for (i = 0; prog->depend[i]; i++)
+ {
+ struct prog *dp = prog_lookup_by_tag (prog->depend[i]);
+ if (!IS_PROG (dp)) /* Skip retranslators */
+ continue;
+ switch (dp->v.p.status)
+ {
+ case status_disabled:
+ prog->v.p.status = status_disabled;
+ return;
+ /* FIXME: other states */
+ }
+ }
+ for (i = 0; prog->depend[i]; i++)
+ prog_start (prog_lookup_by_tag (prog->depend[i]));
+}
+
+void
+prog_stop_dependent (struct prog *prog)
+{
+ pies_depmap_pos_t pos;
+ unsigned n;
+ int warned = 0;
+
+ for (n = depmap_first (depmap, depmap_row, prog->idx, &pos);
+ n != (unsigned)-1;
+ n = depmap_next (depmap, pos))
+ {
+ struct prog *dp = prog_lookup_by_idx (n);
+ if (!dp) /* should not happen */
+ continue;
+ if (!warned)
+ {
+ mu_diag_output (MU_DIAG_DEBUG, "Stopping dependencies of %s",
+ prog->tag);
+ warned = 1;
+ }
+ prog_stop (dp, SIGTERM);
+ }
+ free (pos);
+}
+
+static void
+prog_stop (struct prog *prog, int sig)
+{
+ if (prog->v.p.status == status_enabled && prog->pid > 0)
+ {
+ prog->v.p.status = status_stopping;
+ prog_stop_dependent (prog);
+ mu_diag_output (MU_DIAG_DEBUG, "Stopping %s (%lu)",
+ prog->tag, (unsigned long) prog->pid);
+ kill (prog->pid, sig);
+ }
+}
+
+static void
+prog_stop_all (int sig)
+{
+ struct prog *prog;
+
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && prog->v.p.status == status_enabled)
+ prog_stop (prog, sig);
+}
+
+void
+progman_stop ()
+{
+ unsigned long i;
+
+ prog_stop_all (SIGTERM);
+ for (i = 0; i < shutdown_timeout; i++)
+ {
+ progman_cleanup (1);
+ if (progman_running_count () == 0)
+ return;
+ sleep (1);
+ }
+ prog_stop_all (SIGKILL);
+}
+
+static void
+print_status (char *tag, pid_t pid, int status, int expect_term)
+{
+ if (WIFEXITED (status))
+ {
+ if (WEXITSTATUS (status) == 0)
+ mu_diag_output (MU_DIAG_DEBUG,
+ _("%s (%lu) exited successfully"),
+ tag, (unsigned long) pid);
+ else
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) failed with status %d"),
+ tag, (unsigned long) pid,
+ WEXITSTATUS (status));
+ }
+ else if (WIFSIGNALED (status))
+ {
+ int prio;
+
+ if (expect_term && WTERMSIG (status) == SIGTERM)
+ prio = MU_DIAG_DEBUG;
+ else
+ prio = MU_DIAG_ERR;
+
+ mu_diag_output (prio,
+ _("%s (%lu) terminated on signal %d"),
+ tag, (unsigned long) pid,
+ WTERMSIG (status));
+ }
+ else if (WIFSTOPPED (status))
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) stopped on signal %d"),
+ tag, (unsigned long) pid,
+ WSTOPSIG (status));
+#ifdef WCOREDUMP
+ else if (WCOREDUMP (status))
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) dumped core"),
+ tag, (unsigned long) pid);
+#endif
+ else
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) terminated with unrecognized status"),
+ tag, (unsigned long) pid);
+}
+
+static void
+send_msg (mu_address_t rcpt, mu_message_t msg)
+{
+ mu_mailer_t mailer;
+ int rc;
+
+ rc = mu_mailer_create (&mailer, NULL);
+ if (rc)
+ {
+ const char *mailer_url;
+ mu_mailer_get_url_default (&mailer_url);
+
+ mu_error (_("Cannot create mailer `%s': %s"),
+ mailer_url, mu_strerror (rc));
+ return;
+ }
+
+ /* FIXME: mailer flags? */
+ rc = mu_mailer_open (mailer, 0);
+ if (rc)
+ {
+ const char *mailer_url;
+ mu_mailer_get_url_default (&mailer_url);
+
+ mu_mailer_destroy(&mailer);
+ mu_error (_("Opening mailer `%s' failed: %s"),
+ mailer_url, mu_strerror (rc));
+ return;
+ }
+
+ rc = mu_mailer_send_message (mailer, msg, NULL, rcpt);
+ mu_mailer_destroy (&mailer);
+ if (rc)
+ mu_error (_("Cannot send message: %s"), mu_strerror (rc));
+}
+
+static const char default_termination_message[] =
+"From: <>\n"
+"X-Agent: ${canonical-program-name} (${package} ${version})\n"
+"Subject: Component ${component} terminated with code ${retcode}.\n"
+"\n";
+
+static void
+vartab_define_project (mu_vartab_t vtab)
+{
+ static struct {
+ char *name;
+ char *value;
+ } ptab[] = {
+ { "canonical-program-name", "pies" },
+ { "package", PACKAGE },
+ { "version", PACKAGE_VERSION },
+ { "mu-version", MAILUTILS_VERSION },
+ { NULL }
+ };
+ int i;
+
+ for (i = 0; ptab[i].name; i++)
+ mu_vartab_define (vtab, ptab[i].name, ptab[i].value, 1);
+}
+
+
+static void
+notify (const char *tag, int status, struct action *act)
+{
+ mu_vartab_t vtab;
+ char *msg_text = NULL;
+ mu_message_t msg = NULL;
+ mu_stream_t stream = NULL;
+ int rc;
+
+ mu_vartab_create (&vtab);
+ mu_vartab_define (vtab, "component", tag, 1);
+ mu_vartab_define (vtab, "retcode", mu_umaxtostr (0, status), 1);
+ mu_vartab_define (vtab, "program-name", mu_program_name, 1);
+ vartab_define_project (vtab);
+ rc = mu_vartab_expand (vtab,
+ act->message ?
+ act->message : default_termination_message,
+ &msg_text);
+ mu_vartab_destroy (&vtab);
+ if (rc)
+ {
+ mu_error (_("cannot expand message body: %s"), mu_strerror (rc));
+ /* FIXME: Notify anyway? */
+ return;
+ }
+
+ rc = mu_message_create (&msg, NULL);
+ if (rc)
+ {
+ mu_error (_("cannot create message: %s"), mu_strerror (rc));
+ free (msg_text);
+ }
+
+ mu_message_get_stream (msg, &stream);
+ mu_stream_write (stream, msg_text, strlen (msg_text), 0, NULL);
+ free (msg_text);
+
+ send_msg (act->addr, msg);
+ mu_message_destroy (&msg, mu_message_get_owner (msg));
+}
+
+void
+progman_cleanup (int expect_term)
+{
+ pid_t pid;
+ int status;
+ while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
+ {
+ struct prog *prog = prog_lookup_by_pid (pid);
+ if (!prog)
+ {
+ mu_diag_output (MU_DIAG_NOTICE,
+ _("subprocess %lu finished"),
+ (unsigned long) pid);
+ continue;
+ }
+ print_status (prog->tag, prog->pid, status, expect_term);
+ prog->pid = 0;
+ if (IS_PROG (prog))
+ {
+ prog_stop_dependent (prog);
+ if (!expect_term)
+ {
+ if (WIFEXITED (status))
+ {
+ struct action *act;
+ status = WEXITSTATUS (status);
+ if (status <= MAX_RETURN_CODE
+ && prog->v.p.act && (act = prog->v.p.act[status]))
+ {
+ switch (act->act)
+ {
+ case action_restart:
+ prog_start (prog);
+ break;
+
+ case action_disable:
+ mu_diag_output (MU_DIAG_NOTICE,
+ _("Disabling component %s: exited "
+ "with code %d"),
+ prog->tag, status);
+ prog->v.p.status = status_disabled;
+ }
+ if (act->addr)
+ notify (prog->tag, status, act);
+ continue;
+ }
+ }
+ /* Default action: */
+ prog_start (prog);
+ }
+ }
+ }
+}
+
+void
+progman_stop_component (const char *name)
+{
+ struct prog *prog;
+
+ mu_diag_output (MU_DIAG_INFO, _("stopping component `%s'"), name);
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (prog) && strcmp (prog->tag, name) == 0)
+ {
+ if (prog->v.p.status != status_enabled)
+ mu_diag_output (MU_DIAG_INFO,
+ _("stopping component `%s': component not started"),
+ name);
+ else
+ prog_stop (prog, SIGTERM);
+ }
+}
+
+void
+progman_dump_stats (const char *filename)
+{
+ FILE *fp;
+ struct prog *prog;
+ char *s;
+ int rc;
+
+ mu_diag_output (MU_DIAG_INFO, _("dumping statistics to `%s'"), filename);
+ fp = fopen (filename, "w");
+ if (!fp)
+ {
+ mu_error (_("cannot open file `%s' for writing: %s"),
+ filename, mu_strerror (errno));
+ return;
+ }
+
+ for (prog = proghead; prog; prog = prog->next)
+ {
+ switch (prog->type)
+ {
+ case TYPE_COMPONENT:
+ fprintf (fp, "component %s ",
+ prog->tag);
+ if (prog->pid)
+ fprintf (fp, "%lu", (unsigned long) prog->pid);
+ else if (prog->v.p.status == status_sleeping)
+ {
+ char buf[48];
+ time_t t = prog->v.p.timestamp + SLEEPTIME;
+ strftime (buf, sizeof buf, "%c",
+ localtime (&t));
+ fprintf (fp, _("[disabled; scheduled for %s]"), buf);
+ }
+ else if (prog->v.p.status == status_disabled)
+ fprintf (fp, _("[disabled]"));
+ else
+ fprintf (fp, "[not running]");
+ if (rc = mu_argcv_string (prog->v.p.argc, prog->v.p.argv, &s))
+ {
+ mu_error (_("cannot convert argument list: %s"),
+ mu_strerror (rc));
+ }
+ else
+ {
+ fprintf (fp, " %s", s);
+ free (s);
+ }
+ fputc ('\n', fp);
+ break;
+
+ case TYPE_RETR:
+ fprintf (fp, _("retranslator %s %lu\n"), prog->tag,
+ (unsigned long) prog->pid);
+ }
+ }
+ fclose (fp);
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */

Return to:

Send suggestions and report system problems to the System administrator.