Merge branch 'maint'
authorAndre Noll <maan@tuebingen.mpg.de>
Mon, 13 Nov 2017 16:23:14 +0000 (17:23 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Mon, 13 Nov 2017 16:23:14 +0000 (17:23 +0100)
* maint:
  Fix snapshot removal reason.

17 files changed:
.gitignore
INSTALL
Makefile
NEWS
README
daemon.c
daemon.h
dss.1.inc [deleted file]
dss.c
dss.dia [deleted file]
dss.ggo [deleted file]
dss.suite [new file with mode: 0644]
err.h
file.c
file.h
ipc.c
mklogo [new file with mode: 0755]

index 9a6163d..1bb2a25 100644 (file)
@@ -1,6 +1,9 @@
 Makefile.deps
-*.[oa]
-cmdline.[ch]
+*.o
+*.swp
+dss.lsg.*
 dss
 dss.1
 dss.1.html
+dss.png
+index.html
diff --git a/INSTALL b/INSTALL
index db3d1c5..09d8505 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -1,27 +1,30 @@
+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 copy it
-to some directory that is included in your PATH, e.g. to `$HOME/bin`
-or to `/usr/local/bin`.
+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:
 
-Note that [gnu
-gengetopt](https://www.gnu.org/software/gengetopt/gengetopt.html)
-is required to compile dss.
+               make CPPFLAGS=-I$HOME/lopsub/include LDFLAGS=-L$HOME/lopsub/lib
+
+Then type
 
-Optionally, type
+               sudo make install
 
-                make man
+to install in /usr/local, or
 
-to create the man page of dss. This invokes help2man so make sure
-that help2man is installed on your system. Note that the man page is
-just the nroff variant of the output of `dss --detailed-help`.
+               make install PREFIX=/somewhere/else
 
-dss is known to compile on Linux, MacOS, Solaris, FreeBSD and
-NetBSD. However, it is run-tested only on Linux.
+to install in /somewhere/else.
 
-Also make sure that [rsync](http://rsync.samba.org/) is installed on
+Also make sure that [rsync](https://rsync.samba.org/) is installed on
 your system. Version 2.6.1 or newer is required.
 
 Examples:
@@ -48,11 +51,11 @@ as follows:
 Then execute the commands
 
                mkdir /baz/qux
-               dss --run
+               dss run
 
 In order to print the list of all snapshots created so far, use
 
-               dss --ls
+               dss ls
 
 Yes, it's really that easy.
 
@@ -67,7 +70,7 @@ system boundaries and increases the number of snapshots.
                rsync-option "--exclude-from=/etc/dss.exclude"
                # don't cross filesystem boundaries
                rsync-option "--one-file-system"
-               # maintain 2^6 - 1 = 63 snaphots
+               # maintain 2^6 - 1 = 63 snapshots
                num-intervals "6"
 
 The /etc/dss.exclude file could look like this (see rsync(1) for
index 8adb38a..f5c34af 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,51 +1,67 @@
-dss_objects := cmdline.o dss.o str.o file.o exec.o sig.o daemon.o df.o tv.o snap.o ipc.o
-all: dss
+PREFIX ?= /usr/local
+INSTALL ?= install
+INSTALL_PROGRAM ?= $(INSTALL)
+INSTALL_DATA ?= $(INSTALL) -m 644
+MKDIR_P := mkdir -p
+
+VERSION_STRING = 0.1.7
+
+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
 
-DEBUG_CFLAGS ?=
-DEBUG_CFLAGS += -Wno-sign-compare -g -Wunused -Wundef
-DEBUG_CFLAGS += -Wredundant-decls
-CFLAGS ?=
-CFLAGS += -Os
-CFLAGS += -Wall
-CFLAGS += -Wuninitialized
-CFLAGS += -Wchar-subscripts
-CFLAGS += -Wformat-security
-CFLAGS += -Werror-implicit-function-declaration
-CFLAGS += -Wmissing-format-attribute
-CFLAGS += -Wunused-macros
-CFLAGS += -Wunused-parameter
-CFLAGS += -Wbad-function-cast
-CFLAGS += -Wshadow
+DSS_CPPFLAGS := -DVERSION_STRING='"$(VERSION_STRING)"'
+DSS_CPPFLAGS += -Wunused-macros
+
+DSS_CFLAGS := -Wno-sign-compare -g -Wunused -Wundef
+DSS_CFLAGS += -Wredundant-decls
+DSS_CFLAGS += -Os
+DSS_CFLAGS += -Wall
+DSS_CFLAGS += -Wuninitialized
+DSS_CFLAGS += -Wchar-subscripts
+DSS_CFLAGS += -Wformat-security
+DSS_CFLAGS += -Werror-implicit-function-declaration
+DSS_CFLAGS += -Wmissing-format-attribute
+DSS_CFLAGS += -Wunused-parameter
+DSS_CFLAGS += -Wbad-function-cast
+DSS_CFLAGS += -Wshadow
 
 Makefile.deps: $(wildcard *.c *.h)
-       gcc -MM -MG *.c > $@
+       $(CC) -MM -MG $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) *.c > $@
 
 -include Makefile.deps
 
 dss: $(dss_objects)
-       $(CC) -o $@ $(dss_objects)
-
-cmdline.o: cmdline.c cmdline.h
-       $(CC) -c $(CFLAGS) $<
+       $(CC) -o $@ $(dss_objects) $(LDFLAGS) -llopsub
 
 %.o: %.c Makefile
-       $(CC) -c $(CFLAGS) $(DEBUG_CFLAGS) $<
-
-%.png: %.dia
-       dia -e $@ -t png $<
+       $(CC) -c $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) $<
 
-cmdline.c cmdline.h: dss.ggo
-       gengetopt --conf-parser < $<
+dss.png: mklogo
+       ./mklogo > $@
 
-dss.1: dss dss.1.inc
-       help2man -h --detailed-help --include dss.1.inc -N ./$< > $@
+%.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' > $@
 
 clean:
-       rm -f *.o dss dss.1 dss.1.html Makefile.deps *.png *~ cmdline.c cmdline.h index.html
+       rm -f *.o dss dss.1 dss.1.html Makefile.deps *.png *~ index.html dss.lsg.h dss.lsg.c
+
+ifneq ($(findstring strip, $(MAKECMDGOALS)),)
+       strip_option := -s
+endif
+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)
 
 index.html: dss.1.html index.html.in INSTALL README NEWS
        sed -e '/@README@/,$$d' index.html.in > $@
diff --git a/NEWS b/NEWS
index 1532320..de1dca6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,28 @@
+-----------------------
+x.y.z (to be announced)
+-----------------------
+
+ - Improved error diagnostics for the kill subcommand.
+
+ - The --no-resume option has been removed.
+
+ - The gengetopt option parser has been replaced by the
+ [lopsub](http://people.tuebingen.mpg.de/maan/lopsub) library. Hence
+ lopsub must be installed to compile this package. Also help2man is
+ no longer required since lopsub has built-in roff support.
+
+ - "make install" will install the executable and the man page.
+
+ - In run mode, dss no longer exits successfully if another instance
+ is already running.
+
+ - New option --checksum to let rsync compute checksums occasionally.
+
+ - CFLAGS, CPPFLAGS and LDFLAGS can now be used to override the flags
+ of the build system.
+
+ - The dss logo is now created with ImageMagick rather than dia.
+
 ------------------
 0.1.7 (2017-04-17)
 ------------------
diff --git a/README b/README
index c5abb29..79b8889 100644 (file)
--- a/README
+++ b/README
@@ -11,7 +11,7 @@ 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 snaphot looks
+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.
index 86e8906..0b201a8 100644 (file)
--- a/daemon.c
+++ b/daemon.c
  *
  * \sa fork(2), setsid(2), dup(2).
  */
-void daemon_init(void)
+int daemon_init(void)
 {
        pid_t pid;
-       int null;
+       int null, fd[2];
 
        DSS_INFO_LOG(("daemonizing\n"));
+       if (pipe(fd) < 0)
+               goto err;
        pid = fork();
        if (pid < 0)
                goto err;
-       if (pid)
-               exit(EXIT_SUCCESS); /* parent exits */
+       if (pid) {
+               /*
+                * The parent process exits once it has received one byte from
+                * the reading end of the pipe. If the child exits before it
+                * was able to complete its setup (acquire the lock on the
+                * semaphore), the read() below will return zero. In this case
+                * we let the parent die unsuccessfully.
+                */
+               char c;
+               int ret;
+               close(fd[1]);
+               ret = read(fd[0], &c, 1);
+               if (ret <= 0) {
+                       DSS_EMERG_LOG(("child terminated unexpectedly\n"));
+                       exit(EXIT_FAILURE);
+               }
+               exit(EXIT_SUCCESS);
+       }
+       close(fd[0]);
        /* become session leader */
        if (setsid() < 0)
                goto err;
@@ -56,7 +75,7 @@ void daemon_init(void)
        if (dup2(null, STDERR_FILENO) < 0)
                goto err;
        close(null);
-       return;
+       return fd[1];
 err:
        DSS_EMERG_LOG(("fatal: %s\n", strerror(errno)));
        exit(EXIT_FAILURE);
index aead8e8..e36e37c 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -1,7 +1,7 @@
 
 /** \file daemon.h exported symbols from daemon.c */
 
-void daemon_init(void);
+int daemon_init(void);
 FILE *open_log(const char *logfile_name);
 void close_log(FILE* logfile);
 void log_welcome(int loglevel);
diff --git a/dss.1.inc b/dss.1.inc
deleted file mode 100644 (file)
index 74bd4fb..0000000
--- a/dss.1.inc
+++ /dev/null
@@ -1,5 +0,0 @@
-[NAME]
-dss \- dyadic snapshot scheduler
-[SEE ALSO]
-.BR ssh (1) ,
-.BR rsync (1)
diff --git a/dss.c b/dss.c
index a895019..ecd6ac3 100644 (file)
--- a/dss.c
+++ b/dss.c
 #include <sys/wait.h>
 #include <fnmatch.h>
 #include <limits.h>
-
+#include <fcntl.h>
+#include <lopsub.h>
+#include <sys/mman.h>
 
 #include "gcc-compat.h"
-#include "cmdline.h"
 #include "log.h"
 #include "str.h"
 #include "err.h"
 #include "tv.h"
 #include "snap.h"
 #include "ipc.h"
+#include "dss.lsg.h"
+
+#define CMD_PTR(_cname) lls_cmd(LSG_DSS_CMD_ ## _cname, dss_suite)
+#define OPT_RESULT(_cname, _oname) (lls_opt_result(\
+       LSG_DSS_ ## _cname ## _OPT_ ## _oname, (CMD_PTR(_cname) == CMD_PTR(DSS))? lpr : sublpr))
+#define OPT_GIVEN(_cname, _oname) (lls_opt_given(OPT_RESULT(_cname, _oname)))
+#define OPT_STRING_VAL(_cname, _oname) (lls_string_val(0, \
+       OPT_RESULT(_cname, _oname)))
+#define OPT_UINT32_VAL(_cname, _oname) (lls_uint32_val(0, \
+               OPT_RESULT(_cname, _oname)))
+
+struct dss_user_data {int (*handler)(void);};
+#define EXPORT_CMD_HANDLER(_cmd) const struct dss_user_data \
+       lsg_dss_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
+
+/*
+ * Command line and active options. We need to keep a copy of the parsed
+ * command line options for the SIGHUP case where we merge the command line
+ * options and the new config file options.
+ */
+static struct lls_parse_result *cmdline_lpr, *lpr;
 
