]> git.tuebingen.mpg.de Git - dss.git/commitdiff
Merge topic branch t/realpath into pu pu
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 16 Jun 2024 16:28:34 +0000 (18:28 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 16 Jun 2024 16:28:34 +0000 (18:28 +0200)
Started on 2024-04-27

* refs/heads/t/realpath:
  Compute ipc key only once.
  Resolve config file path only once.
  Use standard realpath(3).

15 files changed:
.gitignore
INSTALL [deleted file]
Makefile
README [deleted file]
README.m4 [new file with mode: 0644]
defs.m4 [new file with mode: 0644]
dss.c
dss.css [deleted file]
dss.suite [deleted file]
dss.suite.m4 [new file with mode: 0644]
index.html.in [deleted file]
index.html.m4 [new file with mode: 0644]
log.h
str.c
version-gen.sh [new file with mode: 0755]

index 36dfcfa431746cdf6c84f70d1de7e99006f3498a..c8ab019759894766aff72cd82775dae13c76b844 100644 (file)
@@ -1,9 +1,5 @@
-Makefile.deps
 Makefile.local
-*.o
 *.swp
-dss.lsg.*
+build/*
 dss
-dss.1
-dss.1.html
-index.html
+dss.1.gz
diff --git a/INSTALL b/INSTALL
deleted file mode 100644 (file)
index 09d8505..0000000
--- a/INSTALL
+++ /dev/null
@@ -1,94 +0,0 @@
-dss is known to compile on Linux, FreeBSD and NetBSD. However, it is
-run-tested only on Linux.
-
-Note that [lopsub](http://people.tuebingen.mpg.de/maan/lopsub)
-is required to compile dss.
-
-Type
-
-               make
-
-in the dss source directory to build the dss executable and the man
-page. If lopsub is installed in a non-standard path, you may need to
-run `make` as follows:
-
-               make CPPFLAGS=-I$HOME/lopsub/include LDFLAGS=-L$HOME/lopsub/lib
-
-Then type
-
-               sudo make install
-
-to install in /usr/local, or
-
-               make install PREFIX=/somewhere/else
-
-to install in /somewhere/else.
-
-Also make sure that [rsync](https://rsync.samba.org/) is installed on
-your system. Version 2.6.1 or newer is required.
-
-Examples:
----------
-
-Suppose you'd like to create snapshots of the existing directory
-
-               /foo/bar
-
-in the directory
-
-               /baz/qux.
-
-Create the config file
-
-               ~/.dssrc
-
-that contains the values for the source and the destination directories
-as follows:
-
-               echo 'source-dir "/foo/bar"' > ~/.dssrc
-               echo 'dest-dir "/baz/qux"' >> ~/.dssrc
-
-Then execute the commands
-
-               mkdir /baz/qux
-               dss run
-
-In order to print the list of all snapshots created so far, use
-
-               dss ls
-
-Yes, it's really that easy.
-
-The second example involves a slightly more sophisticated config file.
-It instructs dss to exclude everything which matches at least one
-pattern of the given exclude file, prevents rsync from crossing file
-system boundaries and increases the number of snapshots.
-
-               source-dir "/foo/bar"
-               dest-dir "/baz/qux"
-               # exclude files matching patterns in /etc/dss.exclude
-               rsync-option "--exclude-from=/etc/dss.exclude"
-               # don't cross filesystem boundaries
-               rsync-option "--one-file-system"
-               # maintain 2^6 - 1 = 63 snapshots
-               num-intervals "6"
-
-The /etc/dss.exclude file could look like this (see rsync(1) for
-more examples)
-
-
-                - /proc
-                - /**/tmp/
-
-Note that dss supports many more features and config options such
-as taking snapshots from remote hosts and several hooks that are
-executed on certain events, for example whenever a snapshot was
-created successfully. Try
-
-               dss -h
-
-for an overview of all supported command line options or
-
-               dss --detailed-help
-
-for the full help text.
index c8242b4f9e3ecc96e3c7da222bf1079a05640d62..db232890a63e4ccf9129d898926aa50745e3abf3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,18 +1,34 @@
 # SPDX-License-Identifier: GPL-2.0
+
+PACKAGE := dss
 PREFIX ?= /usr/local
 INSTALL ?= install
 INSTALL_PROGRAM ?= $(INSTALL)
 INSTALL_DATA ?= $(INSTALL) -m 644
 MKDIR_P := mkdir -p
+B := build
+VERSION := $(shell $(MKDIR_P) $(B) && ./version-gen.sh $(PACKAGE) $(B)/version.c)
+RM := rm -f
+LSG := lopsubgen
+GROFF := groff -m man -t -Thtml -P -l -P -r -P -I -P image
+GZIP := gzip -cfn9
+GIT := git
+M4 := m4 -D "PACKAGE=$(PACKAGE)" defs.m4
+c_source := $(PACKAGE) str file exec sig daemon df tv snap ipc
+c_generated := $(B)/$(PACKAGE).lsg $(B)/version
+objs := $(addsuffix .o, $(c_generated) $(addprefix $(B)/, $(c_source)))
+deps := $(objs:.o=.d)
+all := $(PACKAGE) $(PACKAGE).1.gz
+www := $(B)/index.html $(B)/$(PACKAGE).1.html $(B)/$(PACKAGE)
 
-VERSION_STRING = 1.0.1
-
-dss_objects := dss.o str.o file.o exec.o sig.o daemon.o df.o tv.o snap.o ipc.o dss.lsg.o
-all: dss dss.1
-man: dss.1
+all: $(all)
+www: $(www)
+man: $(PACKAGE).1.gz
+$(objs): $(B)/$(PACKAGE).lsg.h Makefile
+-include $(deps)
 
-DSS_CPPFLAGS := -DVERSION_STRING='"$(VERSION_STRING)"'
 DSS_CPPFLAGS += -Wunused-macros
+DSS_CPPFLAGS += -I$(B)
 
 DSS_CFLAGS := -Wno-sign-compare -g -Wunused -Wundef
 DSS_CFLAGS += -Wredundant-decls
@@ -27,29 +43,67 @@ DSS_CFLAGS += -Wunused-parameter
 DSS_CFLAGS += -Wbad-function-cast
 DSS_CFLAGS += -Wshadow
 
-Makefile.deps: $(wildcard *.c *.h)
-       $(CC) -MM -MG $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) *.c > $@
-
--include Makefile.deps
-
-dss: $(dss_objects)
-       $(CC) -o $@ $(dss_objects) $(LDFLAGS) -llopsub
+CC_CMD = $(CC) -c -o $@ $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) \
+               -MMD -MF $(B)/$(*F).d -MT $@ $<
+LD_CMD = $(CC) -o $@ $(objs) $(LDFLAGS) -llopsub
 
-%.o: %.c Makefile
-       $(CC) -c $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) $<
-
-%.lsg.h: %.suite
-       lopsubgen --gen-h=$@ < $<
-%.lsg.c: %.suite
-       lopsubgen --gen-c=$@ < $<
-%.1: %.suite
-       lopsubgen --gen-man=$@ --version-string=$(VERSION_STRING) < $<
-
-%.1.html: %.1
-       groff -m man -Thtml -P -l -P -r $< | sed -e '1,/^<body>/d; /^<\/body>/,$$d' > $@
+.ONESHELL:
+.SHELLFLAGS := -ec
+ifeq ("$(origin V)", "command line")
+       SAY =
+else
+       SAY = @printf '%s\n' '$(strip $(1))'
+endif
+$(B)/version.c:
+       $(call SAY, VG $@)
+       ./version-gen.sh $(PACKAGE) version.c > /dev/null
+$(PACKAGE): $(objs)
+       $(call SAY, LD $@)
+       $(LD_CMD)
+$(B)/$(PACKAGE): $(objs)
+       $(call SAY, LD $@)
+       $(LD_CMD) -static
+       strip $@
+$(B)/%.o: $(B)/%.c
+       $(call SAY, CC $<)
+       $(CC_CMD)
+$(B)/%.o: %.c
+       $(call SAY, CC $<)
+       $(CC_CMD)
 
+.PHONY: all www clean distclean maintainer-clean install README
+.PRECIOUS: $(B)/%.lsg.c $(B)/%.lsg.h $(B)/%.suite $(B)/%.1
+$(B)/$(PACKAGE).suite: $(PACKAGE).suite.m4 defs.m4 Makefile
+       $(call SAY, M4 $<)
+       $(M4) $< > $@
+$(B)/%.lsg.h: $(B)/%.suite
+       $(call SAY, LSGH $<)
+       $(LSG) --gen-h=$@ < $<
+$(B)/%.lsg.c: $(B)/%.suite
+       $(call SAY, LSGC $<)
+       $(LSG) --gen-c=$@ < $<
+$(B)/%.1: $(B)/%.suite
+       $(call SAY, LSGM $<)
+       $(LSG) --gen-man=$@ --version-string=$(VERSION) < $<
+%.1.gz: $(B)/%.1
+       $(call SAY, GZIP $<)
+       $(GZIP) < $< > $@
+$(B)/%.1.html: $(B)/%.1
+       $(call SAY, GROFF $<)
+       cd $(B)
+       $(GROFF) ../$< > ../$@
+$(B)/index.html: index.html.m4 Makefile defs.m4 dss.svg
+       $(call SAY, M4 $@)
+       $(M4) $< > $@
 clean:
-       rm -f *.o dss dss.1 dss.1.html Makefile.deps *~ index.html dss.lsg.h dss.lsg.c
+       $(call SAY, CLEAN)
+       $(RM) $(B)/*.o $(B)/*.d $(all) $(www)
+distclean: clean
+       $(call SAY, DISTCLEAN)
+       $(RM) -r $(B)
+maintainer-clean: distclean
+       $(call SAY, MAINTANER-CLEAN)
+       $(GIT) clean -dfqxe Makefile.local
 
 ifneq ($(findstring strip, $(MAKECMDGOALS)),)
        strip_option := -s
@@ -58,18 +112,10 @@ bindir := $(DESTDIR)$(PREFIX)/bin
 mandir := $(DESTDIR)$(PREFIX)/share/man/man1
 install install-strip: all
        $(MKDIR_P) $(bindir) $(mandir)
-       $(INSTALL_PROGRAM) $(strip_option) dss $(bindir)
-       $(INSTALL_DATA) dss.1 $(mandir)
+       $(INSTALL_PROGRAM) $(strip_option) $(PACKAGE) $(bindir)
+       $(INSTALL_DATA) $(PACKAGE).1.gz $(mandir)
 
-index.html: dss.1.html index.html.in INSTALL README NEWS
-       sed -e '/@README@/,$$d' index.html.in > $@
-       markdown README >> $@
-       sed -e '1,/@README@/d' -e '/@NEWS@/,$$d' index.html.in >> $@
-       markdown NEWS >> $@
-       sed -e '1,/@NEWS@/d' -e '/@INSTALL@/,$$d' index.html.in >> $@
-       markdown INSTALL >> $@
-       sed -e '1,/@INSTALL@/d' -e '/@MAN_PAGE@/,$$d' index.html.in >> $@
-       cat dss.1.html >> $@
-       sed -e '1,/@MAN_PAGE@/d' index.html.in >> $@
+README:
+       @$(M4) README.m4
 
 -include Makefile.local
diff --git a/README b/README
deleted file mode 100644 (file)
index 79b8889..0000000
--- a/README
+++ /dev/null
@@ -1,28 +0,0 @@
-dss creates hardlink-based snapshots of a given directory on a remote
-or local host using rsync's link-dest feature.
-
-dss is admin friendly: It is easy to configure and needs little
-attention once configured to run in daemon mode. It keeps track of
-the available disk space and removes snapshots if disk space becomes
-sparse or snapshots become older than the specified time. Also, due
-to the hardlink-based approach, there is only one type of backup.
-Hence no full, incremental or differential backups need to be
-configured, and there is no database to maintain.
-
-dss is also user-friendly because users can browse the snapshot
-directories without admin intervention and see the contents of the file
-system at the various times a snapshot was taken. Each snapshot looks
-like a full backup, so users can easily restore accidentally removed
-files by using their favorite file browser to simply copy files from
-the snapshot directory back to the live system.
-
-dss gives your data an additional level of security besides the usual
-tape-based backups: If the file server goes down and all data is lost
-you can simply use the most recent snapshot as an immediate replacement
--- no need for a restore from tape that takes days to complete.
-
-Snapshot pruning takes place in a dyadic fashion: Many recent snapshots
-are available, but the number of snapshots per time interval decreases
-exponentially. For example, one can configure dss so that it keeps
-16 snapshots not older than one week, 8 snapshots between one and
-two weeks old, 4 snapshots between two and three weeks old, and so on.
diff --git a/README.m4 b/README.m4
new file mode 100644 (file)
index 0000000..2a716f1
--- /dev/null
+++ b/README.m4
@@ -0,0 +1,16 @@
+dnl Run "make README" to see this with variables expanded.
+PACKAGE() - SLOGAN()
+
+DESCRIPTION1()
+
+DESCRIPTION2()
+
+DESCRIPTION3()
+
+Resources
+~~~~~~~~~
+project web page: PACKAGE_HOMEPAGE()
+git clone CLONE_URL()
+gitweb: GITWEB_URL()
+author home page: HOME_URL()
+report bugs to: AUTHOR() <EMAIL()>
diff --git a/defs.m4 b/defs.m4
new file mode 100644 (file)
index 0000000..da2ad92
--- /dev/null
+++ b/defs.m4
@@ -0,0 +1,39 @@
+divert(-1)
+changequote(`«', `»')
+
+define(«SLOGAN», «the dyadic snapshot scheduler»)
+define(«AUTHOR», «Andre Noll»)
+define(«EMAIL», «maan@tuebingen.mpg.de»)
+define(«URL», «https://people.tuebingen.mpg.de/maan/PACKAGE()/»)
+define(«CLONE_URL», «https://git.tuebingen.mpg.de/PACKAGE()»)
+define(«GITWEB_URL», «https://git.tuebingen.mpg.de/PACKAGE().git»)
+define(«PACKAGE_HOMEPAGE», «https://people.tuebingen.mpg.de/maan/PACKAGE()/»)
+define(«HOME_URL», «https://people.tuebingen.mpg.de/maan/»)
+define(«LICENSE», «GPL-2.0»)
+define(«LICENSE_URL», «https://www.gnu.org/licenses»)
+
+define(«DESCRIPTION1», «dnl
+PACKAGE() maintains hardlink-based snapshots of a given directory on
+a remote or local host using rsync's link-dest feature. The snapshots
+are organized so that any snapshot can directly be deployed as an
+(emergency) replacement for the primary system.»)
+
+define(«DESCRIPTION2», «dnl
+PACKAGE() is admin friendly: It is easy to configure and needs
+little attention after the initial setup. In particular, no full,
+incremental or differential backups need to be configured, and there is
+no database to maintain. PACKAGE() is also user-friendly: Assuming the
+snapshot server allows read-only user access over the network, users
+can restore accidentically removed files without admin intervention,
+using their favorite file browser to copy files from the snapshot
+directory back to the primary system.»)
+
+define(«DESCRIPTION3», «dnl
+PACKAGE() keeps track of the age and the state of existing snapshots and
+triggers snapshot creation and removal according to the configuration
+settings. It tries to maintain a scheme where many recent snapshots
+and few old snapshots exist, for example 16 snapshots newer than a
+week, 8 snapshots between one and two weeks old, 4 snapshots between
+two and three weeks old, and so on.»)
+
+divert(0)
diff --git a/dss.c b/dss.c
index 1ea8adab7b3dd0155c340c1810fedfe700826432..477df6f1a5f3e8f356af352e400a0ee73dcdf6ac 100644 (file)
--- a/dss.c
+++ b/dss.c
@@ -1829,19 +1829,23 @@ static int setup_signal_handling(void)
        return install_sighandler(SIGCHLD);
 }
 
+const char *dss_version(void);
 static void handle_version_and_help(void)
 {
        char *txt;
 
+       if (OPT_GIVEN(DSS, VERSION)) {
+               printf("%s\n", dss_version());
+               exit(EXIT_SUCCESS);
+       }
        if (OPT_GIVEN(DSS, DETAILED_HELP))
                txt = lls_long_help(CMD_PTR(DSS));
        else if (OPT_GIVEN(DSS, HELP))
                txt = lls_short_help(CMD_PTR(DSS));
-       else if (OPT_GIVEN(DSS, VERSION))
-               txt = make_message("%s\n", VERSION_STRING);
        else
                return;
        printf("%s", txt);
+       printf("\nRun dss help for help on subcommands.\n");
        free(txt);
        exit(EXIT_SUCCESS);
 }
@@ -1857,8 +1861,39 @@ static void show_subcommand_summary(void)
                const char *purpose = lls_purpose(cmd);
                printf("%-11s%s\n", name, purpose);
        }
-       exit(EXIT_SUCCESS);
+       printf("\nRun dss help <subcmd> for help on <subcmd>.\n");
+}
+
+static int com_help(void)
+{
+       int ret;
+       char *errctx, *help;
+       const char *arg;
+       const struct lls_command *cmd;
+
+       ret = lls_check_arg_count(sublpr, 0, 1, &errctx);
+       if (ret < 0)
+               return lopsub_error(ret, &errctx);
+       if (lls_num_inputs(sublpr) == 0) {
+               show_subcommand_summary();
+               return 0;
+       }
+       arg = lls_input(0, sublpr);
+       ret = lls_lookup_subcmd(arg, dss_suite, &errctx);
+       if (ret < 0)
+               return lopsub_error(ret, &errctx);
+       cmd = lls_cmd(ret, dss_suite);
+       if (OPT_GIVEN(HELP, LONG))
+               help = lls_long_help(cmd);
+       else
+               help = lls_short_help(cmd);
+       printf("%s", help);
+       free(help);
+       if (!OPT_GIVEN(HELP, LONG))
+               printf("\nRun dss -- help -l %s for long help.\n", arg);
+       return 0;
 }
+EXPORT_CMD_HANDLER(help);
 
 int main(int argc, char **argv)
 {
@@ -1879,8 +1914,11 @@ int main(int argc, char **argv)
                goto out;
        handle_version_and_help();
        num_inputs = lls_num_inputs(lpr);
-       if (num_inputs == 0)
+       if (num_inputs == 0) {
                show_subcommand_summary();
+               ret = 0;
+               goto out;
+       }
        ret = lls_lookup_subcmd(argv[argc - num_inputs], dss_suite, &errctx);
        if (ret < 0) {
                ret = lopsub_error(ret, &errctx);
diff --git a/dss.css b/dss.css
deleted file mode 100644 (file)
index a9da07f..0000000
--- a/dss.css
+++ /dev/null
@@ -1,12 +0,0 @@
-BODY,TD {
-       background-color: #ffffff;
-       color: #333333;
-}
-
-HR {
-       background-color: #ffff00;
-}
-
-A {
-       color: #4444ff;
-}
diff --git a/dss.suite b/dss.suite
deleted file mode 100644 (file)
index f9cf8af..0000000
--- a/dss.suite
+++ /dev/null
@@ -1,564 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-
-[suite dss]
-caption = Subcommands
-
-[supercommand dss]
-       [description]
-               dss creates hardlink-based snapshots of a given directory on a remote
-               or local host using rsync's link-dest feature.
-       [/description]
-       purpose = the dyadic snapshot scheduler
-       synopsis = [global-options...] [--] [<subcommand> [subcommand-options...]]
-
-       [option general-options-section]
-               summary = General options
-               flag ignored
-       [option help]
-               summary = print help and exit
-               short_opt = h
-       [option detailed-help]
-               summary = print help, including all details, and exit
-       [option version]
-               summary = print version and exit
-               short_opt = V
-       [option config-file]
-               short_opt = c
-               summary = use alternative config file (default: ~/.dssrc)
-               typestr = path
-               arg_info = required_arg
-               arg_type = string
-               [help]
-                       Options may be given at the command line or in the configuration
-                       file. As usual, if an option is given both at the command line and
-                       in the configuration file, the command line option takes precedence.
-
-                       However, there is one exception to this rule: The run subcommand
-                       re-reads the configuration file when it receives the HUP signal. In
-                       this case the options in the config file override any options that
-                       were previously given at the command line. This allows changing the
-                       configuration of a running dss process by sending SIGHUP.
-               [/help]
-       [option loglevel]
-               short_opt = l
-               summary = set loglevel (0-6)
-               typestr = level
-               arg_info = required_arg
-               arg_type = uint32
-               default_val = 4
-               [help]
-                       Lower values mean more verbose logging.
-               [/help]
-       [option dry-run]
-               short_opt = n
-               summary = only print what would be done
-               [help]
-                       This flag does not make sense for all subcommands. The run subcommand
-                       refuses to start if this option was given while the ls subcommand
-                       silently ignores the flag.
-               [/help]
-       [option source-dir]
-               summary = the remote directory to snapshot
-               typestr = dirname
-               arg_info = required_arg
-               arg_type = string
-               flag multiple
-               [help]
-                       The directory on the remote host from which snapshots are taken.
-                       Of course, the user specified as --remote-user must have read access
-                       to this directory.
-
-                       This option is mandatory for the create and run subcommands: It may
-                       be given multiple times to specify more than one source directory.
-                       However, all source directories must reside on the same server.
-               [/help]
-       [option dest-dir]
-               summary = where snapshots are stored
-               typestr = dirname
-               arg_info = required_arg
-               arg_type = string
-               [help]
-                       The destination directory on the local host where snapshots will be
-                       written. This must be writable by the user who runs dss.
-
-                       This option is mandatory for all subcommands except kill.
-                       Unlike --source-dir, this option may only be given once.
-               [/help]
-       [option mountpoint]
-               summary = abort if destination directory is not a mountpoint
-               [help]
-                       This option checks whether a file system is mounted on the directory
-                       specified as the argument to --dest-dir. Operation proceeds only
-                       if this is the case. Otherwise dss exits unsuccessfully without
-                       performing any action. Use this option to prevent snapshot creation
-                       if the snapshot file system is not mounted.
-
-                       This option is silently ignored for subcommands which do not depend
-                       on the destination directory.
-               [/help]
-       [option Rsync-options]
-               summary = Controlling how rsync is run
-               flag ignored
-               [help]
-                       These options are only relevant to the run and the create subcommands.
-               [/help]
-       [option remote-host]
-               short_opt = H
-               summary = host to take snapshots from
-               typestr = hostname
-               arg_info = required_arg
-               arg_type = string
-               default_val = localhost
-               [help]
-                       If this option is given and its value differs from the local
-                       host, then rsync uses ssh. Make sure there is no password
-                       needed for the ssh connection. To achieve that, use public key
-                       authentication for ssh and, if needed, set the remote user name
-                       by using the --remote-user option.
-               [/help]
-       [option remote-user]
-               short_opt = U
-               summary = Remote user name (default: current user)
-               arg_info = required_arg
-               typestr = username
-               arg_type = string
-               [help]
-                       Set this if the user that runs dss is different from the user on the
-                       remote host.
-               [/help]
-       [option checksum]
-               summary = run rsync with --checksum occasionally
-               typestr = permille
-               arg_info = required_arg
-               arg_type = uint32
-               default_val = 0
-               [help]
-                       If a file on the backup becomes corrupt in a way that file size
-                       and modification time still match the original file, rsync will not
-                       consider the file for transfer ("quick check"). Hence the corruption
-                       stays on the backup until the file is modified on the source.
-                       The --checksum option of rsync disables the quick check and compares
-                       the contents of each file, fixing such corruptions. Since computing
-                       the checksums adds a significant slowdown due to a lot of disk I/O,
-                       the option is not enabled by default.
-
-                       The argument to the --checksum option of dss is a number between 0
-                       and 1000, inclusively, which determines the probability of adding
-                       --checksum to the rsync options each time a snapshot is created. The
-                       default value zero means to never add the option. The value 100 will
-                       create every tenth snapshot (on average) using checksums, and the
-                       value 1000 will always pass --checksum to rsync.
-               [/help]
-       [option rsync-option]
-               short_opt = O
-               summary = further rsync options
-               typestr = option
-               arg_info = required_arg
-               arg_type = string
-               flag multiple
-               [help]
-                       This option may be given multiple times. The given argument is
-                       passed verbatim to the rsync command. Note that in order to use
-                       rsync options that require an argument, you have to specify the
-                       option and its argument as separate --rsync-options, like this:
-
-                               --rsync-option --exclude --rsync-option /proc
-               [/help]
-       [option intervals]
-               summary = Fine tuning the number of snapshots per time unit
-               flag ignored
-               [help]
-                       Snapshot aging is implemented in terms of intervals. There are two
-                       command line options related to intervals: the duration u of a unit
-                       interval and the number of unit intervals, denoted n below.
-
-                       dss removes snapshots older than n times u and tries to keep 2^(n -
-                       k - 1) snapshots in interval k, where the interval number k counts
-                       from zero to n - 1, with zero being the most recent unit interval.
-
-                       Hence the oldest snapshot will at most be u * n days old (4 days *
-                       5 intervals = 20 days, if default values are used). Moreover, there
-                       are at most 2^n - 1 snapshots in total (2^5 - 1 = 31 by default). Note
-                       that for this to work out your system must be fast enough to create at
-                       least 2^(n - 1) snapshots per unit interval (16 snapshots in 4 days =
-                       one snapshot in 6 hours), because this is the number of snapshots in
-                       interval zero.
-               [/help]
-       [option unit-interval]
-               short_opt = u
-               summary = the duration of a unit interval
-               typestr = days
-               arg_info = required_arg
-               arg_type = uint32
-               default_val = 4
-               [help]
-                       Increasing this number instructs dss to create fewer snapshots per
-                       time unit while the number of snapshots to keep stays the same.
-               [/help]
-       [option num-intervals]
-               short_opt = n
-               summary = the number of unit intervals
-               typestr = num
-               arg_info = required_arg
-               arg_type = uint32
-               default_val = 5
-               [help]
-                       Increasing this number by one doubles the total number of
-                       snapshots.
-               [/help]
-       [option hooks]
-               summary = Commands to be run on certain events
-               flag ignored
-               [help]
-                       All hooks default to "true". That is, the true(1) utility (which
-                       always returns with exit code zero) is executed if the hook command
-                       is not specified.
-               [/help]
-       [option pre-create-hook]
-               short_opt = r
-               summary = executed before a snapshot is created
-               typestr = command
-               arg_info = required_arg
-               arg_type = string
-               default_val = true
-               [help]
-                       This command is executed before dss runs rsync to create a new
-                       snapshot. If the command returns with a non-zero exit status, no
-                       snapshot will be created and the operation is retried later.
-
-                       For example, the command could execute a script that checks whether
-                       all snapshot-related file systems are mounted.
-
-                       Another possible application of the pre-create hook is to return
-                       non-zero during office hours in order to not slow down the file
-                       systems by taking snapshots.
-               [/help]
-       [option post-create-hook]
-               summary = executed after a snapshot has been created
-               typestr = command
-               arg_info = required_arg
-               arg_type = string
-               default_val = true
-               [help]
-                       This is only executed if a snapshot has successfully been created. The
-                       full path of the newly created snapshot is passed to the hook as the
-                       first argument. The exit code of this hook is ignored.
-
-                       For instance this hook could count the number of files per user
-                       and/or compute disk usage patterns to be stored in a database for
-                       further analysis.
-               [/help]
-       [option pre-remove-hook]
-               summary = executed before a snapshot is removed
-               typestr = command
-               arg_info = required_arg
-               arg_type = string
-               default_val = true
-               [help]
-                       The full path to the snapshot which is about to be removed is passed
-                       to the command as the first argument. If the command returns with
-                       a non-zero exit status, the snapshot is not going to be removed and
-                       the operation is retried later.
-
-                       For example, one could execute a script that checks whether the
-                       snapshot to be deleted is currently used by another process, e.g. by
-                       a tape-based backup system that runs concurrently to dss.
-
-                       Another possible application of this is to record disk-usage
-                       patterns before and after snapshot removal.
-               [/help]
-       [option post-remove-hook]
-               summary = executed after snapshot removal
-               typestr = command
-               arg_info = required_arg
-               arg_type = string
-               default_val = true
-               [help]
-                       As for the pre-remove hook, the full path of the removed snapshot is
-                       passed to the hook as the first argument. The exit code of this hook
-                       is ignored.
-               [/help]
-       [option exit-hook]
-               summary = executed before the run command exits
-               typestr = command
-               arg_info = required_arg
-               arg_type = string
-               default_val = true
-               [help]
-                       This hook is only relevant to the run subcommand. It is executed just
-                       before dss terminates. The reason for termination is passed as the
-                       first argument.
-
-                       One possible application for this hook is to send email to the system
-                       administrator to let her know that no more snapshots are going to
-                       be created.
-               [/help]
-
-       [option disk-space-monitoring]
-               summary = Disk space monitoring
-               flag ignored
-               [help]
-                       The options of this section control the aggressiveness of snapshot
-                       removal. That is, they define under which circumstances existing
-                       snapshots are removed. These options are only relevant to the run
-                       and the prune subcommands.
-               [/help]
-       [option min-free-mb]
-               short_opt = m
-               summary = minimal amount of free disk space
-               arg_info = required_arg
-               arg_type = uint32
-               typestr = megabytes
-               default_val = 100
-               [help]
-                       If disk space on the file system containing the destination
-                       directory gets low, the run subcommand suspends the currently
-                       running rsync process and starts to remove snapshots in order to
-                       free disk space. This option specifies the minimal amount of free
-                       disk space. If less than the given number of megabytes is available,
-                       snapshots are being deleted. See also the --min_free_percent and the
-                       min-free-percent-inodes options below.
-
-                       A value of zero deactivates this check.
-               [/help]
-       [option min-free-percent]
-               short_opt = p
-               summary = minimal percentage of free disk space
-               arg_info = required_arg
-               arg_type = uint32
-               typestr = percent
-               default_val = 2
-               [help]
-                       This is like --min-free-mb but the amount of free disk space
-                       is specified as a percentage. It is not recommended to set both
-                       --min-free-mb and --min-free-percent to zero as this will cause your
-                       file system to fill up quickly.
-               [/help]
-       [option min-free-percent-inodes]
-               short_opt = i
-               summary = minimal percent of free inodes
-               arg_info = required_arg
-               arg_type = uint32
-               typestr = percent
-               default_val = 0
-               [help]
-                       The minimum amount of free inodes on the file system containing the
-                       destination dir. If the percentage of free inodes drops below the
-                       given value, snapshot removal kicks in like in case of low disk space.
-
-                       The number of free inodes is determined from the f_ffree field of
-                       the statvfs structure. However, some file systems set this field to
-                       zero, indicating that the number of inodes is basically unlimited.
-                       Moreover it is not possible to reliably detect whether this is the
-                       case. Therefore this feature is disabled by default. It's safe to
-                       enable it for ext2/ext3/ext4 file systems on linux though.
-
-                       A value of zero (the default) deactivates this check.
-               [/help]
-       [option keep-redundant]
-               short_opt = k
-               summary = prune by disk space only
-               [help]
-                       By default, redundant and outdated snapshots are removed automatically
-                       to keep the number of snapshots in harmony with the configured
-                       policy. If this flag is given, dss removes such snapshots only if
-                       disk space or number of free inodes becomes low.
-               [/help]
-       [option min-complete]
-               summary = minimal number of complete snapshots to keep
-               arg_info = optional_arg
-               arg_type = uint32
-               typestr = num
-               default_val = 1
-               [help]
-                       This option is only relevant if snapshots must be deleted because
-                       disk space gets low.
-
-                       dss refuses to remove old snapshots if there are fewer complete
-                       snapshots left than the given number. The default value of one
-                       guarantees that at least one complete snapshot is available at
-                       all times.
-
-                       If only <num> complete snapshots are left, and there is not enough
-                       disk space available for another snapshot, the program terminates
-                       with a "No space left on device" error.
-               [/help]
-
-[introduction]
-       dss supports the subcommands described below. If no subcommand is
-       given, the list of available subcommands is shown and the program
-       terminates successfully without performing any further action.
-[/introduction]
-
-[subcommand run]
-       purpose = start creating and pruning snapshots
-       [description]
-               This is the main mode of operation. Snapshots are created in an endless
-               loop as needed and pruned automatically. The loop only terminates on
-               fatal errors or if a terminating signal was received. See also the
-               --exit-hook option.
-       [/description]
-       [option daemon]
-               short_opt = d
-               summary = run as background daemon
-               [help]
-                       If this option is given, the dss command detaches from the console
-                       and continues to run in the background. It is not possible to let
-                       a daemonized process re-attach to the console by editing the config
-                       file and sending SIGHUP. However, log output may be redirected to a
-                       different file in this way.
-
-                       See --logfile.
-               [/help]
-       [option logfile]
-               short_opt = l
-               summary = where to write log output
-               arg_info = required_arg
-               arg_type = string
-               typestr = path
-               default_val = /dev/null
-               [help]
-                       This option is only honored if --daemon is given, in which case
-                       log messages go to the given file. Otherwise the option is silently
-                       ignored and log output is written to stderr.
-               [/help]
-       [option max-rsync-errors]
-               summary = terminate after this many rsync failures
-               typestr = count
-               arg_info = required_arg
-               arg_type = uint32
-               default_val = 10
-               [help]
-                       If the rsync process exits with a fatal error, dss restarts the command
-                       in the hope that the problem is transient and subsequent rsync runs
-                       succeed. After the given number of consecutive rsync error exits,
-                       however, dss gives up, executes the exit hook and terminates. Set
-                       this to zero if dss should exit immediately on the first rsync error.
-
-                       The only non-fatal error is when rsync exits with code 24. This
-                       indicates a partial transfer due to vanished source files and happens
-                       frequently when snapshotting a directory which is concurrently being
-                       modified.
-               [/help]
-[subcommand create]
-       purpose = execute rsync once to create a new snapshot
-       [description]
-               This command does not check the amount free disk space. The pre-create
-               and post-create hooks are honored, however.
-
-               Specify --dry-run to see the rsync command which is executed to create
-               snapshots.
-       [/description]
-[subcommand prune]
-       purpose = remove snapshots
-       [description]
-               A snapshot is said to be (a) outdated if its interval number is greater
-               or equal than the specified number of unit intervals, (b) redundant if
-               the interval it belongs to contains more than the configured number of
-               snapshots, and (c) orphaned if it is incomplete and not being created
-               or deleted. All other snapshots are called regular.
-
-               Unless --dry-run is given, which just prints the snapshot that would be
-               removed, this subcommand gets rid of non-regular snapshots.  At most
-               one snapshot is removed per invocation. If no such snapshot exists
-               and disk space is low, the subcommand also removes regular snapshots,
-               always picking the oldest one.
-
-               The subcommand fails if there is another dss "run" process.
-       [/description]
-       [option disk-space]
-               summary = act as if free disk space was high/low
-               arg_info = required_arg
-               arg_type = string
-               typestr = mode
-               values = {
-                       FDS_CHECK = "check",
-                       FDS_HIGH = "high",
-                       FDS_LOW = "low"
-               }
-               default_val = check
-               [help]
-                       By default, free disk space is checked and even regular snapshots
-                       become candidates for removal if disk space is low. This option
-                       overrides the result of the check.
-               [/help]
-[subcommand ls]
-       purpose = print the list of all snapshots
-       [description]
-               The list contains all existing snapshots, no matter of their state.
-               Incomplete snapshots and snapshots being deleted will also be listed.
-       [/description]
-[subcommand kill]
-       purpose = send a signal to a running dss process
-       [description]
-               This sends a signal to the dss process that corresponds to the given
-               config file. If --dry-run is given, the PID of the dss process is
-               written to stdout, but no signal is sent.
-       [/description]
-       [option signal]
-               short_opt = s
-               summary = send the given signal rather than SIGTERM
-               typestr = signal
-               arg_info = required_arg
-               arg_type = string
-               default_val = SIGTERM
-               [help]
-                       Like for kill(1), alternate signals may be specified in three ways: as
-                       a signal number (e.g., 9), the signal name (e.g., KILL), or the signal
-                       name prefixed with "SIG" (e.g., SIGKILL). In the latter two forms,
-                       the signal name and the prefix are case insensitive, so "sigkill"
-                       works as well.
-
-                       Sending SIGHUP causes the running dss process to reload its config file.
-               [/help]
-       [option wait]
-               short_opt = w
-               summary = wait until the signalled process has terminated
-               [help]
-                       This option is handy for system shutdown scripts which would like
-                       to terminate the dss daemon process.
-
-                       Without --wait the dss process which executes the kill subcommand
-                       exits right after the kill(2) system call returns. At this point the
-                       signalled process might still be alive (even if SIGKILL was sent).
-                       If --wait is given, the process waits until the signalled process
-                       has terminated or the timeout expires.
-
-                       If --wait is not given, the kill subcommand exits successfully if
-                       and only if the signal was sent (i.e., if there exists another dss
-                       process to receive the signal). With --wait it exits successfully
-                       if, additionally, the signalled process has terminated before the
-                       timeout expires.
-
-                       It makes only sense to use the option for signals which terminate dss.
-               [/help]
-[subcommand configtest]
-       purpose = run a configuration file syntax test
-       [description]
-               This command checks the command line options and the configuration
-               file for syntactic correctness. It either reports "Syntax Ok" and
-               exits successfully or prints information about the first syntax error
-               detected and terminates with exit code 1.
-       [/description]
-
-[section copyright]
-       Written by Andre Noll
-       .br
-       Copyright (C) 2008 - present Andre Noll
-       .br
-       License: GNU GPL version 2
-       .br
-       This is free software: you are free to change and redistribute it.
-       .br
-       There is NO WARRANTY, to the extent permitted by law.
-       .br
-       Report bugs to
-       .MT <maan@tuebingen.mpg.de>
-       Andre Noll
-       .ME
-[/section]
-
-[section see also]
-       .BR ssh (1) ,
-       .BR rsync (1)
-[/section]
diff --git a/dss.suite.m4 b/dss.suite.m4
new file mode 100644 (file)
index 0000000..3fe3122
--- /dev/null
@@ -0,0 +1,684 @@
+# SPDX-License-Identifier: GPL-2.0
+
+[suite dss]
+caption = Subcommands
+
+[supercommand dss]
+       [description]
+               DESCRIPTION1()
+
+               DESCRIPTION2()
+
+               DESCRIPTION3()
+       [/description]
+       purpose = SLOGAN()
+       synopsis = [global-options...] [--] [<subcommand> [subcommand-options...]]
+
+       [option general-options-section]
+               summary = General options
+               flag ignored
+       [option help]
+               summary = print help and exit
+               short_opt = h
+       [option detailed-help]
+               summary = print help, including all details, and exit
+       [option version]
+               summary = print version and exit
+               short_opt = V
+       [option config-file]
+               short_opt = c
+               summary = use alternative config file (default: ~/.dssrc)
+               typestr = path
+               arg_info = required_arg
+               arg_type = string
+               [help]
+                       Options may be given at the command line or in the configuration
+                       file. As usual, if an option is given both at the command line and
+                       in the configuration file, the command line option takes precedence.
+
+                       However, there is one exception to this rule: The run subcommand
+                       re-reads the configuration file when it receives the HUP signal. In
+                       this case the options in the config file override any options that
+                       were previously given at the command line. This allows changing the
+                       configuration of a running dss process by sending SIGHUP.
+               [/help]
+       [option loglevel]
+               short_opt = l
+               summary = set loglevel (0-6)
+               typestr = level
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 4
+               [help]
+                       Lower values mean more verbose logging.
+               [/help]
+       [option dry-run]
+               short_opt = n
+               summary = only print what would be done
+               [help]
+                       This flag does not make sense for all subcommands. The run subcommand
+                       refuses to start if this option was given while the ls subcommand
+                       silently ignores the flag.
+               [/help]
+       [option source-dir]
+               summary = the remote directory to snapshot
+               typestr = dirname
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               [help]
+                       The directory on the remote host from which snapshots are taken.
+                       Of course, the user specified as --remote-user must have read access
+                       to this directory.
+
+                       This option is mandatory for the create and run subcommands: It may
+                       be given multiple times to specify more than one source directory.
+                       However, all source directories must reside on the same server.
+               [/help]
+       [option dest-dir]
+               summary = where snapshots are stored
+               typestr = dirname
+               arg_info = required_arg
+               arg_type = string
+               [help]
+                       The destination directory on the local host where snapshots will be
+                       written. This must be writable by the user who runs dss.
+
+                       This option is mandatory for all subcommands except kill.
+                       Unlike --source-dir, this option may only be given once.
+               [/help]
+       [option mountpoint]
+               summary = abort if destination directory is not a mountpoint
+               [help]
+                       This option checks whether a file system is mounted on the directory
+                       specified as the argument to --dest-dir. Operation proceeds only
+                       if this is the case. Otherwise dss exits unsuccessfully without
+                       performing any action. Use this option to prevent snapshot creation
+                       if the snapshot file system is not mounted.
+
+                       This option is silently ignored for subcommands which do not depend
+                       on the destination directory.
+               [/help]
+       [option Rsync-options]
+               summary = Controlling how rsync is run
+               flag ignored
+               [help]
+                       These options are only relevant to the run and the create subcommands.
+               [/help]
+       [option remote-host]
+               short_opt = H
+               summary = host to take snapshots from
+               typestr = hostname
+               arg_info = required_arg
+               arg_type = string
+               default_val = localhost
+               [help]
+                       If this option is given and its value differs from the local
+                       host, then rsync uses ssh. Make sure there is no password
+                       needed for the ssh connection. To achieve that, use public key
+                       authentication for ssh and, if needed, set the remote user name
+                       by using the --remote-user option.
+               [/help]
+       [option remote-user]
+               short_opt = U
+               summary = Remote user name (default: current user)
+               arg_info = required_arg
+               typestr = username
+               arg_type = string
+               [help]
+                       Set this if the user that runs dss is different from the user on the
+                       remote host.
+               [/help]
+       [option checksum]
+               summary = run rsync with --checksum occasionally
+               typestr = permille
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 0
+               [help]
+                       If a file on the backup becomes corrupt in a way that file size
+                       and modification time still match the original file, rsync will not
+                       consider the file for transfer ("quick check"). Hence the corruption
+                       stays on the backup until the file is modified on the source.
+                       The --checksum option of rsync disables the quick check and compares
+                       the contents of each file, fixing such corruptions. Since computing
+                       the checksums adds a significant slowdown due to a lot of disk I/O,
+                       the option is not enabled by default.
+
+                       The argument to the --checksum option of dss is a number between 0
+                       and 1000, inclusively, which determines the probability of adding
+                       --checksum to the rsync options each time a snapshot is created. The
+                       default value zero means to never add the option. The value 100 will
+                       create every tenth snapshot (on average) using checksums, and the
+                       value 1000 will always pass --checksum to rsync.
+               [/help]
+       [option rsync-option]
+               short_opt = O
+               summary = further rsync options
+               typestr = option
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               [help]
+                       This option may be given multiple times. The given argument is
+                       passed verbatim to the rsync command. Note that in order to use
+                       rsync options that require an argument, you have to specify the
+                       option and its argument as separate --rsync-options, like this:
+
+                               --rsync-option --exclude --rsync-option /proc
+               [/help]
+       [option intervals]
+               summary = Fine tuning the number of snapshots per time unit
+               flag ignored
+       [option unit-interval]
+               short_opt = u
+               summary = the duration of a unit interval
+               typestr = days
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 4
+               [help]
+                       Increasing this number instructs dss to create fewer snapshots per
+                       time unit while the number of snapshots to keep stays the same.
+               [/help]
+       [option num-intervals]
+               short_opt = n
+               summary = the number of unit intervals
+               typestr = num
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 5
+               [help]
+                       Increasing this number by one doubles the total number of
+                       snapshots.
+               [/help]
+       [option hooks]
+               summary = Commands to be run on certain events
+               flag ignored
+               [help]
+                       All hooks default to "true". That is, the true(1) utility (which
+                       always returns with exit code zero) is executed if the hook command
+                       is not specified.
+               [/help]
+       [option pre-create-hook]
+               short_opt = r
+               summary = executed before a snapshot is created
+               typestr = command
+               arg_info = required_arg
+               arg_type = string
+               default_val = true
+               [help]
+                       This command is executed before dss runs rsync to create a new
+                       snapshot. If the command returns with a non-zero exit status, no
+                       snapshot will be created and the operation is retried later.
+
+                       For example, the command could execute a script that checks whether
+                       all snapshot-related file systems are mounted.
+
+                       Another possible application of the pre-create hook is to return
+                       non-zero during office hours in order to not slow down the file
+                       systems by taking snapshots.
+               [/help]
+       [option post-create-hook]
+               summary = executed after a snapshot has been created
+               typestr = command
+               arg_info = required_arg
+               arg_type = string
+               default_val = true
+               [help]
+                       This is only executed if a snapshot has successfully been created. The
+                       full path of the newly created snapshot is passed to the hook as the
+                       first argument. The exit code of this hook is ignored.
+
+                       For instance this hook could count the number of files per user
+                       and/or compute disk usage patterns to be stored in a database for
+                       further analysis.
+               [/help]
+       [option pre-remove-hook]
+               summary = executed before a snapshot is removed
+               typestr = command
+               arg_info = required_arg
+               arg_type = string
+               default_val = true
+               [help]
+                       The full path to the snapshot which is about to be removed is passed
+                       to the command as the first argument. If the command returns with
+                       a non-zero exit status, the snapshot is not going to be removed and
+                       the operation is retried later.
+
+                       For example, one could execute a script that checks whether the
+                       snapshot to be deleted is currently used by another process, e.g. by
+                       a tape-based backup system that runs concurrently to dss.
+
+                       Another possible application of this is to record disk-usage
+                       patterns before and after snapshot removal.
+               [/help]
+       [option post-remove-hook]
+               summary = executed after snapshot removal
+               typestr = command
+               arg_info = required_arg
+               arg_type = string
+               default_val = true
+               [help]
+                       As for the pre-remove hook, the full path of the removed snapshot is
+                       passed to the hook as the first argument. The exit code of this hook
+                       is ignored.
+               [/help]
+       [option exit-hook]
+               summary = executed before the run command exits
+               typestr = command
+               arg_info = required_arg
+               arg_type = string
+               default_val = true
+               [help]
+                       This hook is only relevant to the run subcommand. It is executed just
+                       before dss terminates. The reason for termination is passed as the
+                       first argument.
+
+                       One possible application for this hook is to send email to the system
+                       administrator to let her know that no more snapshots are going to
+                       be created.
+               [/help]
+
+       [option disk-space-monitoring]
+               summary = Disk space monitoring
+               flag ignored
+               [help]
+                       The options of this section control the aggressiveness of snapshot
+                       removal. That is, they define under which circumstances existing
+                       snapshots are removed. These options are only relevant to the run
+                       and the prune subcommands.
+               [/help]
+       [option min-free-mb]
+               short_opt = m
+               summary = minimal amount of free disk space
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = megabytes
+               default_val = 100
+               [help]
+                       If disk space on the file system containing the destination
+                       directory gets low, the run subcommand suspends the currently
+                       running rsync process and starts to remove snapshots in order to
+                       free disk space. This option specifies the minimal amount of free
+                       disk space. If less than the given number of megabytes is available,
+                       snapshots are being deleted. See also the --min_free_percent and the
+                       min-free-percent-inodes options below.
+
+                       A value of zero deactivates this check.
+               [/help]
+       [option min-free-percent]
+               short_opt = p
+               summary = minimal percentage of free disk space
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = percent
+               default_val = 2
+               [help]
+                       This is like --min-free-mb but the amount of free disk space
+                       is specified as a percentage. It is not recommended to set both
+                       --min-free-mb and --min-free-percent to zero as this will cause your
+                       file system to fill up quickly.
+               [/help]
+       [option min-free-percent-inodes]
+               short_opt = i
+               summary = minimal percent of free inodes
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = percent
+               default_val = 0
+               [help]
+                       The minimum amount of free inodes on the file system containing the
+                       destination dir. If the percentage of free inodes drops below the
+                       given value, snapshot removal kicks in like in case of low disk space.
+
+                       The number of free inodes is determined from the f_ffree field of
+                       the statvfs structure. However, some file systems set this field to
+                       zero, indicating that the number of inodes is basically unlimited.
+                       Moreover it is not possible to reliably detect whether this is the
+                       case. Therefore this feature is disabled by default. It's safe to
+                       enable it for ext2/ext3/ext4 file systems on linux though.
+
+                       A value of zero (the default) deactivates this check.
+               [/help]
+       [option keep-redundant]
+               short_opt = k
+               summary = prune by disk space only
+               [help]
+                       By default, redundant and outdated snapshots are removed automatically
+                       to keep the number of snapshots in harmony with the configured
+                       policy. If this flag is given, dss removes such snapshots only if
+                       disk space or number of free inodes becomes low.
+               [/help]
+       [option min-complete]
+               summary = minimal number of complete snapshots to keep
+               arg_info = optional_arg
+               arg_type = uint32
+               typestr = num
+               default_val = 1
+               [help]
+                       This option is only relevant if snapshots must be deleted because
+                       disk space gets low.
+
+                       dss refuses to remove old snapshots if there are fewer complete
+                       snapshots left than the given number. The default value of one
+                       guarantees that at least one complete snapshot is available at
+                       all times.
+
+                       If only <num> complete snapshots are left, and there is not enough
+                       disk space available for another snapshot, the program terminates
+                       with a "No space left on device" error.
+               [/help]
+
+[introduction]
+       dss supports the subcommands described below. If no subcommand is
+       given, the list of available subcommands is shown and the program
+       terminates successfully without performing any further action.
+[/introduction]
+
+[subcommand run]
+       purpose = start creating and pruning snapshots
+       [description]
+               This is the main mode of operation. Snapshots are created in an endless
+               loop as needed and pruned automatically. The loop only terminates on
+               fatal errors or if a terminating signal was received. See also the
+               --exit-hook option.
+       [/description]
+       [option daemon]
+               short_opt = d
+               summary = run as background daemon
+               [help]
+                       If this option is given, the dss command detaches from the console
+                       and continues to run in the background. It is not possible to let
+                       a daemonized process re-attach to the console by editing the config
+                       file and sending SIGHUP. However, log output may be redirected to a
+                       different file in this way.
+
+                       See --logfile.
+               [/help]
+       [option logfile]
+               short_opt = l
+               summary = where to write log output
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               default_val = /dev/null
+               [help]
+                       This option is only honored if --daemon is given, in which case
+                       log messages go to the given file. Otherwise the option is silently
+                       ignored and log output is written to stderr.
+               [/help]
+       [option max-rsync-errors]
+               summary = terminate after this many rsync failures
+               typestr = count
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 10
+               [help]
+                       If the rsync process exits with a fatal error, dss restarts the command
+                       in the hope that the problem is transient and subsequent rsync runs
+                       succeed. After the given number of consecutive rsync error exits,
+                       however, dss gives up, executes the exit hook and terminates. Set
+                       this to zero if dss should exit immediately on the first rsync error.
+
+                       The only non-fatal error is when rsync exits with code 24. This
+                       indicates a partial transfer due to vanished source files and happens
+                       frequently when snapshotting a directory which is concurrently being
+                       modified.
+               [/help]
+[subcommand create]
+       purpose = execute rsync once to create a new snapshot
+       [description]
+               This command does not check the amount free disk space. The pre-create
+               and post-create hooks are honored, however.
+
+               Specify --dry-run to see the rsync command which is executed to create
+               snapshots.
+       [/description]
+[subcommand prune]
+       purpose = remove snapshots
+       [description]
+               A snapshot is said to be (a) outdated if its interval number is greater
+               or equal than the specified number of unit intervals, (b) redundant if
+               the interval it belongs to contains more than the configured number of
+               snapshots, and (c) orphaned if it is incomplete and not being created
+               or deleted. All other snapshots are called regular.
+
+               Unless --dry-run is given, which just prints the snapshot that would be
+               removed, this subcommand gets rid of non-regular snapshots.  At most
+               one snapshot is removed per invocation. If no such snapshot exists
+               and disk space is low, the subcommand also removes regular snapshots,
+               always picking the oldest one.
+
+               The subcommand fails if there is another dss "run" process.
+       [/description]
+       [option disk-space]
+               summary = act as if free disk space was high/low
+               arg_info = required_arg
+               arg_type = string
+               typestr = mode
+               values = {
+                       FDS_CHECK = "check",
+                       FDS_HIGH = "high",
+                       FDS_LOW = "low"
+               }
+               default_val = check
+               [help]
+                       By default, free disk space is checked and even regular snapshots
+                       become candidates for removal if disk space is low. This option
+                       overrides the result of the check.
+               [/help]
+[subcommand ls]
+       purpose = print the list of all snapshots
+       [description]
+               The list contains all existing snapshots, no matter of their state.
+               Incomplete snapshots and snapshots being deleted will also be listed.
+       [/description]
+[subcommand kill]
+       purpose = send a signal to a running dss process
+       [description]
+               This sends a signal to the dss process that corresponds to the given
+               config file. If --dry-run is given, the PID of the dss process is
+               written to stdout, but no signal is sent.
+       [/description]
+       [option signal]
+               short_opt = s
+               summary = send the given signal rather than SIGTERM
+               typestr = signal
+               arg_info = required_arg
+               arg_type = string
+               default_val = SIGTERM
+               [help]
+                       Like for kill(1), alternate signals may be specified in three ways: as
+                       a signal number (e.g., 9), the signal name (e.g., KILL), or the signal
+                       name prefixed with "SIG" (e.g., SIGKILL). In the latter two forms,
+                       the signal name and the prefix are case insensitive, so "sigkill"
+                       works as well.
+
+                       Sending SIGHUP causes the running dss process to reload its config file.
+               [/help]
+       [option wait]
+               short_opt = w
+               summary = wait until the signalled process has terminated
+               [help]
+                       This option is handy for system shutdown scripts which would like
+                       to terminate the dss daemon process.
+
+                       Without --wait the dss process which executes the kill subcommand
+                       exits right after the kill(2) system call returns. At this point the
+                       signalled process might still be alive (even if SIGKILL was sent).
+                       If --wait is given, the process waits until the signalled process
+                       has terminated or the timeout expires.
+
+                       If --wait is not given, the kill subcommand exits successfully if
+                       and only if the signal was sent (i.e., if there exists another dss
+                       process to receive the signal). With --wait it exits successfully
+                       if, additionally, the signalled process has terminated before the
+                       timeout expires.
+
+                       It makes only sense to use the option for signals which terminate dss.
+               [/help]
+[subcommand configtest]
+       purpose = run a configuration file syntax test
+       [description]
+               This command checks the command line options and the configuration
+               file for syntactic correctness. It either reports "Syntax Ok" and
+               exits successfully or prints information about the first syntax error
+               detected and terminates with exit code 1.
+       [/description]
+[subcommand help]
+       purpose = list available subcommands or print subcommand-specific help
+       non-opts-name = [subcommand]
+       [description]
+               If the optional subcommand argument is given, the help text of that
+               subcommand is shown. Without the argument the available subcommands
+               are listed instead.
+       [/description]
+       [option long]
+               short_opt = l
+               summary = show the long help text of a subcommand
+               [help]
+                       If this option is given, the command also shows the description of
+                       the subcommand and the help text of each option, Otherwise only the
+                       purpose, the synopsis and the option list of the subcommand is shown.
+                       If no subcommand is supplied, the option has no effect.
+               [/help]
+
+[section examples]
+       Suppose you'd like to create snapshots of the existing directory
+       .I /foo/bar
+       in the directory
+       .IR /baz/qux .
+       Create the config file
+       .I ~/.dssrc
+       containing
+       the values for the source and the destination directories
+       as follows:
+
+       .RS 6
+       .EX
+               echo 'source-dir "/foo/bar"' > ~/.dssrc
+               echo 'dest-dir "/baz/qux"' >> ~/.dssrc
+       .EE
+       .RE
+
+       Then execute the commands
+
+       .RS 6
+       .EX
+               mkdir /baz/qux
+               dss run
+       .EE
+       .RE
+
+       To print the list of all snapshots created so far, run
+       .IR dss\~ls .
+
+       The second example involves a slightly more sophisticated config
+       file. It instructs dss to exclude everything which matches at least
+       one pattern of the given exclude file, prevents rsync from crossing
+       file system boundaries and increases the number of snapshots.
+
+       .RS 6
+       .EX
+               source-dir "/foo/bar"
+               dest-dir "/baz/qux"
+               # exclude files matching patterns in /etc/dss.exclude
+               rsync-option \-\-exclude\-from=/etc/dss.exclude
+               # don't cross filesystem boundaries
+               rsync-option \-\-one\-file\-system
+               # maintain 2^6 - 1 = 63 snapshots
+               num-intervals "6"
+       .EE
+       .RE
+
+       The
+       .I /etc/dss.exclude
+       file could look like this (see rsync(1) for more examples)
+
+       .RS 6
+       .EX
+                - /proc
+                - /**/tmp/
+       .EE
+       .RE
+[/section]
+
+[section snapshot distribution]
+       The age of a snapshot is measured in terms of unit
+       intervals. Given
+       the duration
+       .I u
+       of a unit
+       interval and the number
+       .I n
+       of unit intervals to consider, dss tries to keep
+       .I 2^(n-k-1)
+       snapshots in interval
+       .IR k ,
+       where the interval number
+       .I k
+       counts
+       from zero to
+       .IR n-1 ,
+       with zero being the most recent unit interval. Snapshots older than
+       .I n
+       unit intervals are regarded as outdated and are removed. There are
+       .I 2^n-1
+       snapshots in total.
+
+       For example, with four unit intervals, the 2^4 - 1 = 15 snapshots
+       are distributed as follows.
+
+       .TS
+       allbox;
+       lb r r r r
+       lb r r r r.
+       Interval        3       2       1       0
+       Snapshots       \~\~\~\~*\~\~\~ \~\~*\~\~*\~\~  \~*\~*\~*\~*    ********
+       .TE
+
+       Note that for this to work out the system must be fast enough to
+       create at least
+       .I 2^(n-1)
+       snapshots per unit interval because this is the number of snapshots
+       in interval zero.
+[/section]
+
+[section copyright]
+       Written by AUTHOR()
+       .br
+       Copyright (C) 2008 - present AUTHOR()
+       .br
+       License: LICENSE()
+       .br
+       This is free software: you are free to change and redistribute it.
+       .br
+       There is NO WARRANTY, to the extent permitted by law.
+       .P
+       Project web page:
+       .UR URL()
+       .UE
+       .br
+       Git clone «URL»:
+       .UR CLONE_URL()
+       .UE
+       .br
+       Gitweb:
+       .UR GITWEB_URL()
+       .UE
+       .br
+       Author's home page:
+       .UR HOME_URL()
+       .UE
+       .br
+       Report bugs to
+       .MT EMAIL()
+       AUTHOR()
+       .ME
+[/section]
+
+[section see also]
+       .BR ssh (1) ,
+       .BR rsync (1)
+[/section]
diff --git a/index.html.in b/index.html.in
deleted file mode 100644 (file)
index b505761..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-   "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
-       <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
-       <title>DSS - The dyadic snapshot scheduler</title>
-       <LINK href="dss.css" REL="stylesheet" TYPE="text/css">
-       <link rel="shortcut icon" href="dss.ico">
-</head>
-
-<body>
-       <table>
-               <tr>
-                       <td>
-                               <img src="dss.svg" alt="dss">
-                       </td>
-                       <td>
-                               <h1>The dyadic snapshot scheduler</h1>
-                       </td>
-               </tr>
-       </table>
-
-       <hr>
-
-       [<a href="#readme">README</a>]
-       [<a href="#news">NEWS</a>]
-       [<a href="#download">Download</a>]
-       [<a href="#install">INSTALL</a>]
-       [<a href="#license">License</a>]
-       [<a href="#contact">Contact</a>]
-       [<a href="#manpage">Man page</a>]
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="readme">README</a>
-               </h2>
-       </center>
-
-       @README@
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="news">NEWS</a>
-               </h2>
-       </center>
-
-       @NEWS@
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="download">Download</a>
-               </h2>
-       </center>
-
-       <p>Only the source code is available for download. Use
-
-               <a href="http://www.kernel.org/pub/software/scm/git/docs/">git</a>
-
-       to clone the dss repository by executing</p>
-
-       <center>
-
-               <tt>git clone git://git.tuebingen.mpg.de/dss</tt>
-
-       </center>
-
-       <p> or grab the
-
-       <a href="http://git.tuebingen.mpg.de/cgi-bin/gitweb.cgi?p=dss.git;a=snapshot;h=HEAD;sf=tgz">tarball</a>
-
-       of the current master branch. If you prefer to download the tarball of
-       the latest release, select the corresponding <em>snapshot</em>
-       link on the
-
-               <a href="http://git.tuebingen.mpg.de/dss.git">dss gitweb page</a>
-
-       </p>
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="install">INSTALL</a>
-               </h2>
-       </center>
-
-       @INSTALL@
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="license">License</a>
-               </h2>
-       </center>
-
-       <p>dss is open source software, licensed under the
-
-       <a
-       href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU
-       General Public License, Version 2</a>.</p>
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="contact">Contact</a>
-               </h2>
-       </center>
-
-       <p> Email: Andr&eacute; Noll, <a
-       href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>,
-       Homepage: <a
-       href="http://people.tuebingen.mpg.de/maan/">http://people.tuebingen.mpg.de/maan/</a>
-       </p>
-
-       Comments and bug reports are welcome. Please provide
-       enough info such as the version of dss you are using and
-       relevant parts of the logs. Including the string [dss] in
-       the subject line is also a good idea.
-
-       <hr>
-
-       <center>
-               <h2>
-                       <a name="manpage">Man page</a>
-               </h2>
-       </center>
-
-       @MAN_PAGE@
-
-</body> </html>
-
diff --git a/index.html.m4 b/index.html.m4
new file mode 100644 (file)
index 0000000..849e203
--- /dev/null
@@ -0,0 +1,71 @@
+dnl SPDX-License-Identifier: GPL-2.0
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+       <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>
+       <title>PACKAGE() - SLOGAN()</title>
+       <style type='text/css'>
+               body {
+                       text-align: justify;
+                       background-color: #ffffff;
+                       color: #333333;
+                       padding: 0px 30px 0px 30px;
+               }
+               a {
+                       color: #4444ff;
+               }
+               pre,code {
+                       font-size: 110%;
+               }
+       </style>
+       <link rel="shortcut icon" href="dss.ico">
+</head>
+
+<body>
+       <table>
+               <tr>
+                       <td> include(«dss.svg») </td>
+                       <td> <h1>SLOGAN()</h1> </td>
+               </tr>
+       </table>
+
+       <p> DESCRIPTION1() </p>
+       <p> DESCRIPTION2() </p>
+       <p> DESCRIPTION3() </p>
+
+       <h2> <a name="installation">Installation</a> </h2>
+
+       <p> The easiest way to install PACKAGE() is to download and run
+       this pre-compiled <a href="PACKAGE()">static binary</a> for x86. It
+       should work fine on all Linux distributions but it won't work on arm
+       systems. </p>
+
+       <p> To install from source, you first need to install the dependencies:
+       <code>apt-get install gcc rsync liblopsub-dev m4</code>. </p>
+
+       <p> To download the source code, run <code>git clone
+       CLONE_URL()</code>, or grab a current tarball from the <a
+       href="GITWEB_URL()">gitweb page</a> and unpack it. </p>
+
+       <p> To build the PACKAGE() executable and the man page, enter the
+       PACKAGE() source directory and type <code>make</code>. To install
+       both files run <code>sudo make install</code>. </p>
+
+       <p> Try <code>dss -h</code> for an overview of all supported command
+       line options, <code>dss --detailed-help</code> for the long help text,
+       or <code>man dss</code> to see the man page which contains example
+       configurations. </p>
+
+       <h2> <a name="resources">Resources</a> </h2>
+       <ul>
+               <li> <a href="PACKAGE_HOMEPAGE()">PACKAGE() home page</a> </li>
+               <li> <code>git clone CLONE_URL()</code> </li>
+               <li> <a href="GITWEB_URL()">Gitweb</a> </li>
+               <li> <a href="HOME_URL()">Author home page</a> </li>
+               <li> <a href="PACKAGE().1.html">manual page</a> </li>
+               <li> Contact: <a href="mailto:EMAIL()">AUTHOR() &lt;EMAIL()&gt;</a> </li>
+               <li> License: <a href="LICENSE_URL()">LICENSE()</a> </li>
+       </ul>
+
+</body> </html>
diff --git a/log.h b/log.h
index 7dd50434313b5acfa9009878089aae785ec27846..47a3b7ac0e5786c7721d8d262252642da7f993bb 100644 (file)
--- a/log.h
+++ b/log.h
@@ -15,9 +15,6 @@
 /** last message before exit */
 #define EMERG 7
 