-/** Command line and config file options. */
-static struct gengetopt_args_info conf;
+/** Parsed subcommand options. */
+static struct lls_parse_result *cmdline_sublpr, *sublpr;
+/** Wether daemon_init() was called. */
+static bool daemonized;
 /** Non-NULL if we log to a file. */
 static FILE *logfile;
 /** The read end of the signal pipe */
@@ -75,6 +101,7 @@ static const char *hook_status_description[] = {HOOK_STATUS_ARRAY};
 static int disk_space_low(struct disk_space *ds)
 {
        struct disk_space ds_struct;
+       uint32_t val;
 
        if (!ds) {
                int ret = get_disk_space(".", &ds_struct);
@@ -82,14 +109,17 @@ static int disk_space_low(struct disk_space *ds)
                        return ret;
                ds = &ds_struct;
        }
-       if (conf.min_free_mb_arg)
-               if (ds->free_mb < conf.min_free_mb_arg)
+       val = OPT_UINT32_VAL(DSS, MIN_FREE_MB);
+       if (val != 0)
+               if (ds->free_mb < val)
                        return 1;
-       if (conf.min_free_percent_arg)
-               if (ds->percent_free < conf.min_free_percent_arg)
+       val = OPT_UINT32_VAL(DSS, MIN_FREE_PERCENT);
+       if (val != 0)
+               if (ds->percent_free < val)
                        return 1;
-       if (conf.min_free_percent_inodes_arg)
-               if (ds->percent_free_inodes < conf.min_free_percent_inodes_arg)
+       val = OPT_UINT32_VAL(DSS, MIN_FREE_PERCENT_INODES);
+       if (val != 0)
+               if (ds->percent_free_inodes < val)
                        return 1;
        return 0;
 }