-/** Log messages with lower priority than that will not be compiled in. */
-#define COMPILE_TIME_LOGLEVEL 0
-
 /** Not all compilers support __func__ or an equivalent. */
 #if (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) && !defined(__GNUC__)
 # if defined(_MSC_VER) && _MSC_VER >= 1300
 # endif
 #endif
 
-/** \cond */
-#if DEBUG > COMPILE_TIME_LOGLEVEL
 #define DSS_DEBUG_LOG(args) \
        do { \
                dss_log_set_params(DEBUG, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_DEBUG_LOG(args) do {;} while (0)
-#endif
 
-#if INFO > COMPILE_TIME_LOGLEVEL
 #define DSS_INFO_LOG(args) \
        do { \
                dss_log_set_params(INFO, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_INFO_LOG(args) do {;} while (0)
-#endif
 
-#if NOTICE > COMPILE_TIME_LOGLEVEL
 #define DSS_NOTICE_LOG(args) \
        do { \
                dss_log_set_params(NOTICE, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_NOTICE_LOG(args) do {;} while (0)
-#endif
 
-#if WARNING > COMPILE_TIME_LOGLEVEL
 #define DSS_WARNING_LOG(args) \
        do { \
                dss_log_set_params(WARNING, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_WARNING_LOG(args) do {;} while (0)
-#endif
 
-#if ERROR > COMPILE_TIME_LOGLEVEL
 #define DSS_ERROR_LOG(args) \
        do { \
                dss_log_set_params(ERROR, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_ERROR_LOG(args) do {;} while (0)
-#endif
 
-#if CRIT > COMPILE_TIME_LOGLEVEL
 #define DSS_CRIT_LOG(args) \
        do { \
                dss_log_set_params(CRIT, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_CRIT_LOG(args) do {;} while (0)
-#endif
 
-#if EMERG > COMPILE_TIME_LOGLEVEL
 #define DSS_EMERG_LOG(args) \
        do { \
                dss_log_set_params(EMERG, __FILE__, __LINE__, __func__); \
                dss_log args ; \
        } while (0)
-#else
-#define DSS_EMERG_LOG(args)
-#endif
diff --git a/str.c b/str.c
index 13776300d6ca1aa62479df6be4428a5ba8d836a0..eeb635cfa97dd7b09096b405df651bcb52a5deb1 100644 (file)
--- a/str.c
+++ b/str.c
@@ -151,16 +151,19 @@ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
        }
 }
 
-/**
+/*
  * Get the home directory of the current user.
  *
- * \return A dynamically allocated string that must be freed by the caller. If
- * the home directory could not be found, this function returns "/tmp".
+ * Returns a dynamically allocated string that must be freed by the caller. If
+ * the HOME environment variable is unset or empty, the function aborts.
  */
 __must_check __malloc char *get_homedir(void)
 {
-       struct passwd *pw = getpwuid(getuid());
-       return dss_strdup(pw? pw->pw_dir : "/tmp");
+       const char *home = getenv("HOME");
+       if (home && *home)
+               return dss_strdup(home);
+       DSS_EMERG_LOG(("fatal: HOME is unset or empty\n"));
+       exit(EXIT_FAILURE);
 }
 
 /**
@@ -192,19 +195,18 @@ int dss_atoi64(const char *str, int64_t *value)
        return 1;
 }
 
-/**
+/*
  * Get the logname of the current user.
  *
- * \return A dynamically allocated string that must be freed by the caller. On
- * errors, the string "unknown user" is returned, i.e. this function never
- * returns \p NULL.
- *
- * \sa getpwuid(3).
+ * Returns a dynamically allocated string that must be freed by the caller. On
+ * errors, the string "unknown" is returned. This function never returns NULL.
  */
 __must_check __malloc char *dss_logname(void)
 {
-       struct passwd *pw = getpwuid(getuid());
-       return dss_strdup(pw? pw->pw_name : "unknown_user");
+       const char *logname = getenv("LOGNAME");
+       if (!logname && !*logname)
+               logname = "unknown";
+       return dss_strdup(logname);
 }
 
 /**
diff --git a/version-gen.sh b/version-gen.sh
new file mode 100755 (executable)
index 0000000..ba4af3e
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-3.0-only
+
+package="$1"
+version_file="$2"
+
+ver='unnamed_version'
+# First try git, then gitweb, then default.
+if [ -e '.git' -o -e '../.git' ]; then
+       git_ver=$(git describe --abbrev=4 --always HEAD 2>/dev/null)
+       [ -z "$git_ver" ] && git_ver="$ver"
+       # update stat information in index to match working tree
+       git update-index -q --refresh > /dev/null
+       # if there are differences (exit code 1), the working tree is dirty
+       git diff-index --quiet HEAD || git_ver=$git_ver-dirty
+       ver=$git_ver
+elif [ "${PWD%%-*}" = $package- ]; then
+       ver=${PWD##*/$package-}
+fi
+ver=${ver#v}
+
+echo "$ver"
+[ -z "${version_file}" ] && exit 0
+# update version file if necessary
+content="const char *${package}_version(void) {return \"$ver\";};"
+[ -r "$version_file" ] && echo "$content" | cmp -s - $version_file && exit 0
+[ "$version_file" = "${version_file%/*}" ] || mkdir -p ${version_file%/*}
+printf '%s\n' "$content" > $version_file