@@ -97,12 +127,13 @@ static int disk_space_low(struct disk_space *ds)
 static void dump_dss_config(const char *msg)
 {
        const char dash[] = "-----------------------------";
+       char *lopsub_dump;
        int ret;
        FILE *log = logfile? logfile : stderr;
        struct disk_space ds;
        int64_t now = get_current_time();
 
-       if (conf.loglevel_arg > INFO)
+       if (OPT_UINT32_VAL(DSS, LOGLEVEL) > INFO)
                return;
 
        fprintf(log, "%s <%s config> %s\n", dash, msg, dash);
@@ -118,8 +149,14 @@ static void dump_dss_config(const char *msg)
 
        /* we continue on errors from get_disk_space */
 
-       fprintf(log, "\n*** command line and config file options ***\n\n");
-       cmdline_parser_dump(log, &conf);
+       fprintf(log, "\n*** non-default options ***\n\n");
+       lopsub_dump = lls_dump_parse_result(lpr, CMD_PTR(DSS), true);
+       fprintf(log, "%s", lopsub_dump);
+       free(lopsub_dump);
+       fprintf(log, "\n*** non-default options for \"run\" ***\n\n");
+       lopsub_dump = lls_dump_parse_result(lpr, CMD_PTR(RUN), true);
+       fprintf(log, "%s", lopsub_dump);
+       free(lopsub_dump);
        fprintf(log, "\n*** internal state ***\n\n");
        fprintf(log,
                "pid: %d\n"
@@ -132,7 +169,7 @@ static void dump_dss_config(const char *msg)
                "num_consecutive_rsync_errors: %d\n"
                ,
                (int) getpid(),
-               logfile? conf.logfile_arg : "stderr",
+               logfile? OPT_STRING_VAL(RUN, LOGFILE) : "stderr",
                snapshot_currently_being_removed?
                        snapshot_currently_being_removed->name : "(none)",
                path_to_last_complete_snapshot?
@@ -172,28 +209,6 @@ static void dump_dss_config(const char *msg)
        fprintf(log, "%s </%s config> %s\n", dash, msg, dash);
 }
 
-/* a litte cpp magic helps to DRY */
-#define COMMANDS \
-       COMMAND(ls) \
-       COMMAND(create) \
-       COMMAND(prune) \
-       COMMAND(run) \
-       COMMAND(kill) \
-       COMMAND(reload) \
-
-#define COMMAND(x) static int com_ ##x(void);
-COMMANDS
-#undef COMMAND
-#define COMMAND(x) if (conf.x ##_given) return com_ ##x();
-static int call_command_handler(void)
-{
-       COMMANDS
-       DSS_EMERG_LOG(("BUG: did not find command handler\n"));
-       return -E_BUG;
-}
-#undef COMMAND
-#undef COMMANDS
-
 static int loglevel = -1;
 static const char *location_file = NULL;
 static int         location_line = -1;
@@ -222,15 +237,16 @@ __printf_1_2 void dss_log(const char* fmt,...)
        struct tm *tm;
        time_t t1;
        char str[255] = "";
+       int lpr_ll = lpr? OPT_UINT32_VAL(DSS, LOGLEVEL) : WARNING;
 
-       if (loglevel < conf.loglevel_arg)
+       if (loglevel < lpr_ll)
                return;
        outfd = logfile? logfile : stderr;
        time(&t1);
        tm = localtime(&t1);
        strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
        fprintf(outfd, "%s ", str);
-       if (conf.loglevel_arg <= INFO)
+       if (lpr_ll <= INFO)
                fprintf(outfd, "%i: ", loglevel);
 #ifdef DSS_NO_FUNC_NAMES
        fprintf(outfd, "%s:%d: ", location_file, location_line);
@@ -247,7 +263,7 @@ __printf_1_2 void dss_log(const char* fmt,...)
  */
 static __printf_1_2 void dss_msg(const char* fmt,...)
 {
-       FILE *outfd = conf.daemon_given? logfile : stdout;
+       FILE *outfd = logfile? logfile : stdout;
        va_list argp;
        va_start(argp, fmt);
        vfprintf(outfd, fmt, argp);
@@ -258,8 +274,8 @@ static char *get_config_file_name(void)
 {
        char *home, *config_file;
 
-       if (conf.config_file_given)
-               return dss_strdup(conf.config_file_arg);
+       if (OPT_GIVEN(DSS, CONFIG_FILE))
+               return dss_strdup(OPT_STRING_VAL(DSS, CONFIG_FILE));
        home = get_homedir();
        config_file = make_message("%s/.dssrc", home);
        free(home);
@@ -275,7 +291,7 @@ static int send_signal(int sig)
        free(config_file);
        if (ret < 0)
                return ret;
-       if (conf.dry_run_given) {
+       if (OPT_GIVEN(DSS, DRY_RUN)) {
                dss_msg("%d\n", (int)pid);
                return 0;
        }
@@ -285,26 +301,100 @@ static int send_signal(int sig)
        return 1;
 }
 
+struct signal_info {
+       const char * const name;
+       int num;
+};
+
+/*
+ * The table below was taken 2016 from proc/sig.c of procps-3.2.8. Copyright
+ * 1998-2003 by Albert Cahalan, GPLv2.
+ */
+static const struct signal_info signal_table[] = {
+       {"ABRT",   SIGABRT},  /* IOT */
+       {"ALRM",   SIGALRM},
+       {"BUS",    SIGBUS},
+       {"CHLD",   SIGCHLD},  /* CLD */
+       {"CONT",   SIGCONT},
+       {"FPE",    SIGFPE},
+       {"HUP",    SIGHUP},
+       {"ILL",    SIGILL},
+       {"INT",    SIGINT},
+       {"KILL",   SIGKILL},
+       {"PIPE",   SIGPIPE},
+#ifdef SIGPOLL
+       {"POLL",   SIGPOLL},  /* IO */
+#endif
+       {"PROF",   SIGPROF},
+#ifdef SIGPWR
+       {"PWR",    SIGPWR},
+#endif
+       {"QUIT",   SIGQUIT},
+       {"SEGV",   SIGSEGV},
+#ifdef SIGSTKFLT
+       {"STKFLT", SIGSTKFLT},
+#endif
+       {"STOP",   SIGSTOP},
+       {"SYS",    SIGSYS},   /* UNUSED */
+       {"TERM",   SIGTERM},
+       {"TRAP",   SIGTRAP},
+       {"TSTP",   SIGTSTP},
+       {"TTIN",   SIGTTIN},
+       {"TTOU",   SIGTTOU},
+       {"URG",    SIGURG},
+       {"USR1",   SIGUSR1},
+       {"USR2",   SIGUSR2},
+       {"VTALRM", SIGVTALRM},
+       {"WINCH",  SIGWINCH},
+       {"XCPU",   SIGXCPU},
+       {"XFSZ",   SIGXFSZ}
+};
+
+#define SIGNAL_TABLE_SIZE (sizeof(signal_table) / sizeof(signal_table[0]))
+#ifndef SIGRTMAX
+#define SIGRTMAX 64
+#endif
+
 static int com_kill(void)
 {
-       return send_signal(SIGTERM);
-}
+       const char *arg = OPT_STRING_VAL(KILL, SIGNAL);
+       int ret, i;
 
-static int com_reload(void)
-{
-       return send_signal(SIGHUP);
+       if (*arg >= '0' && *arg <= '9') {
+               int64_t val;
+               ret = dss_atoi64(arg, &val);
+               if (ret < 0)
+                       return ret;
+               if (val < 0 || val > SIGRTMAX)
+                       return -ERRNO_TO_DSS_ERROR(EINVAL);
+               return send_signal(val);
+       }
+       if (strncasecmp(arg, "sig", 3) == 0)
+               arg += 3;
+       if (strcasecmp(arg, "CLD") == 0)
+               return send_signal(SIGCHLD);
+       if (strcasecmp(arg, "IOT") == 0)
+               return send_signal(SIGABRT);
+       for (i = 0; i < SIGNAL_TABLE_SIZE; i++)
+               if (strcasecmp(arg, signal_table[i].name) == 0)
+                       return send_signal(signal_table[i].num);
+       DSS_ERROR_LOG(("invalid sigspec: %s\n", arg));
+       return -ERRNO_TO_DSS_ERROR(EINVAL);
 }
+EXPORT_CMD_HANDLER(kill);
 
 static void dss_get_snapshot_list(struct snapshot_list *sl)
 {
-       get_snapshot_list(sl, conf.unit_interval_arg, conf.num_intervals_arg);
+       get_snapshot_list(sl, OPT_UINT32_VAL(DSS, UNIT_INTERVAL),
+               OPT_UINT32_VAL(DSS, NUM_INTERVALS));
 }
 
 static int64_t compute_next_snapshot_time(void)
 {
        int64_t x = 0, now = get_current_time(), unit_interval
-               = 24 * 3600 * conf.unit_interval_arg, ret;
-       unsigned wanted = desired_number_of_snapshots(0, conf.num_intervals_arg),
+               = 24 * 3600 * OPT_UINT32_VAL(DSS, UNIT_INTERVAL), ret;
+       unsigned wanted = desired_number_of_snapshots(0,
+               OPT_UINT32_VAL(DSS, NUM_INTERVALS)),
                num_complete = 0;
        int i;
        struct snapshot *s = NULL;
@@ -361,8 +451,8 @@ static void pre_create_hook(void)
        assert(snapshot_creation_status == HS_READY);
        /* make sure that the next snapshot time will be recomputed */
        invalidate_next_snapshot_time();
-       DSS_DEBUG_LOG(("executing %s\n", conf.pre_create_hook_arg));
-       dss_exec_cmdline_pid(&create_pid, conf.pre_create_hook_arg);
+       DSS_DEBUG_LOG(("executing %s\n", OPT_STRING_VAL(DSS, PRE_CREATE_HOOK)));
+       dss_exec_cmdline_pid(&create_pid, OPT_STRING_VAL(DSS, PRE_CREATE_HOOK));
        snapshot_creation_status = HS_PRE_RUNNING;
 }
 
@@ -381,8 +471,8 @@ static void pre_remove_hook(struct snapshot *s, const char *why)
        *snapshot_currently_being_removed = *s;
        snapshot_currently_being_removed->name = dss_strdup(s->name);
 
-       cmd = make_message("%s %s/%s", conf.pre_remove_hook_arg,
-               conf.dest_dir_arg, s->name);
+       cmd = make_message("%s %s/%s", OPT_STRING_VAL(DSS, PRE_REMOVE_HOOK),
+               OPT_STRING_VAL(DSS, DEST_DIR), s->name);
        DSS_DEBUG_LOG(("executing %s\n", cmd));
        dss_exec_cmdline_pid(&remove_pid, cmd);
        free(cmd);
@@ -469,10 +559,11 @@ static struct snapshot *find_redundant_snapshot(struct snapshot_list *sl)
        int i, interval;
        struct snapshot *s;
        unsigned missing = 0;
+       uint32_t N = OPT_UINT32_VAL(DSS, NUM_INTERVALS);
 
        DSS_DEBUG_LOG(("looking for intervals containing too many snapshots\n"));
-       for (interval = conf.num_intervals_arg - 1; interval >= 0; interval--) {
-               unsigned keep = desired_number_of_snapshots(interval, conf.num_intervals_arg);
+       for (interval = N - 1; interval >= 0; interval--) {
+               unsigned keep = desired_number_of_snapshots(interval, N);
                unsigned num = sl->interval_count[interval];
                struct snapshot *victim = NULL, *prev = NULL;
                int64_t score = LONG_MAX;
@@ -522,13 +613,13 @@ static struct snapshot *find_outdated_snapshot(struct snapshot_list *sl)
        struct snapshot *s;
 
        DSS_DEBUG_LOG(("looking for snapshots belonging to intervals >= %d\n",
-               conf.num_intervals_arg));
+               OPT_UINT32_VAL(DSS, NUM_INTERVALS)));
        FOR_EACH_SNAPSHOT(s, i, sl) {
                if (snapshot_is_being_created(s))
                        continue;
                if (is_reference_snapshot(s))
                        continue;
-               if (s->interval < conf.num_intervals_arg)
+               if (s->interval < OPT_UINT32_VAL(DSS, NUM_INTERVALS))
                        continue;
                return s;
        }
@@ -541,7 +632,7 @@ static struct snapshot *find_oldest_removable_snapshot(struct snapshot_list *sl)
        struct snapshot *s, *ref = NULL;
 
        num_complete = num_complete_snapshots(sl);
-       if (num_complete <= conf.min_complete_arg)
+       if (num_complete <= OPT_UINT32_VAL(DSS, MIN_COMPLETE))
                return NULL;
        FOR_EACH_SNAPSHOT(s, i, sl) {
                if (snapshot_is_being_created(s))
@@ -601,7 +692,7 @@ static int try_to_free_disk_space(void)
        if (tv_diff(&next_removal_check, &now, NULL) > 0)
                return 0;
        if (!low_disk_space) {
-               if (conf.keep_redundant_given)
+               if (OPT_GIVEN(DSS, KEEP_REDUNDANT))
                        return 0;
                if (snapshot_creation_status != HS_READY)
                        return 0;
@@ -619,7 +710,8 @@ static int try_to_free_disk_space(void)
         * snapshots than configured, plus one. This way there is always one
         * snapshot that can be recycled.
         */
-       if (!low_disk_space && sl.num_snapshots <= 1 << conf.num_intervals_arg)
+       if (!low_disk_space && sl.num_snapshots <=
+                       1 << OPT_UINT32_VAL(DSS, NUM_INTERVALS))
                goto out;
        why = "outdated";
        victim = find_outdated_snapshot(&sl);
@@ -653,8 +745,9 @@ out:
 
 static void post_create_hook(void)
 {
-       char *cmd = make_message("%s %s/%s", conf.post_create_hook_arg,
-               conf.dest_dir_arg, path_to_last_complete_snapshot);
+       char *cmd = make_message("%s %s/%s",
+               OPT_STRING_VAL(DSS, POST_CREATE_HOOK),
+               OPT_STRING_VAL(DSS, DEST_DIR), path_to_last_complete_snapshot);
        DSS_NOTICE_LOG(("executing %s\n", cmd));
        dss_exec_cmdline_pid(&create_pid, cmd);
        free(cmd);
@@ -668,8 +761,8 @@ static void post_remove_hook(void)
 
        assert(s);
 
-       cmd = make_message("%s %s/%s", conf.post_remove_hook_arg,
-               conf.dest_dir_arg, s->name);
+       cmd = make_message("%s %s/%s", OPT_STRING_VAL(DSS, POST_REMOVE_HOOK),
+               OPT_STRING_VAL(DSS, DEST_DIR), s->name);
        DSS_NOTICE_LOG(("executing %s\n", cmd));
        dss_exec_cmdline_pid(&remove_pid, cmd);
        free(cmd);
@@ -865,11 +958,12 @@ static int handle_rsync_exit(int status)
        if (es != 0 && es != 24) {
                DSS_WARNING_LOG(("rsync exit code %d, error count %d\n",
                        es, ++num_consecutive_rsync_errors));
-               if (conf.create_given) {
+               if (!logfile) { /* called by com_run() */
                        ret = -E_BAD_EXIT_CODE;
                        goto out;
                }
-               if (num_consecutive_rsync_errors > conf.max_rsync_errors_arg) {
+               if (num_consecutive_rsync_errors >
+                               OPT_UINT32_VAL(RUN, MAX_RSYNC_ERRORS)) {
                        ret = -E_TOO_MANY_RSYNC_ERRORS;
                        snapshot_creation_status = HS_READY;
                        goto out;
@@ -906,7 +1000,7 @@ static int handle_pre_create_hook_exit(int status)
        if (es) {
                if (!warn_count--) {
                        DSS_NOTICE_LOG(("pre_create_hook %s returned %d\n",
-                               conf.pre_create_hook_arg, es));
+                               OPT_STRING_VAL(DSS, PRE_CREATE_HOOK), es));
                        DSS_NOTICE_LOG(("deferring snapshot creation...\n"));
                        warn_count = 60; /* warn only once per hour */
                }
@@ -960,85 +1054,160 @@ static int handle_sigchld(void)
        return -E_BUG;
 }
 
-static int check_config(void)
+static int change_to_dest_dir(void)
 {
-       if (conf.unit_interval_arg <= 0) {
-               DSS_ERROR_LOG(("bad unit interval: %i\n", conf.unit_interval_arg));
+       int ret;
+       const char *dd = OPT_STRING_VAL(DSS, DEST_DIR);
+
+       DSS_INFO_LOG(("changing cwd to %s\n", dd));
+       if (chdir(dd) >= 0)
+               return 1;
+       ret = -ERRNO_TO_DSS_ERROR(errno);
+       DSS_ERROR_LOG(("could not change cwd to %s\n", dd));
+       return ret;
+}
+
+static int check_config(const struct lls_command *cmd)
+{
+       int ret;
+       uint32_t unit_interval = OPT_UINT32_VAL(DSS, UNIT_INTERVAL);
+       uint32_t num_intervals = OPT_UINT32_VAL(DSS, NUM_INTERVALS);
+
+       if (unit_interval == 0) {
+               DSS_ERROR_LOG(("bad unit interval: %i\n", unit_interval));
                return -E_INVALID_NUMBER;
        }
-       DSS_DEBUG_LOG(("unit interval: %i day(s)\n", conf.unit_interval_arg));
-       if (conf.num_intervals_arg <= 0 || conf.num_intervals_arg > 30) {
-               DSS_ERROR_LOG(("bad number of intervals: %i\n",
-                       conf.num_intervals_arg));
+       DSS_DEBUG_LOG(("unit interval: %i day(s)\n", unit_interval));
+
+       if (num_intervals == 0 || num_intervals > 30) {
+               DSS_ERROR_LOG(("bad number of intervals: %i\n", num_intervals));
                return -E_INVALID_NUMBER;
        }
-       DSS_DEBUG_LOG(("number of intervals: %i\n", conf.num_intervals_arg));
+       if (cmd == CMD_PTR(RUN) || cmd == CMD_PTR(CREATE))
+               if (!OPT_GIVEN(DSS, SOURCE_DIR)) {
+                       DSS_ERROR_LOG(("--source-dir required\n"));
+                       return -E_SYNTAX;
+               }
+       if (cmd == CMD_PTR(RUN) || cmd == CMD_PTR(CREATE)
+                       || cmd == CMD_PTR(LS) || cmd == CMD_PTR(PRUNE)) {
+               if (!OPT_GIVEN(DSS, DEST_DIR)) {
+                       DSS_ERROR_LOG(("--dest-dir required\n"));
+                       return -E_SYNTAX;
+               }
+               ret = change_to_dest_dir();
+               if (ret < 0)
+                       return ret;
+       }
+       DSS_DEBUG_LOG(("number of intervals: %i\n", num_intervals));
        return 1;
 }
 
-/*
- * Returns < 0 on errors, 0 if no config file is given and > 0 if the config
- * file was read successfully.
- */
-static int parse_config_file(bool sighup)
+static int lopsub_error(int lopsub_ret, char **errctx)
 {
-       int ret, config_file_exists;
+       const char *msg = lls_strerror(-lopsub_ret);
+       if (*errctx)
+               DSS_ERROR_LOG(("%s: %s\n", *errctx, msg));
+       else
+               DSS_ERROR_LOG(("%s\n", msg));
+       free(*errctx);
+       *errctx = NULL;
+       return -E_LOPSUB;
+}
+
+static int parse_config_file(bool sighup, const struct lls_command *cmd)
+{
+       int ret, fd = -1;
        char *config_file = get_config_file_name();
        struct stat statbuf;
-       char *old_logfile_arg = NULL;
-       int old_daemon_given = 0;
-
-       if (sighup) {
-               if (conf.logfile_given)
-                       old_logfile_arg = dss_strdup(conf.logfile_arg);
-               old_daemon_given = conf.daemon_given;
+       void *map;
+       size_t sz;
+       int cf_argc;
+       char **cf_argv, *errctx = NULL;
+       struct lls_parse_result *cf_lpr, *merged_lpr, *clpr;
+       const char *subcmd_name;
+
+       ret = open(config_file, O_RDONLY);
+       if (ret < 0) {
+               if (errno != ENOENT || OPT_GIVEN(DSS, CONFIG_FILE)) {
+                       ret = -ERRNO_TO_DSS_ERROR(errno);
+                       DSS_ERROR_LOG(("config file %s can not be opened\n",
+                               config_file));
+                       goto out;
+               }
+               /* no config file -- nothing to do */
+               ret = 0;
+               goto success;
        }
-
-       config_file_exists = !stat(config_file, &statbuf);
-       if (!config_file_exists && conf.config_file_given) {
+       fd = ret;
+       ret = fstat(fd, &statbuf);
+       if (ret < 0) {
                ret = -ERRNO_TO_DSS_ERROR(errno);
                DSS_ERROR_LOG(("failed to stat config file %s\n", config_file));
-               goto out;
+               goto close_fd;
        }
-       if (config_file_exists) {
-               struct cmdline_parser_params params;
-               params.override = sighup;
-               params.initialize = 0;
-               params.check_required = 1;
-               params.check_ambiguity = 0;
-               params.print_errors = 1;
-               if (sighup) { /* invalidate all rsync options */
-                       int i;
-
-                       for (i = 0; i < conf.rsync_option_given; i++) {
-                               free(conf.rsync_option_arg[i]);
-                               conf.rsync_option_arg[i] = NULL;
-                       }
-                       conf.rsync_option_given = 0;
-               }
-               cmdline_parser_config_file(config_file, &conf, &params);
+       sz = statbuf.st_size;
+       if (sz == 0) { /* config file is empty -- nothing to do */
+               ret = 0;
+               goto success;
        }
-       ret = check_config();
-       if (ret < 0)
-               goto out;
-       if (sighup) {
-               /* don't change daemon mode on SIGHUP */
-               conf.daemon_given = old_daemon_given;
-               close_log(logfile);
-               logfile = NULL;
-               if (conf.logfile_given)
-                       free(old_logfile_arg);
-               else if (conf.daemon_given) { /* re-use old logfile */
-                       conf.logfile_arg = old_logfile_arg;
-                       conf.logfile_given = 1;
-               }
+       map = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (map == MAP_FAILED) {
+               ret = -ERRNO_TO_DSS_ERROR(errno);
+               DSS_ERROR_LOG(("failed to mmap config file %s\n",
+                       config_file));
+               goto close_fd;
+       }
+       if (cmd == CMD_PTR(DSS))
+               subcmd_name = NULL;
+       else
+               subcmd_name = lls_command_name(cmd);
+       ret = lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx);
+       munmap(map, sz);
+       if (ret < 0) {
+               DSS_ERROR_LOG(("failed to convert config file %s\n",
+                       config_file));
+               ret = lopsub_error(ret, &errctx);
+               goto close_fd;
+       }
+       cf_argc = ret;
+       ret = lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx);
+       lls_free_argv(cf_argv);
+       if (ret < 0) {
+               ret = lopsub_error(ret, &errctx);
+               goto close_fd;
+       }
+       clpr = cmd == CMD_PTR(DSS)? cmdline_lpr : cmdline_sublpr;
+       if (sighup) /* config file overrides command line */
+               ret = lls_merge(cf_lpr, clpr, cmd, &merged_lpr, &errctx);
+       else /* command line options overrride config file options */
+               ret = lls_merge(clpr, cf_lpr, cmd, &merged_lpr, &errctx);
+       lls_free_parse_result(cf_lpr, cmd);
+       if (ret < 0) {
+               ret = lopsub_error(ret, &errctx);
+               goto close_fd;
        }
-       if (conf.logfile_given && conf.run_given && conf.daemon_given) {
-               logfile = open_log(conf.logfile_arg);
-               log_welcome(conf.loglevel_arg);
+       ret = 1;
+success:
+       assert(ret >= 0);
+       DSS_DEBUG_LOG(("loglevel: %d\n", OPT_UINT32_VAL(DSS, LOGLEVEL)));
+       if (cmd != CMD_PTR(DSS)) {
+               if (ret > 0) {
+                       if (sublpr != cmdline_sublpr)
+                               lls_free_parse_result(sublpr, cmd);
+                       sublpr = merged_lpr;
+               } else
+                       sublpr = cmdline_sublpr;
+       } else {
+               if (ret > 0) {
+                       if (lpr != cmdline_lpr)
+                               lls_free_parse_result(lpr, cmd);
+                       lpr = merged_lpr;
+               } else
+                       lpr = cmdline_lpr;
        }
-       DSS_DEBUG_LOG(("loglevel: %d\n", conf.loglevel_arg));
-       ret = config_file_exists;
+close_fd:
+       if (fd >= 0)
+               close(fd);
 out:
        free(config_file);
        if (ret < 0)
@@ -1046,24 +1215,31 @@ out:
        return ret;
 }
 
-static int change_to_dest_dir(void)
-{
-       DSS_INFO_LOG(("changing cwd to %s\n", conf.dest_dir_arg));
-       return dss_chdir(conf.dest_dir_arg);
-}
-
 static int handle_sighup(void)
 {
        int ret;
 
        DSS_NOTICE_LOG(("SIGHUP, re-reading config\n"));
        dump_dss_config("old");
-       ret = parse_config_file(true /* SIGHUP */);
+       ret = parse_config_file(true /* SIGHUP */, CMD_PTR(DSS));
+       if (ret < 0)
+               return ret;
+       ret = parse_config_file(true /* SIGHUP */, CMD_PTR(RUN));
        if (ret < 0)
                return ret;
+       ret = check_config(CMD_PTR(RUN));
+       if (ret < 0)
+               return ret;
+       close_log(logfile);
+       logfile = NULL;
+       if (OPT_GIVEN(RUN, DAEMON) || daemonized) {
+               logfile = open_log(OPT_STRING_VAL(RUN, LOGFILE));
+               log_welcome(OPT_UINT32_VAL(DSS, LOGLEVEL));
+               daemonized = true;
+       }
        dump_dss_config("reloaded");
        invalidate_next_snapshot_time();
-       return change_to_dest_dir();
+       return 1;
 }
 
 static void kill_children(void)
@@ -1105,11 +1281,12 @@ out:
  */
 static int use_rsync_locally(char *logname)
 {
-       char *h = conf.remote_host_arg;
+       const char *h = OPT_STRING_VAL(DSS, REMOTE_HOST);
 
        if (strcmp(h, "localhost") && strcmp(h, "127.0.0.1"))
                return 0;
-       if (conf.remote_user_given && strcmp(conf.remote_user_arg, logname))
+       if (OPT_GIVEN(DSS, REMOTE_USER) &&
+                       strcmp(OPT_STRING_VAL(DSS, REMOTE_USER), logname))
                return 0;
        return 1;
 }
@@ -1125,8 +1302,6 @@ static int rename_resume_snap(int64_t creation_time)
        sl.num_snapshots = 0;
 
        ret = 0;
-       if (conf.no_resume_given)
-               goto out;
        dss_get_snapshot_list(&sl);
        /*
         * Snapshot recycling: We first look at the newest snapshot. If this
@@ -1171,20 +1346,30 @@ out:
 static void create_rsync_argv(char ***argv, int64_t *num)
 {
        char *logname;
-       int i = 0, j;
+       int i = 0, j, N = OPT_GIVEN(DSS, RSYNC_OPTION);
        struct snapshot_list sl;
+       static bool seeded;
 
        dss_get_snapshot_list(&sl);
        assert(!name_of_reference_snapshot);
        name_of_reference_snapshot = name_of_newest_complete_snapshot(&sl);
        free_snapshot_list(&sl);
 
-       *argv = dss_malloc((15 + conf.rsync_option_given) * sizeof(char *));
+       *argv = dss_malloc((15 + N) * sizeof(char *));
        (*argv)[i++] = dss_strdup("rsync");
        (*argv)[i++] = dss_strdup("-a");
        (*argv)[i++] = dss_strdup("--delete");
-       for (j = 0; j < conf.rsync_option_given; j++)
-               (*argv)[i++] = dss_strdup(conf.rsync_option_arg[j]);
+       if (!seeded) {
+               srandom((unsigned)time(NULL)); /* no need to be fancy here */
+               seeded = true;
+       }
+       if (1000 * (random() / (RAND_MAX + 1.0)) < OPT_UINT32_VAL(DSS, CHECKSUM)) {
+               DSS_NOTICE_LOG(("adding --checksum to rsync options\n"));
+               (*argv)[i++] = dss_strdup("--checksum");
+       }
+       for (j = 0; j < N; j++)
+               (*argv)[i++] = dss_strdup(lls_string_val(j,
+                       OPT_RESULT(DSS, RSYNC_OPTION)));
        if (name_of_reference_snapshot) {
                DSS_INFO_LOG(("using %s as reference\n", name_of_reference_snapshot));
                (*argv)[i++] = make_message("--link-dest=../%s",
@@ -1193,11 +1378,13 @@ static void create_rsync_argv(char ***argv, int64_t *num)
                DSS_INFO_LOG(("no suitable reference snapshot found\n"));
        logname = dss_logname();
        if (use_rsync_locally(logname))
-               (*argv)[i++] = dss_strdup(conf.source_dir_arg);
+               (*argv)[i++] = dss_strdup(OPT_STRING_VAL(DSS, SOURCE_DIR));
        else
-               (*argv)[i++] = make_message("%s@%s:%s/", conf.remote_user_given?
-                       conf.remote_user_arg : logname,
-                       conf.remote_host_arg, conf.source_dir_arg);
+               (*argv)[i++] = make_message("%s@%s:%s/",
+                       OPT_GIVEN(DSS, REMOTE_USER)?
+                               OPT_STRING_VAL(DSS, REMOTE_USER) : logname,
+                       OPT_STRING_VAL(DSS, REMOTE_HOST),
+                       OPT_STRING_VAL(DSS, SOURCE_DIR));
        free(logname);
        *num = get_current_time();
        (*argv)[i++] = incomplete_name(*num);
@@ -1314,15 +1501,15 @@ out:
 
 static void exit_hook(int exit_code)
 {
-       char *argv[3];
+       const char *argv[3];
        pid_t pid;
 
-       argv[0] = conf.exit_hook_arg;
+       argv[0] = OPT_STRING_VAL(DSS, EXIT_HOOK);
        argv[1] = dss_strerror(-exit_code);
        argv[2] = NULL;
 
        DSS_NOTICE_LOG(("executing %s %s\n", argv[0], argv[1]));
-       dss_exec(&pid, conf.exit_hook_arg, argv);
+       dss_exec(&pid, argv[0], (char **)argv);
 }
 
 static void lock_dss_or_die(void)
@@ -1339,16 +1526,41 @@ static void lock_dss_or_die(void)
 
 static int com_run(void)
 {
-       int ret;
+       int ret, fd = -1;
+       char *config_file;
+       pid_t pid;
 
-       lock_dss_or_die();
-       if (conf.dry_run_given) {
-               DSS_ERROR_LOG(("dry_run not supported by this command\n"));
+       if (OPT_GIVEN(DSS, DRY_RUN)) {
+               DSS_ERROR_LOG(("dry run not supported by this command\n"));
                return -E_SYNTAX;
        }
+       config_file = get_config_file_name();
+       ret = get_dss_pid(config_file, &pid);
+       free(config_file);
+       if (ret >= 0) {
+               DSS_ERROR_LOG(("pid %d\n", (int)pid));
+               return -E_ALREADY_RUNNING;
+       }
+       if (OPT_GIVEN(RUN, DAEMON)) {
+               fd = daemon_init();
+               daemonized = true;
+               logfile = open_log(OPT_STRING_VAL(RUN, LOGFILE));
+       }
+       lock_dss_or_die();
+       dump_dss_config("startup");
        ret = install_sighandler(SIGHUP);
        if (ret < 0)
                return ret;
+       if (fd >= 0) {
+               ret = write(fd, "\0", 1);
+               if (ret != 1) {
+                       DSS_ERROR_LOG(("write to daemon pipe returned %d\n",
+                               ret));
+                       if (ret < 0)
+                               return -ERRNO_TO_DSS_ERROR(errno);
+                       return -E_BUG;
+               }
+       }
        ret = select_loop();
        if (ret >= 0) /* impossible */
                ret = -E_BUG;
@@ -1356,6 +1568,7 @@ static int com_run(void)
        exit_hook(ret);
        return ret;
 }
+EXPORT_CMD_HANDLER(run);
 
 static int com_prune(void)
 {
@@ -1382,7 +1595,7 @@ static int com_prune(void)
        ret = 0;
        goto out;
 rm:
-       if (conf.dry_run_given) {
+       if (OPT_GIVEN(DSS, DRY_RUN)) {
                dss_msg("%s snapshot %s (interval = %i)\n",
                        why, victim->name, victim->interval);
                ret = 0;
@@ -1415,6 +1628,7 @@ out:
        free_snapshot_list(&sl);
        return ret;
 }
+EXPORT_CMD_HANDLER(prune);
 
 static int com_create(void)
 {
@@ -1422,7 +1636,7 @@ static int com_create(void)
        char **rsync_argv;
 
        lock_dss_or_die();
-       if (conf.dry_run_given) {
+       if (OPT_GIVEN(DSS, DRY_RUN)) {
                int i;
                char *msg = NULL;
                create_rsync_argv(&rsync_argv, &current_snapshot_creation_time);
@@ -1463,6 +1677,7 @@ out:
        free_rsync_argv(rsync_argv);
        return ret;
 }
+EXPORT_CMD_HANDLER(create);
 
 static int com_ls(void)
 {
@@ -1480,6 +1695,7 @@ static int com_ls(void)
        free_snapshot_list(&sl);
        return 1;
 }
+EXPORT_CMD_HANDLER(ls);
 
 static int setup_signal_handling(void)
 {
@@ -1496,47 +1712,95 @@ static int setup_signal_handling(void)
        return install_sighandler(SIGCHLD);
 }
 
+static void handle_version_and_help(void)
+{
+       char *txt;
+
+       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 = dss_strdup(VERSION_STRING);
+       else
+               return;
+       printf("%s", txt);
+       free(txt);
+       exit(EXIT_SUCCESS);
+}
+
+static void show_subcommand_summary(void)
+{
+       const struct lls_command *cmd;
+       int i;
+
+       printf("Available subcommands:\n");
+       for (i = 1; (cmd = lls_cmd(i, dss_suite)); i++) {
+               const char *name = lls_command_name(cmd);
+               const char *purpose = lls_purpose(cmd);
+               printf("%-10s%s\n", name, purpose);
+       }
+       exit(EXIT_SUCCESS);
+}
+
 int main(int argc, char **argv)
 {
        int ret;
-       struct cmdline_parser_params params;
-
-       params.override = 0;
-       params.initialize = 1;
-       params.check_required = 0;
-       params.check_ambiguity = 0;
-       params.print_errors = 1;
+       const struct lls_command *cmd = CMD_PTR(DSS);
+       char *errctx = NULL;
+       unsigned num_inputs;
+       const struct dss_user_data *ud = NULL;
 
-       cmdline_parser_ext(argc, argv, &conf, &params); /* aborts on errors */
-       ret = parse_config_file(0);
-       ret = parse_config_file(false /* no SIGHUP */);
+       ret = lls_parse(argc, argv, cmd, &cmdline_lpr, &errctx);
+       if (ret < 0) {
+               ret = lopsub_error(ret, &errctx);
+               goto out;
+       }
+       lpr = cmdline_lpr;
+       ret = parse_config_file(false /* no SIGHUP */, cmd);
        if (ret < 0)
                goto out;
-       if (ret == 0) { /* no config file given */
-               /*
-                * Parse the command line options again, but this time check
-                * that all required options are given.
-                */
-               params.override = 1;
-               params.initialize = 1;
-               params.check_required = 1;
-               params.check_ambiguity = 1;
-               params.print_errors = 1;
-               cmdline_parser_ext(argc, argv, &conf, &params); /* aborts on errors */
+       handle_version_and_help();
+       num_inputs = lls_num_inputs(lpr);
+       if (num_inputs == 0)
+               show_subcommand_summary();
+       ret = lls_lookup_subcmd(argv[argc - num_inputs], dss_suite, &errctx);
+       if (ret < 0) {
+               ret = lopsub_error(ret, &errctx);
+               goto out;
        }
-       if (conf.daemon_given)
-               daemon_init();
-       ret = change_to_dest_dir();
+       cmd = lls_cmd(ret, dss_suite);
+       ret = lls_parse(num_inputs, argv + argc - num_inputs, cmd,
+               &cmdline_sublpr, &errctx);
+       if (ret < 0) {
+               ret = lopsub_error(ret, &errctx);
+               goto out;
+       }
+       sublpr = cmdline_sublpr;
+       ret = parse_config_file(false /* no SIGHUP */, cmd);
+       if (ret < 0)
+               goto out;
+       ret = check_config(cmd);
        if (ret < 0)
                goto out;
-       dump_dss_config("startup");
        ret = setup_signal_handling();
        if (ret < 0)
                goto out;
-       ret = call_command_handler();
+       ud = lls_user_data(cmd);
+       ret = ud->handler();
        signal_shutdown();
 out:
-       if (ret < 0)
+       if (ret < 0) {
+               if (errctx)
+                       DSS_ERROR_LOG(("%s\n", errctx));
                DSS_EMERG_LOG(("%s\n", dss_strerror(-ret)));
+       }
+       free(errctx);
+       lls_free_parse_result(lpr, CMD_PTR(DSS));
+       if (lpr != cmdline_lpr)
+               lls_free_parse_result(cmdline_lpr, CMD_PTR(DSS));
+       lls_free_parse_result(sublpr, cmd);
+       if (sublpr != cmdline_sublpr)
+               lls_free_parse_result(cmdline_sublpr, cmd);
        exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE);
 }
diff --git a/dss.dia b/dss.dia
deleted file mode 100644 (file)
index a02f700..0000000
--- a/dss.dia
+++ /dev/null
@@ -1,960 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<dia:diagram xmlns:dia="http://www.lysator.liu.se/~alla/dia/">
-  <dia:diagramdata>
-    <dia:attribute name="background">
-      <dia:color val="#ffffff"/>
-    </dia:attribute>
-    <dia:attribute name="pagebreak">
-      <dia:color val="#000099"/>
-    </dia:attribute>
-    <dia:attribute name="paper">
-      <dia:composite type="paper">
-        <dia:attribute name="name">
-          <dia:string>#A4#</dia:string>
-        </dia:attribute>
-        <dia:attribute name="tmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="bmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="lmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="rmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="is_portrait">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="scaling">
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="fitto">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-      </dia:composite>
-    </dia:attribute>
-    <dia:attribute name="grid">
-      <dia:composite type="grid">
-        <dia:attribute name="width_x">
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="width_y">
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="visible_x">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="visible_y">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:composite type="color"/>
-      </dia:composite>
-    </dia:attribute>
-    <dia:attribute name="color">
-      <dia:color val="#d8e5e5"/>
-    </dia:attribute>
-    <dia:attribute name="guides">
-      <dia:composite type="guides">
-        <dia:attribute name="hguides"/>
-        <dia:attribute name="vguides"/>
-      </dia:composite>
-    </dia:attribute>
-  </dia:diagramdata>
-  <dia:layer name="New layer 1" visible="true" active="true">
-    <dia:object type="Standard - Box" version="0" id="O0">
-      <dia:attribute name="obj_pos">
-        <dia:point val="13.1551,5.70485"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="13.1051,5.65485;29.4092,11.5949"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="13.1551,5.70485"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="16.204140120230058"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="5.8400708047529699"/>
-      </dia:attribute>
-      <dia:attribute name="border_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="border_color">
-        <dia:color val="#ffff00"/>
-      </dia:attribute>
-      <dia:attribute name="inner_color">
-        <dia:color val="#ffff00"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:group>
-      <dia:object type="Standard - Box" version="0" id="O1">
-        <dia:attribute name="obj_pos">
-          <dia:point val="13.5072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="13.4572,6.02489;17.3572,11.2249"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="13.5072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.8000000000000007"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="5.0999999999999996"/>
-        </dia:attribute>
-        <dia:attribute name="border_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="border_color">
-          <dia:color val="#cbd4fa"/>
-        </dia:attribute>
-        <dia:attribute name="inner_color">
-          <dia:color val="#cbd4fa"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - Box" version="0" id="O2">
-        <dia:attribute name="obj_pos">
-          <dia:point val="17.4072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="17.3572,6.02489;21.2572,11.2249"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="17.4072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.8000000000000007"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="5.0999999999999996"/>
-        </dia:attribute>
-        <dia:attribute name="border_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="border_color">
-          <dia:color val="#90a5fe"/>
-        </dia:attribute>
-        <dia:attribute name="inner_color">
-          <dia:color val="#90a5fe"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - Box" version="0" id="O3">
-        <dia:attribute name="obj_pos">
-          <dia:point val="21.3072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="21.2572,6.02489;25.1572,11.2249"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="21.3072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.8000000000000007"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="5.0999999999999996"/>
-        </dia:attribute>
-        <dia:attribute name="border_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="border_color">
-          <dia:color val="#5e78f6"/>
-        </dia:attribute>
-        <dia:attribute name="inner_color">
-          <dia:color val="#5e78f6"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - Box" version="0" id="O4">
-        <dia:attribute name="obj_pos">
-          <dia:point val="25.2072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="25.1572,6.02489;29.0572,11.2249"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="25.2072,6.07489"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.8000000000000007"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="5.0999999999999996"/>
-        </dia:attribute>
-        <dia:attribute name="border_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="border_color">
-          <dia:color val="#0000ff"/>
-        </dia:attribute>
-        <dia:attribute name="inner_color">
-          <dia:color val="#0910f4"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:group>
-        <dia:group>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O5">
-            <dia:attribute name="obj_pos">
-              <dia:point val="25.3046,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="25.2546,9.69613;25.7177,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="25.3046,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O6">
-            <dia:attribute name="obj_pos">
-              <dia:point val="25.7677,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="25.7177,9.69613;26.1809,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="25.7677,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O7">
-            <dia:attribute name="obj_pos">
-              <dia:point val="26.2309,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="26.1809,9.69613;26.644,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="26.2309,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O8">
-            <dia:attribute name="obj_pos">
-              <dia:point val="26.694,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="26.644,9.69613;27.1072,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="26.694,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O9">
-            <dia:attribute name="obj_pos">
-              <dia:point val="27.1572,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="27.1072,9.69613;27.5703,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="27.1572,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O10">
-            <dia:attribute name="obj_pos">
-              <dia:point val="27.6203,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="27.5703,9.69613;28.0335,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="27.6203,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O11">
-            <dia:attribute name="obj_pos">
-              <dia:point val="28.0835,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="28.0335,9.69613;28.4966,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="28.0835,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O12">
-            <dia:attribute name="obj_pos">
-              <dia:point val="28.5466,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="28.4966,9.69613;28.9598,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="28.5466,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-        </dia:group>
-        <dia:group>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O13">
-            <dia:attribute name="obj_pos">
-              <dia:point val="21.8026,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="21.7526,9.69613;22.2157,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="21.8026,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O14">
-            <dia:attribute name="obj_pos">
-              <dia:point val="22.6179,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="22.5679,9.69613;23.0311,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="22.6179,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O15">
-            <dia:attribute name="obj_pos">
-              <dia:point val="23.4333,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="23.3833,9.69613;23.8464,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="23.4333,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O16">
-            <dia:attribute name="obj_pos">
-              <dia:point val="24.2486,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="24.1986,9.69613;24.6618,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="24.2486,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-        </dia:group>
-        <dia:object type="Geometric - Perfect Circle" version="1" id="O17">
-          <dia:attribute name="obj_pos">
-            <dia:point val="15.2256,9.74613"/>
-          </dia:attribute>
-          <dia:attribute name="obj_bb">
-            <dia:rectangle val="15.1756,9.69613;15.6388,10.1593"/>
-          </dia:attribute>
-          <dia:attribute name="meta">
-            <dia:composite type="dict"/>
-          </dia:attribute>
-          <dia:attribute name="elem_corner">
-            <dia:point val="15.2256,9.74613"/>
-          </dia:attribute>
-          <dia:attribute name="elem_width">
-            <dia:real val="0.3631490811886362"/>
-          </dia:attribute>
-          <dia:attribute name="elem_height">
-            <dia:real val="0.3631490811886362"/>
-          </dia:attribute>
-          <dia:attribute name="line_width">
-            <dia:real val="0.10000000149011612"/>
-          </dia:attribute>
-          <dia:attribute name="line_colour">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="fill_colour">
-            <dia:color val="#ff0000"/>
-          </dia:attribute>
-          <dia:attribute name="show_background">
-            <dia:boolean val="true"/>
-          </dia:attribute>
-          <dia:attribute name="line_style">
-            <dia:enum val="0"/>
-            <dia:real val="1"/>
-          </dia:attribute>
-          <dia:attribute name="flip_horizontal">
-            <dia:boolean val="false"/>
-          </dia:attribute>
-          <dia:attribute name="flip_vertical">
-            <dia:boolean val="false"/>
-          </dia:attribute>
-          <dia:attribute name="subscale">
-            <dia:real val="1"/>
-          </dia:attribute>
-        </dia:object>
-        <dia:group>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O18">
-            <dia:attribute name="obj_pos">
-              <dia:point val="18.3889,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="18.3389,9.69613;18.8021,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="18.3889,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.3631490811886362"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-          <dia:object type="Geometric - Perfect Circle" version="1" id="O19">
-            <dia:attribute name="obj_pos">
-              <dia:point val="19.8623,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="obj_bb">
-              <dia:rectangle val="19.8123,9.69613;20.2754,10.1593"/>
-            </dia:attribute>
-            <dia:attribute name="meta">
-              <dia:composite type="dict"/>
-            </dia:attribute>
-            <dia:attribute name="elem_corner">
-              <dia:point val="19.8623,9.74613"/>
-            </dia:attribute>
-            <dia:attribute name="elem_width">
-              <dia:real val="0.36314908118863798"/>
-            </dia:attribute>
-            <dia:attribute name="elem_height">
-              <dia:real val="0.36314908118863798"/>
-            </dia:attribute>
-            <dia:attribute name="line_width">
-              <dia:real val="0.10000000149011612"/>
-            </dia:attribute>
-            <dia:attribute name="line_colour">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="fill_colour">
-              <dia:color val="#ff0000"/>
-            </dia:attribute>
-            <dia:attribute name="show_background">
-              <dia:boolean val="true"/>
-            </dia:attribute>
-            <dia:attribute name="line_style">
-              <dia:enum val="0"/>
-              <dia:real val="1"/>
-            </dia:attribute>
-            <dia:attribute name="flip_horizontal">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="flip_vertical">
-              <dia:boolean val="false"/>
-            </dia:attribute>
-            <dia:attribute name="subscale">
-              <dia:real val="1"/>
-            </dia:attribute>
-          </dia:object>
-        </dia:group>
-      </dia:group>
-      <dia:object type="Standard - Line" version="0" id="O20">
-        <dia:attribute name="obj_pos">
-          <dia:point val="14.4506,9.23568"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="14.4006,8.90478;28.1138,9.56659"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="14.4506,9.23568"/>
-          <dia:point val="28.002,9.23568"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_color">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="end_arrow">
-          <dia:enum val="3"/>
-        </dia:attribute>
-        <dia:attribute name="end_arrow_length">
-          <dia:real val="0.5"/>
-        </dia:attribute>
-        <dia:attribute name="end_arrow_width">
-          <dia:real val="0.5"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - Text" version="1" id="O21">
-        <dia:attribute name="obj_pos">
-          <dia:point val="19.8922,8.37201"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="19.8922,6.86701;22.6222,8.68701"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#DSS#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="Serif" style="80" name="Times-Bold"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="1.8584333549302243"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="19.8922,8.37201"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#ffffff"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="0"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="valign">
-          <dia:enum val="3"/>
-        </dia:attribute>
-      </dia:object>
-    </dia:group>
-  </dia:layer>
-</dia:diagram>
diff --git a/dss.ggo b/dss.ggo
deleted file mode 100644 (file)
index 12871ea..0000000
--- a/dss.ggo
+++ /dev/null
@@ -1,483 +0,0 @@
-# Copyright (C) 2008-2010 Andre Noll <maan@tuebingen.mpg.de>
-#
-# Licensed under the GPL v2. For licencing details see COPYING.
-
-package "dss"
-version "0.1.7"
-purpose "the dyadic snapshot scheduler
-
-dss creates hardlink-based snapshots of a given directory on a remote
-or local host using rsync's link-dest feature.
-"
-
-#########################
-section "General options"
-#########################
-
-option "config-file" c
-#~~~~~~~~~~~~~~~~~~~~~
-"(default='~/.dssrc')"
-string typestr="filename"
-optional
-details="
-       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 an important exception to this rule:
-       If the --run option was given (see below) then dss honors
-       SIGHUP and re-reads its configuration file whenever it
-       receives this signal. In this case the options in the config
-       file override any options that were previously given at the
-       command line. This allows to change the configuration of a
-       running dss process on the fly by sending SIGHUP.
-"
-
-option "daemon" d
-#~~~~~~~~~~~~~~~~
-"Run as background daemon"
-flag off
-details="
-       This option is mostly useful in conjunction with the -R option
-       described below.
-
-       Note that it is not possible to change whether dss runs as background
-       daemon by sending SIGHUP.
-"
-
-option "dry-run" D
-#~~~~~~~~~~~~~~~~~
-"Only print what would be done"
-flag off
-details="
-       This flag does not make sense for all commands. The run
-       command refuses to start if this option was given. The ls
-       command silently ignores this flag.
-"
-
-#################
-section "Logging"
-#################
-
-option "loglevel" l
-#~~~~~~~~~~~~~~~~~~
-"Set loglevel (0-6)"
-int typestr="level"
-default="3"
-optional
-details="
-       Lower values mean more verbose logging.
-"
-
-option "logfile" -
-#~~~~~~~~~~~~~~~~~
-"Logfile for the dss daemon process"
-string typestr="filename"
-optional
-default="/dev/null"
-details = "
-       This option is only honored if both --run and --daemon are
-       given. Otherwise it is silently ignored and log output is written
-       to stderr.
-
-       The default value means that nothing will be logged in daemon mode
-       unless this option is given.
-"
-
-##################
-section "Commands"
-##################
-
-defgroup "command"
-#=================
-groupdesc="
-       dss supports a couple of commands each of which corresponds
-       to a different command line option. Exactly one of these
-       options must be given.
-
-"
-required
-
-groupoption "create" C
-#~~~~~~~~~~~~~~~~~~~~~
-"Create a new snapshot"
-group="command"
-details="
-       Execute the rsync command to create a new snapshot. Note that
-       this command does not care about free disk space.
-"
-
-groupoption "prune" P
-#~~~~~~~~~~~~~~~~~~~~
-"Remove redundant and outdated snapshots"
-group="command"
-details="
-       A snapshot is considered outdated if its interval number
-       is greater or equal than the specified number of unit
-       intervals. See the \"Intervals\" section below for the precise
-       definition of these terms.
-
-       A snapshot is said to be redundant if it belongs to an
-       interval that already contains more than the desired number
-       of snapshots.
-
-       The prune command gets rid of both outdated and redundant
-       snapshots.
-"
-
-groupoption "ls" L
-#~~~~~~~~~~~~~~~~~
-"Print a list of all snapshots"
-group="command"
-details="
-       The list will contain all snapshots no matter of their state,
-       i. e. incomplete snapshots and snapshots being deleted will
-       also be listed.
-"
-
-groupoption "run" R
-#~~~~~~~~~~~~~~~~~~
-"Start creating and pruning snapshots"
-group="command"
-details="
-       This is the main mode of operation. Snapshots will be 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.
-"
-
-groupoption "kill" K
-#~~~~~~~~~~~~~~~~~~~
-"Kill a running dss process"
-group="command"
-details="
-       This sends SIGTERM 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.
-"
-
-groupoption "reload" -
-#~~~~~~~~~~~~~~~~~~~~~
-"force a running dss process to reload its config file"
-group="command"
-details="
-       This differs from --kill only in that SIGHUP rather than SIGTERM
-       is sent to the dss process.
-"
-
-###############################
-section "Rsync-related options"
-###############################
-
-option "remote-host" H
-#~~~~~~~~~~~~~~~~~~~~~
-"Remote host"
-string typestr="hostname"
-default="localhost"
-optional
-details="
-       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.
-"
-
-option "remote-user" U
-#~~~~~~~~~~~~~~~~~~~~~
-"Remote user name (default: current user)"
-string typestr="username"
-optional
-details="
-       Set this if the user running dss is different from the
-       user at the remote host when using ssh.
-"
-
-option "source-dir" -
-#~~~~~~~~~~~~~~~~~~~~
-"The data directory"
-string typestr="dirname"
-required
-details="
-       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.
-"
-
-option "dest-dir" -
-#~~~~~~~~~~~~~~~~~~
-"Snapshot dir"
-string typestr="dirname"
-required
-details="
-       The destination directory on the local host where snapshots
-       will be written. This must be writable by the user who runs
-       dss.
-"
-
-option "no-resume" -
-#~~~~~~~~~~~~~~~~~~~
-"Do not try to resume from previous runs"
-flag off
-details = "
-       Starting from version 0.1.4, dss tries to resume from a
-       previously cancelled dss instance by default. It does so by
-       looking at the status of the most recently created snapshot. If
-       this snapshot status is incomplete, its directory is reused
-       as the destination directory for a subsequent rsync run.
-
-       The --no-resume option deactivates this feature so that a new
-       directory is always used as the rsync destination directory.
-"
-
-option "rsync-option" O
-#~~~~~~~~~~~~~~~~~~~~~~
-"Further rsync options"
-string typestr="option"
-optional
-multiple
-details="
-       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
-"
-
-option "max-rsync-errors" -
-"Terminate after this many rsync failures"
-int typestr="count"
-default="10"
-optional
-details="
-       Only relevant when operating in --run mode (see above). 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.
-"
-
-###################
-section "Intervals"
-###################
-
-option "unit-interval" u
-#~~~~~~~~~~~~~~~~~~~~~~~
-"The duration of a unit interval"
-int typestr="days"
-default="4"
-optional
-details="
-       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.
-"
-
-option "num-intervals" n
-#~~~~~~~~~~~~~~~~~~~~~~~
-"The number of unit intervals"
-int typestr="num"
-default="5"
-optional
-details="
-       Note that increasing this number by one doubles the total number of
-       snapshots. See the documentation of --unit-interval above.
-"
-
-###############
-section "Hooks"
-###############
-
-option "pre-create-hook" r
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Executed before snapshot creation"
-string typestr="command"
-default = "true"
-optional
-details="
-       Execute this command before trying to create a new snapshot.
-       If this command returns with a non-zero exit status, no
-       snapshot is being created and the operation is retried later.
-
-       For example, one might want to execute a script that checks
-       whether all snapshot-related file systems are properly mounted.
-
-       Another possible application of this is to return non-zero
-       during office hours in order to not slow down the file systems
-       by taking snapshots.
-"
-
-option "post-create-hook" o
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Executed after snapshot creation"
-string typestr="command"
-default = "true"
-optional
-details="
-       Execute this after 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 can be used to count the number of
-       files per user and/or the disk usage patterns in order to
-       store them in a database for further analysis.
-"
-
-option "pre-remove-hook" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Executed before snapshot removal"
-string typestr="command"
-default = "true"
-optional
-details="
-       Execute this command before removing a snapshot. The full
-       path to the snapshot about to be deleted is passed to the
-       command as the first argument. If the command returns with
-       a non-zero exit status, no snapshot is being removed and the
-       operation is retried later.
-
-       For example, one might want to 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.
-"
-
-option "post-remove-hook" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Executed after snapshot removal"
-string typestr="command"
-default = "true"
-optional
-details="
-       Execute this after a snapshot has successfully been removed. 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.
-"
-
-option "exit-hook" e
-#~~~~~~~~~~~~~~~~~~~
-"Executed if run command exits"
-string typestr="command"
-default = "true"
-optional
-details="
-       This hook is only used if the --run command was given which
-       instructs dss to run in an endless loop. The exit-hook gets
-       executed whenever this endless loop terminates. The reason
-       for terminating the loop 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.
-"
-
-###############################
-section "Disk space monitoring"
-###############################
-
-option "min-free-mb" m
-#~~~~~~~~~~~~~~~~~~~~~
-"Minimal amount of free disk space"
-int typestr="megabytes"
-default="100"
-optional
-details="
-       If disk space on the file system containing the destination
-       directory gets low, \"dss --run\" will suspend the currently
-       running rsync process and will start 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.
-
-       A value of zero deactivates this check.
-"
-
-option "min-free-percent" p
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Minimal percent of free disk space"
-int typestr="percent"
-default="2"
-optional
-details="
-       See --min-free-mb. Note that 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.
-"
-option "min-free-percent-inodes" i
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Minimal percent of free inodes"
-int typestr="percent"
-default="0"
-optional
-details="
-       Specify the minimum amount of free inodes on the file system
-       containing the destination dir. If less than that many inodes
-       are free, snapshot removal kicks in just as in case of low
-       disk space.
-
-       Note that not every file system supports the concept of inodes.
-       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.
-"
-
-option "keep-redundant" k
-#~~~~~~~~~~~~~~~~~~~~~~~~
-"Prune by disk space only"
-flag off
-details="
-       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.
-"
-
-option "min-complete" -
-#~~~~~~~~~~~~~~~~~~~~~~
-"Minimal number of complete snapshots to keep"
-int typestr = "num"
-default = "1"
-optional
-details = "
-       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 snapshot are left, and there is not
-       enough disk space available for another snapshot, the program
-       terminates with a \"No space left on device\" error.
-"
diff --git a/dss.suite b/dss.suite
new file mode 100644 (file)
index 0000000..e69cc28
--- /dev/null
+++ b/dss.suite
@@ -0,0 +1,499 @@
+[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 to change 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
+               [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 must
+                       be given at the command line or in the config file.
+               [/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.
+               [/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 allows to specify the amount of
+                       free disk space 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 redundant and outdated snapshots
+       [description]
+               A snapshot is considered outdated if its interval number is greater or
+               equal than the specified number of unit intervals. See --unit-interval
+               and --num-intervals above.
+
+               A snapshot is said to be redundant if the interval it belongs to
+               contains more than the configured number of snapshots.
+
+               The prune command gets rid of both outdated and redundant snapshots. At
+               most one snapshot is removed per invocation. If --dry-run is given, the
+               subcommand only prints the snapshot that would be removed.
+       [/description]
+[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]
+
+[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/err.h b/err.h
index e7aced0..3d4e136 100644 (file)
--- a/err.h
+++ b/err.h
@@ -56,7 +56,9 @@ static inline char *dss_strerror(int num)
        DSS_ERROR(SIGNAL, "caught terminating signal"), \
        DSS_ERROR(BUG, "values of beta might cause dom!"), \
        DSS_ERROR(NOT_RUNNING, "dss not running"), \
-       DSS_ERROR(TOO_MANY_RSYNC_ERRORS, "too many consecutive rsync errors")
+       DSS_ERROR(ALREADY_RUNNING, "dss already running"), \
+       DSS_ERROR(TOO_MANY_RSYNC_ERRORS, "too many consecutive rsync errors"), \
+       DSS_ERROR(LOPSUB, "lopsub error")
 
 /**
  * This is temporarily defined to expand to its first argument (prefixed by
diff --git a/file.c b/file.c
index 8494408..d606c16 100644 (file)
--- a/file.c
+++ b/file.c
@@ -69,19 +69,6 @@ out:
        closedir(dir);
        return ret;
 }
-/**
- * Wrapper for chdir(2).
- *
- * \param path The specified directory.
- *
- * \return Standard.
- */
-int dss_chdir(const char *path)
-{
-       if (chdir(path) >= 0)
-               return 1;
-       return -ERRNO_TO_DSS_ERROR(errno);
-}
 
 /**
  * Set a file descriptor to non-blocking mode.
diff --git a/file.h b/file.h
index c462b10..140a505 100644 (file)
--- a/file.h
+++ b/file.h
@@ -3,7 +3,6 @@
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
-int dss_chdir(const char *path);
 int for_each_subdir(int (*func)(const char *, void *), void *private_data);
 __must_check int mark_fd_nonblocking(int fd);
 /**
diff --git a/ipc.c b/ipc.c
index c55554c..183c76e 100644 (file)
--- a/ipc.c
+++ b/ipc.c
@@ -258,11 +258,11 @@ static int get_key_or_die(const char *config_file)
        return ret;
 }
 
-static int mutex_get(int key, int flags)
+static int mutex_get(key_t key, int flags)
 {
        int ret;
 
-       DSS_DEBUG_LOG(("getting semaphore 0x%x\n", key));
+       DSS_DEBUG_LOG(("getting semaphore 0x%lx\n", (long)key));
        ret = semget(key, 2, flags);
        if (ret < 0)
                return -ERRNO_TO_DSS_ERROR(errno);
@@ -282,31 +282,6 @@ static int do_semop(int id, struct sembuf *sops, int num)
        return -ERRNO_TO_DSS_ERROR(errno);
 }
 
-static int mutex_lock(int id)
-{
-       struct sembuf sops[4];
-
-       DSS_DEBUG_LOG(("locking\n"));
-
-       sops[0].sem_num = 0;
-       sops[0].sem_op = 0;
-       sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT;
-
-       sops[1].sem_num = 0;
-       sops[1].sem_op = 1;
-       sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT;
-
-       sops[2].sem_num = 1;
-       sops[2].sem_op = 0;
-       sops[2].sem_flg = SEM_UNDO | IPC_NOWAIT;
-
-       sops[3].sem_num = 1;
-       sops[3].sem_op = 1;
-       sops[3].sem_flg = SEM_UNDO | IPC_NOWAIT;
-
-       return do_semop(id, sops, 4);
-}
-
 static bool mutex_is_locked(int id)
 {
        struct sembuf sops;
@@ -326,23 +301,44 @@ static bool mutex_is_locked(int id)
 
 int lock_dss(char *config_file)
 {
-       int ret, key = get_key_or_die(config_file);
+       int ret, id;
+       struct sembuf sops[4];
+       key_t key = get_key_or_die(config_file);
 
        ret = mutex_get(key, IPC_CREAT | 0600);
        if (ret < 0)
                return ret;
-       return mutex_lock(ret);
+       id = ret;
+
+       sops[0].sem_num = 0;
+       sops[0].sem_op = 0;
+       sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+       sops[1].sem_num = 0;
+       sops[1].sem_op = 1;
+       sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+       sops[2].sem_num = 1;
+       sops[2].sem_op = 0;
+       sops[2].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+       sops[3].sem_num = 1;
+       sops[3].sem_op = 1;
+       sops[3].sem_flg = SEM_UNDO | IPC_NOWAIT;
+
+       return do_semop(id, sops, 4);
 }
 
 int get_dss_pid(char *config_file, pid_t *pid)
 {
-       int ret, semid, key = get_key_or_die(config_file);
+       int ret, semid;
+       key_t key = get_key_or_die(config_file);
 
        if (pid)
                *pid = 0;
        ret = mutex_get(key, 0);
        if (ret < 0)
-               return ret;
+               return ret == -ERRNO_TO_DSS_ERROR(ENOENT)? -E_NOT_RUNNING : ret;
        semid = ret;
        ret = semctl(semid, 1, GETPID);
        if (ret < 0)
diff --git a/mklogo b/mklogo
new file mode 100755 (executable)
index 0000000..54c8c95
--- /dev/null
+++ b/mklogo
@@ -0,0 +1,100 @@
+#!/bin/bash
+
+# Script for Image Magick that writes the dss logo in png format
+# to stdout.
+
+set -u
+
+width=320
+height=110
+border=5
+radius1=4
+radius2=5
+circle_y=90
+arrow_y=70
+text_x=128
+text_y=55
+pointsize=36
+
+declare -a circle arrow rectangle text
+
+make_circles()
+{
+       local inner='stroke black fill red circle'
+       local outer='stroke black fill black circle'
+       local num=1
+       local idx=0
+       local y=$circle_y
+       local step x0 i j idx num
+
+       for ((i = 0; i < 4; i++)); do
+               step=$(((width / 4 + num) / (num + 1)))
+               x0=$((border + width / 4 * i))
+               for ((j = 1; j <= $num; j++)); do
+                       x=$((x0 + j * $step))
+                       circle[$idx]='-draw'; let idx++
+                       circle[$idx]="$outer $x,$y,$((x + radius2)),$y"; let idx++
+                       circle[$idx]='-draw'; let idx++
+                       circle[$idx]="$inner $x,$y,$((x + radius1)),$y"; let idx++
+               done
+               num=$((num * 2))
+       done
+       #echo "${circle[@]}"; exit 0
+}
+
+make_arrow()
+{
+       local arrow_head='l -15,-5  +5,+5  -5,+5  +15,-5 z'
+       local idx=0
+       local x0 x1 y
+
+       arrow[$idx]='-draw'; let idx++
+       x0=$((3 * border)); x1=$((width - 2 * border))
+       y=$arrow_y
+       arrow[$idx]="stroke white line $x0,$y $x1,$y"; let idx++
+       arrow[$idx]='-draw'; let idx++
+       x0=$((width - 2 * border))
+       arrow[$idx]="stroke white fill white path 'M $x0,$y $arrow_head'"
+       #echo "${arrow[@]}"; exit 0
+}
+
+make_rectangles()
+{
+       local idx=0
+       local x x0 x1 y y0 y1 i red_green color
+
+       rectangle[$idx]='-draw'; let idx++
+       x=$((width + 2 * border))
+       y=$((height + 2 * border))
+       rectangle[$idx]="stroke yellow fill yellow rectangle 0,0 $x,$y"; let idx++
+       for ((i = 0; i < 4; i++)); do
+               rectangle[$idx]='-draw'; let idx++
+               red_green="$(printf '%02x' $(((3 - i) * 60)))"
+               color="#${red_green}${red_green}ff"
+               x0=$((border + i * width / 4)); x1=$((x0 + width / 4 - 1))
+               y0=$border; y1=$((y0 + height))
+               rectangle[$idx]="stroke $color fill $color rectangle $x0,$y0 $x1,$y1"
+               let idx++
+       done
+       #echo "${rectangle[@]}"; exit 0
+}
+
+make_text()
+{
+       text=(-pointsize $pointsize -draw \
+               "fill white text $text_x,$text_y DSS")
+       #echo "${text[@]}"; exit 0
+}
+
+make_rectangles
+make_arrow
+make_circles
+make_text
+
+convert -size $((width + 2 * border))x$((height + 2 * border)) \
+       -background none xc: \
+       "${rectangle[@]}" \
+       "${arrow[@]}" \
+       "${circle[@]}" \
+       "${text[@]}" \
+       png:-