--- /dev/null
+dss_objects := dss.o cmdline.o string.o fd.o exec.o signal.o daemon.o
+all: dss
+man: dss.1
+
+DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W
+DEBUG_CPPFLAGS += -Wredundant-decls
+CPPFLAGS += -Os
+CPPFLAGS += -Wall
+CPPFLAGS += -Wuninitialized
+CPPFLAGS += -Wchar-subscripts
+CPPFLAGS += -Wformat-security
+CPPFLAGS += -Werror-implicit-function-declaration
+CPPFLAGS += -Wmissing-format-attribute
+CPPFLAGS += -Wunused-macros
+CPPFLAGS += -Wbad-function-cast
+
+Makefile.deps: $(wildcard *.c *.h)
+ gcc -MM -MG *.c > $@
+
+include Makefile.deps
+
+dss: $(dss_objects)
+ $(CC) $(CPPFLAGS) $(DEBUG_CPPFLAGS) -o $@ $(dss_objects)
+
+%.o: %.c
+ $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) $<
+
+%.ppm: %.sk
+ sk2ppm $< > $@
+%.png: %.ppm
+ convert $< $@
+
+cmdline.c cmdline.h: dss.ggo
+ gengetopt --unamed-opts=command --conf-parser < $<
+
+dss.1: dss
+ help2man -N ./$< > $@
+
+%.1.html: %.1
+ man2html $< > $@
+
+clean:
+ rm -f *.o dss dss.1 dss.1.html Makefile.deps *.ppm *.png *~ cmdline.c cmdline.h
+
+
--- /dev/null
+dss creates hardlink-based snapshots of a given directory on a remote
+or local host using rsync's link-dest feature.
+
+dss is admin friendly: It is easy to configure and needs little
+attention once the dss daemon is running because dss keeps track of
+the available disk space and removes snapshots if disk space becomes
+sparse or snapshots become older than the specified time.
+
+It's also user-friendly because users can browse the snapshot
+directories and see the contents of the file system at the time the
+snapshot was taken. For example, users can simply copy data from the
+snapshot directory back to the live system in case they deleted files
+by accident.
+
+Snapshot pruning takes place in a dyadic fashion: Many recent snapshots
+are available, but the number of snapshots per time interval decreases
+exponentially. For example, one can configure dss so that it keeps
+16 snapshots not older than one week, 8 snapshots between one and
+two weeks old, 4 snapshots between two and three weeks old and so on.
--- /dev/null
+/*
+ * Copyright (C) 1997-2008 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file daemon.c Some helpers for programs that detach from the console. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <assert.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#include "gcc-compat.h"
+#include "error.h"
+#include "log.h"
+#include "string.h"
+
+/**
+ * Do the usual stuff to become a daemon.
+ *
+ * Fork, become session leader, dup fd 0, 1, 2 to /dev/null.
+ *
+ * \sa fork(2), setsid(2), dup(2).
+ */
+void daemon_init(void)
+{
+ pid_t pid;
+ int null;
+
+ DSS_INFO_LOG("daemonizing\n");
+ pid = fork();
+ if (pid < 0)
+ goto err;
+ if (pid)
+ exit(EXIT_SUCCESS); /* parent exits */
+ /* become session leader */
+ if (setsid() < 0)
+ goto err;
+ if (chdir("/") < 0)
+ goto err;
+ umask(0);
+ null = open("/dev/null", O_RDONLY);
+ if (null < 0)
+ goto err;
+ if (dup2(null, STDIN_FILENO) < 0)
+ goto err;
+ if (dup2(null, STDOUT_FILENO) < 0)
+ goto err;
+ if (dup2(null, STDERR_FILENO) < 0)
+ goto err;
+ close(null);
+ return;
+err:
+ DSS_EMERG_LOG("fatal: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+}
+
+/**
+ * fopen() the given file in append mode.
+ *
+ * \param logfile_name The name of the file to open.
+ *
+ * \return Either calls exit() or returns a valid file handle.
+ */
+FILE *open_log(const char *logfile_name)
+{
+ FILE *logfile;
+
+ assert(logfile_name);
+ logfile = fopen(logfile_name, "a");
+ if (!logfile) {
+ DSS_EMERG_LOG("can not open %s: %s\n", logfile_name,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ setlinebuf(logfile);
+ return logfile;
+}
+
+/**
+ * Close the log file of the daemon.
+ *
+ * \param logfile The log file handle.
+ *
+ * It's OK to call this with logfile == \p NULL.
+ */
+void close_log(FILE* logfile)
+{
+ if (!logfile)
+ return;
+ DSS_INFO_LOG("closing logfile\n");
+ fclose(logfile);
+}
+
+/**
+ * Log the startup message.
+ */
+void log_welcome(int loglevel)
+{
+ DSS_INFO_LOG("***** welcome to dss ******\n");
+ DSS_DEBUG_LOG("using loglevel %d\n", loglevel);
+}
+
+int com_daemon(int argc, char * const * argv)
+{
+ return 1;
+}
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <fnmatch.h>
+#include <limits.h>
+
+
+#include "gcc-compat.h"
+#include "cmdline.h"
+#include "log.h"
+#include "string.h"
+#include "error.h"
+#include "fd.h"
+#include "exec.h"
+
+
+struct gengetopt_args_info conf;
+char *dss_error_txt = NULL;
+
+DEFINE_DSS_ERRLIST;
+
+/** Defines one dss command. */
+struct server_command {
+ /** The name of the command. */
+ const char *name;
+ /** Pointer to the function that handles the command. */
+ int (*handler)(int, char * const * const);
+};
+
+/* a litte cpp magic helps to DRY */
+#define SERVER_COMMANDS \
+ SERVER_COMMAND(ls) \
+ SERVER_COMMAND(create) \
+ SERVER_COMMAND(prune) \
+ SERVER_COMMAND(daemon)
+#define SERVER_COMMAND(x) int com_ ##x(int, char * const * const);
+SERVER_COMMANDS
+#undef SERVER_COMMAND
+#define SERVER_COMMAND(x) {.name = #x, .handler = com_ ## x},
+static struct server_command command_list[] = {
+ SERVER_COMMANDS
+ {.name = NULL, .handler = NULL}
+};
+#undef SERVER_COMMAND
+
+/*
+ * complete, not being deleted: 1204565370-1204565371.Sun_Mar_02_2008_14_33-Sun_Mar_02_2008_14_43
+ * complete, being deleted: 1204565370-1204565371.being_deleted
+ * incomplete, not being deleted: 1204565370-incomplete
+ * incomplete, being deleted: 1204565370-incomplete.being_deleted
+ */
+enum snapshot_status_flags {
+ SS_COMPLETE = 1,
+ SS_BEING_DELETED = 2,
+};
+
+struct snapshot {
+ char *name;
+ int64_t creation_time;
+ int64_t completion_time;
+ enum snapshot_status_flags flags;
+ unsigned interval;
+};
+
+int is_snapshot(const char *dirname, int64_t now, struct snapshot *s)
+{
+ int i, ret;
+ char *dash, *dot, *tmp;
+ int64_t num;
+
+ assert(dirname);
+ dash = strchr(dirname, '-');
+ if (!dash || !dash[1] || dash == dirname)
+ return 0;
+ for (i = 0; dirname[i] != '-'; i++)
+ if (!isdigit(dirname[i]))
+ return 0;
+ tmp = dss_strdup(dirname);
+ tmp[i] = '\0';
+ ret = dss_atoi64(tmp, &num);
+ free(tmp);
+ if (ret < 0) {
+ free(dss_error_txt);
+ return 0;
+ }
+ assert(num >= 0);
+ if (num > now)
+ return 0;
+ s->creation_time = num;
+ //DSS_DEBUG_LOG("%s start time: %lli\n", dirname, (long long)s->creation_time);
+ s->interval = (long long) ((now - s->creation_time)
+ / conf.unit_interval_arg / 24 / 3600);
+ if (!strcmp(dash + 1, "incomplete")) {
+ s->completion_time = -1;
+ s->flags = 0; /* neither complete, nor being deleted */
+ goto success;
+ }
+ if (!strcmp(dash + 1, "incomplete.being_deleted")) {
+ s->completion_time = -1;
+ s->flags = SS_BEING_DELETED; /* mot cpmplete, being deleted */
+ goto success;
+ }
+ tmp = dash + 1;
+ dot = strchr(tmp, '.');
+ if (!dot || !dot[1] || dot == tmp)
+ return 0;
+ for (i = 0; tmp[i] != '.'; i++)
+ if (!isdigit(tmp[i]))
+ return 0;
+ tmp = dss_strdup(dash + 1);
+ tmp[i] = '\0';
+ ret = dss_atoi64(tmp, &num);
+ free(tmp);
+ if (ret < 0) {
+ free(dss_error_txt);
+ return 0;
+ }
+ if (num > now)
+ return 0;
+ s->completion_time = num;
+ s->flags = SS_COMPLETE;
+ if (strcmp(dot + 1, "being_deleted"))
+ s->flags |= SS_BEING_DELETED;
+success:
+ s->name = dss_strdup(dirname);
+ return 1;
+}
+
+int64_t get_current_time(void)
+{
+ time_t now;
+ time(&now);
+ DSS_DEBUG_LOG("now: %lli\n", (long long) now);
+ return (int64_t)now;
+}
+
+char *incomplete_name(int64_t start)
+{
+ return make_message("%lli-incomplete", (long long)start);
+}
+
+char *being_deleted_name(struct snapshot *s)
+{
+ if (s->flags & SS_COMPLETE)
+ return make_message("%lli-%lli.being_deleted",
+ (long long)s->creation_time,
+ (long long)s->completion_time);
+ return make_message("%lli-incomplete.being_deleted",
+ (long long)s->creation_time);
+}
+
+int complete_name(int64_t start, int64_t end, char **result)
+{
+ struct tm start_tm, end_tm;
+ time_t *start_seconds = (time_t *) (uint64_t *)&start; /* STFU, gcc */
+ time_t *end_seconds = (time_t *) (uint64_t *)&end; /* STFU, gcc */
+ char start_str[200], end_str[200];
+
+ if (!localtime_r(start_seconds, &start_tm)) {
+ make_err_msg("%lli", (long long)start);
+ return -E_LOCALTIME;
+ }
+ if (!localtime_r(end_seconds, &end_tm)) {
+ make_err_msg("%lli", (long long)end);
+ return -E_LOCALTIME;
+ }
+ if (!strftime(start_str, sizeof(start_str), "%a_%b_%d_%Y_%H_%M_%S", &start_tm)) {
+ make_err_msg("%lli", (long long)start);
+ return -E_STRFTIME;
+ }
+ if (!strftime(end_str, sizeof(end_str), "%a_%b_%d_%Y_%H_%M_%S", &end_tm)) {
+ make_err_msg("%lli", (long long)end);
+ return -E_STRFTIME;
+ }
+ *result = make_message("%lli-%lli.%s-%s", (long long) start, (long long) end,
+ start_str, end_str);
+ return 1;
+}
+
+struct snapshot_list {
+ int64_t now;
+ unsigned num_snapshots;
+ unsigned array_size;
+ struct snapshot **snapshots;
+ /**
+ * Array of size num_intervals + 1
+ *
+ * It contains the number of snapshots in each interval. interval_count[num_intervals]
+ * is the number of snapshots which belong to any interval greater than num_intervals.
+ */
+ unsigned *interval_count;
+};
+
+#define FOR_EACH_SNAPSHOT(s, i, sl) \
+ for ((i) = 0; (i) < (sl)->num_snapshots && ((s) = (sl)->snapshots[(i)]); (i)++)
+
+
+
+#define NUM_COMPARE(x, y) ((int)((x) < (y)) - (int)((x) > (y)))
+
+static int compare_snapshots(const void *a, const void *b)
+{
+ struct snapshot *s1 = *(struct snapshot **)a;
+ struct snapshot *s2 = *(struct snapshot **)b;
+ return NUM_COMPARE(s2->creation_time, s1->creation_time);
+}
+
+/** Compute the minimum of \a a and \a b. */
+#define DSS_MIN(a,b) ((a) < (b) ? (a) : (b))
+
+int add_snapshot(const char *dirname, void *private)
+{
+ struct snapshot_list *sl = private;
+ struct snapshot s;
+ int ret = is_snapshot(dirname, sl->now, &s);
+
+ if (!ret)
+ return 1;
+ if (sl->num_snapshots >= sl->array_size) {
+ sl->array_size = 2 * sl->array_size + 1;
+ sl->snapshots = dss_realloc(sl->snapshots,
+ sl->array_size * sizeof(struct snapshot *));
+ }
+ sl->snapshots[sl->num_snapshots] = dss_malloc(sizeof(struct snapshot));
+ *(sl->snapshots[sl->num_snapshots]) = s;
+ sl->interval_count[DSS_MIN(s.interval, conf.num_intervals_arg)]++;
+ sl->num_snapshots++;
+ return 1;
+}
+
+void get_snapshot_list(struct snapshot_list *sl)
+{
+ sl->now = get_current_time();
+ sl->num_snapshots = 0;
+ sl->array_size = 0;
+ sl->snapshots = NULL;
+ sl->interval_count = dss_calloc((conf.num_intervals_arg + 1) * sizeof(unsigned));
+ for_each_subdir(add_snapshot, sl);
+ qsort(sl->snapshots, sl->num_snapshots, sizeof(struct snapshot *),
+ compare_snapshots);
+}
+
+void free_snapshot_list(struct snapshot_list *sl)
+{
+ int i;
+ struct snapshot *s;
+
+ FOR_EACH_SNAPSHOT(s, i, sl) {
+ free(s->name);
+ free(s);
+ }
+ free(sl->interval_count);
+ free(sl->snapshots);
+}
+
+/**
+ * Print a log message about the exit status of a child.
+ */
+void log_termination_msg(pid_t pid, int status)
+{
+ if (WIFEXITED(status))
+ DSS_INFO_LOG("child %i exited. Exit status: %i\n", (int)pid,
+ WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ DSS_NOTICE_LOG("child %i was killed by signal %i\n", (int)pid,
+ WTERMSIG(status));
+ else
+ DSS_WARNING_LOG("child %i terminated abormally\n", (int)pid);
+}
+
+int wait_for_process(pid_t pid, int *status)
+{
+ int ret;
+
+ DSS_DEBUG_LOG("Waiting for process %d to terminate\n", (int)pid);
+ for (;;) {
+ ret = waitpid(pid, status, 0);
+ if (ret >= 0 || errno != EINTR)
+ break;
+ }
+ if (ret < 0) {
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ make_err_msg("failed to wait for process %d", (int)pid);
+ } else
+ log_termination_msg(pid, *status);
+ return ret;
+}
+
+int remove_snapshot(struct snapshot *s, pid_t *pid)
+{
+ int fds[3] = {0, 0, 0};
+ char *new_name = being_deleted_name(s);
+ int ret = dss_rename(s->name, new_name);
+ char *argv[] = {"rm", "-rf", new_name, NULL};
+
+ if (ret < 0)
+ goto out;
+ DSS_NOTICE_LOG("removing %s (interval = %i)\n", s->name, s->interval);
+ ret = dss_exec(pid, argv[0], argv, fds);
+out:
+ free(new_name);
+ return ret;
+}
+
+int remove_redundant_snapshot(struct snapshot_list *sl,
+ int dry_run, pid_t *pid)
+{
+ int ret, i, interval;
+ struct snapshot *s;
+ unsigned missing = 0;
+
+ DSS_INFO_LOG("looking for intervals containing too many snapshots\n");
+ for (interval = conf.num_intervals_arg - 1; interval >= 0; interval--) {
+ unsigned keep = 1<<(conf.num_intervals_arg - interval - 1);
+ unsigned num = sl->interval_count[interval];
+ struct snapshot *victim = NULL, *prev = NULL;
+ int64_t score = LONG_MAX;
+
+ if (keep >= num)
+ missing += keep - num;
+ DSS_DEBUG_LOG("interval %i: keep: %u, have: %u, missing: %u\n",
+ interval, keep, num, missing);
+ if (keep + missing >= num)
+ continue;
+ /* redundant snapshot in this interval, pick snapshot with lowest score */
+ FOR_EACH_SNAPSHOT(s, i, sl) {
+ int64_t this_score;
+
+ DSS_DEBUG_LOG("checking %s\n", s->name);
+ if (s->interval > interval) {
+ prev = s;
+ continue;
+ }
+ if (s->interval < interval)
+ break;
+ if (!victim) {
+ victim = s;
+ prev = s;
+ continue;
+ }
+ assert(prev);
+ /* check if s is a better victim */
+ this_score = s->creation_time - prev->creation_time;
+ assert(this_score >= 0);
+ DSS_DEBUG_LOG("%s: score %lli\n", s->name, (long long)score);
+ if (this_score < score) {
+ score = this_score;
+ victim = s;
+ }
+ prev = s;
+ }
+ assert(victim);
+ if (dry_run) {
+ printf("%s would be removed (interval = %i)\n",
+ victim->name, victim->interval);
+ continue;
+ }
+ ret = remove_snapshot(victim, pid);
+ return ret < 0? ret : 1;
+ }
+ return 0;
+}
+
+int remove_old_snapshot(struct snapshot_list *sl, int dry_run, pid_t *pid)
+{
+ int i, ret;
+ struct snapshot *s;
+
+ DSS_INFO_LOG("looking for snapshots belonging to intervals greater than %d\n",
+ conf.num_intervals_arg);
+ FOR_EACH_SNAPSHOT(s, i, sl) {
+ if (s->interval <= conf.num_intervals_arg)
+ continue;
+ if (dry_run) {
+ printf("%s would be removed (interval = %i)\n",
+ s->name, s->interval);
+ continue;
+ }
+ ret = remove_snapshot(s, pid);
+ if (ret < 0)
+ return ret;
+ return 1;
+ }
+ return 0;
+}
+
+int wait_for_rm_process(pid_t pid)
+{
+ int status, es, ret = wait_for_process(pid, &status);
+ if (ret < 0)
+ return ret;
+ if (!WIFEXITED(status)) {
+ ret = E_INVOLUNTARY_EXIT;
+ make_err_msg("rm process %d died involuntary", (int)pid);
+ return ret;
+ }
+ es = WEXITSTATUS(status);
+ if (es) {
+ ret = -E_BAD_EXIT_CODE;
+ make_err_msg("rm process %d returned %d", (int)pid, es);
+ return ret;
+ }
+ return 1;
+}
+
+int com_prune(int argc, char * const * argv)
+{
+ int ret, dry_run = 0;
+ struct snapshot_list sl;
+ pid_t pid;
+
+ if (argc > 2) {
+ make_err_msg("too many arguments");
+ return -E_SYNTAX;
+ }
+ if (argc == 2) {
+ if (strcmp(argv[1], "-d")) {
+ make_err_msg("%s", argv[1]);
+ return -E_SYNTAX;
+ }
+ dry_run = 1;
+ }
+ for (;;) {
+ get_snapshot_list(&sl);
+ ret = remove_old_snapshot(&sl, dry_run, &pid);
+ free_snapshot_list(&sl);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ break;
+ ret = wait_for_rm_process(pid);
+ if (ret < 0)
+ goto out;
+ }
+ for (;;) {
+ get_snapshot_list(&sl);
+ ret = remove_redundant_snapshot(&sl, dry_run, &pid);
+ free_snapshot_list(&sl);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ break;
+ ret = wait_for_rm_process(pid);
+ if (ret < 0)
+ goto out;
+ }
+ return 1;
+out:
+ return ret;
+}
+
+struct newest_snapshot_data {
+ char * newest_name;
+ int64_t newest_creation_time;
+ int64_t now;
+};
+
+int get_newest_complete(const char *dirname, void *private)
+{
+ struct newest_snapshot_data *nsd = private;
+ struct snapshot s;
+ int ret = is_snapshot(dirname, nsd->now, &s);
+
+ if (ret <= 0)
+ return 1;
+ if (s.creation_time < nsd->newest_creation_time)
+ return 1;
+ nsd->newest_creation_time = s.creation_time;
+ free(nsd->newest_name);
+ nsd->newest_name = s.name;
+ return 1;
+}
+
+__malloc char *name_of_newest_complete_snapshot(void)
+{
+ struct newest_snapshot_data nsd = {
+ .now = get_current_time(),
+ .newest_creation_time = -1
+ };
+ for_each_subdir(get_newest_complete, &nsd);
+ return nsd.newest_name;
+}
+
+void create_rsync_argv(char ***argv, int64_t *num)
+{
+ char *logname, *newest = name_of_newest_complete_snapshot();
+ int i = 0, j;
+
+ *argv = dss_malloc((15 + conf.rsync_option_given) * sizeof(char *));
+ (*argv)[i++] = dss_strdup("rsync");
+ (*argv)[i++] = dss_strdup("-aq");
+ (*argv)[i++] = dss_strdup("--delete");
+ for (j = 0; j < conf.rsync_option_given; j++)
+ (*argv)[i++] = dss_strdup(conf.rsync_option_arg[j]);
+ if (newest) {
+ DSS_INFO_LOG("using %s as reference snapshot\n", newest);
+ (*argv)[i++] = make_message("--link-dest=../%s", newest);
+ free(newest);
+ } else
+ DSS_INFO_LOG("no previous snapshot found");
+ if (conf.exclude_patterns_given) {
+ (*argv)[i++] = dss_strdup("--exclude-from");
+ (*argv)[i++] = dss_strdup(conf.exclude_patterns_arg);
+
+ }
+ logname = dss_logname();
+ if (conf.remote_user_given && !strcmp(conf.remote_user_arg, logname))
+ (*argv)[i++] = dss_strdup(conf.source_dir_arg);
+ 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);
+ free(logname);
+ *num = get_current_time();
+ (*argv)[i++] = incomplete_name(*num);
+ (*argv)[i++] = NULL;
+ for (j = 0; j < i; j++)
+ DSS_DEBUG_LOG("argv[%d] = %s\n", j, (*argv)[j]);
+}
+
+void free_rsync_argv(char **argv)
+{
+ int i;
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+int create_snapshot(char **argv, pid_t *pid)
+{
+ int fds[3] = {0, 0, 0};
+
+ return dss_exec(pid, argv[0], argv, fds);
+}
+
+int rename_incomplete_snapshot(int64_t start)
+{
+ char *old_name, *new_name;
+ int ret;
+
+ ret = complete_name(start, get_current_time(), &new_name);
+ if (ret < 0)
+ return ret;
+ old_name = incomplete_name(start);
+ ret = dss_rename(old_name, new_name);
+ if (ret >= 0)
+ DSS_NOTICE_LOG("%s -> %s\n", old_name, new_name);
+ free(old_name);
+ free(new_name);
+ return ret;
+}
+
+int com_create(int argc, __a_unused char * const * argv)
+{
+ int ret, status, es;
+ char **rsync_argv;
+ int64_t snapshot_num;
+ pid_t pid;
+
+ if (argc != 1) {
+ ret = -E_SYNTAX;
+ make_err_msg("create: no args expected, %d given", argc - 1);
+ return ret;
+ }
+ create_rsync_argv(&rsync_argv, &snapshot_num);
+ DSS_NOTICE_LOG("creating snapshot %lli\n", (long long)snapshot_num);
+ ret = create_snapshot(rsync_argv, &pid);
+ if (ret < 0)
+ goto out;
+ ret = wait_for_process(pid, &status);
+ if (ret < 0)
+ goto out;
+ if (!WIFEXITED(status)) {
+ ret = E_INVOLUNTARY_EXIT;
+ make_err_msg("rsync process %d died involuntary", (int)pid);
+ goto out;
+ }
+ es = WEXITSTATUS(status);
+ if (es != 0 && es != 23 && es != 24) {
+ ret = -E_BAD_EXIT_CODE;
+ make_err_msg("rsync process %d returned %d", (int)pid, es);
+ goto out;
+ }
+ ret = rename_incomplete_snapshot(snapshot_num);
+out:
+ free_rsync_argv(rsync_argv);
+ return ret;
+}
+
+int com_ls(int argc, __a_unused char * const * argv)
+{
+ int i, ret;
+ struct snapshot_list sl;
+ struct snapshot *s;
+ if (argc != 1) {
+ ret = -E_SYNTAX;
+ make_err_msg("ls: no args expected, %d given", argc - 1);
+ return ret;
+ }
+ get_snapshot_list(&sl);
+ FOR_EACH_SNAPSHOT(s, i, &sl)
+ printf("%u\t%s\n", s->interval, s->name);
+ free_snapshot_list(&sl);
+ return 1;
+}
+
+/* TODO: Unlink pid file */
+__noreturn void clean_exit(int status)
+{
+ //kill(0, SIGTERM);
+ free(dss_error_txt);
+ exit(status);
+}
+
+__printf_2_3 void dss_log(int ll, const char* fmt,...)
+{
+ va_list argp;
+ if (ll < conf.loglevel_arg)
+ return;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+}
+
+int read_config_file(void)
+{
+ int ret;
+ char *config_file;
+ struct stat statbuf;
+
+ if (conf.config_file_given)
+ config_file = dss_strdup(conf.config_file_arg);
+ else {
+ char *home = get_homedir();
+ config_file = make_message("%s/.dssrc", home);
+ free(home);
+ }
+ ret = stat(config_file, &statbuf);
+ if (ret && conf.config_file_given) {
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ make_err_msg("failed to stat config file %s", config_file);
+ goto out;
+ }
+ if (!ret) {
+ struct cmdline_parser_params params = {
+ .override = 0,
+ .initialize = 0,
+ .check_required = 0,
+ .check_ambiguity = 0
+ };
+ cmdline_parser_config_file(config_file, &conf, ¶ms);
+ }
+ if (!conf.source_dir_given || !conf.dest_dir_given) {
+ ret = -E_SYNTAX;
+ make_err_msg("you need to specify both source_dir and dest_dir");
+ goto out;
+ }
+ ret = 1;
+out:
+ free(config_file);
+ return ret;
+}
+
+int check_config(void)
+{
+ if (conf.unit_interval_arg <= 0) {
+ make_err_msg("bad unit interval: %i", conf.unit_interval_arg);
+ return -E_INVALID_NUMBER;
+ }
+ DSS_DEBUG_LOG("unit interval: %i day(s)\n", conf.unit_interval_arg);
+ if (conf.num_intervals_arg <= 0) {
+ make_err_msg("bad number of intervals %i", conf.num_intervals_arg);
+ return -E_INVALID_NUMBER;
+ }
+ DSS_DEBUG_LOG("number of intervals: %i\n", conf.num_intervals_arg);
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ int i, ret;
+
+ cmdline_parser(argc, argv, &conf); /* aborts on errors */
+ if (!conf.inputs_num) {
+ ret = -E_SYNTAX;
+ make_err_msg("no command given");
+ goto out;
+ }
+ ret = read_config_file();
+ if (ret < 0)
+ goto out;
+ ret = check_config();
+ if (ret < 0)
+ goto out;
+ ret = dss_chdir(conf.dest_dir_arg);
+ if (ret < 0)
+ goto out;
+ for (i = 0; command_list[i].name; i++) {
+ if (strcmp(command_list[i].name, conf.inputs[0]))
+ continue;
+ ret = command_list[i].handler(conf.inputs_num, conf.inputs);
+ goto out;
+ }
+ ret = -E_INVALID_COMMAND;
+ make_err_msg("%s", conf.inputs[0]);
+out:
+ if (ret < 0)
+ log_err_msg(EMERG, -ret);
+ clean_exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE);
+}
--- /dev/null
+
+text "
+dss snapshot aging is implemented in terms of intervals. There are
+two command line options related to intervals: the duration of a
+'unit' interval and the number of those intervals.
+
+dss removes any snapshots older than the given number of intervals
+times the duration of an unit interval and tries to keep the following
+amount of snapshots per interval:
+
+ interval number number of snapshots
+ ===============================================
+ 0 2 ^ (num_intervals - 1)
+ 1 2 ^ (num_intervals - 2)
+ 2 2 ^ (num_intervals - 3)
+ ...
+ num_intervals - 2 2
+ num_intervals - 1 1
+ num_intervals 0
+
+In other words, the oldest snapshot will at most be unit_interval *
+num_intervala old (= 5 days * 4 = 20 days if default values are used).
+Moreover, there are at most 2^num_intervals - 1 snapshots in total
+(i.e. 31 by default). Observe that you have to create at least
+num_intervals snapshots each interval for this to work out. "
+
+
+
+package "dss"
+version "0.0.1"
+
+option "config_file" c
+#~~~~~~~~~~~~~~~~~~~~~
+"(default='~/.dssrc')"
+
+ string typestr="filename"
+ optional
+
+section "Logging"
+#~~~~~~~~~~~~~~~~
+
+option "loglevel" l
+#~~~~~~~~~~~~~~~~~~
+
+"set loglevel (0-6)"
+
+ int typestr="level"
+ default="4"
+ optional
+
+option "logfile" L
+#~~~~~~~~~~~~~~~~~~
+
+"logfile for the dss daemon process"
+
+ string typestr="filename"
+ optional
+
+section "rsync-related options"
+#==============================
+
+option "remote_user" U
+#~~~~~~~~~~~~~~~~~~~~~
+
+"remote user name (default: current user)"
+
+ string typestr="username"
+ optional
+
+option "remote_host" H
+#~~~~~~~~~~~~~~~~~~~~~
+
+"remote host"
+
+ string typestr="hostname"
+ default="localhost"
+ optional
+
+option "source_dir" S
+#~~~~~~~~~~~~~~~~~~~~
+
+"directory to backup on the remote host"
+
+ string typestr="dirname"
+ optional
+
+option "dest_dir" D
+#~~~~~~~~~~~~~~~~~~
+
+"snapshots dir on the local host"
+
+ string typestr="dirname"
+ optional
+
+option "rsync_option" O
+#~~~~~~~~~~~~~~~~~~~~~~
+
+"further rsync options that are passed
+verbatim to the rsync command."
+
+ string typestr="option"
+ optional
+ multiple
+
+
+option "exclude_patterns" e
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+"rsync exclude patterns"
+
+ string typestr="path"
+ optional
+
+
+section "Intervals"
+#~~~~~~~~~~~~~~~~~~
+
+
+option "unit_interval" u
+#~~~~~~~~~~~~~~~~~~~~~~~
+"the duration of a unit interval"
+
+ int typestr="days"
+ default="4"
+ optional
+
+option "num_intervals" n
+#~~~~~~~~~~~~~~~~~~~~~~~
+"the number of unit intervals"
+
+ int typestr="num"
+ default="5"
+ optional
+
+section "Hooks"
+#==============
+
+option "pre_create_hook" r
+#~~~~~~~~~~~~~~~~~~~~~~~~~~
+"Executed before snapshot creation"
+
+ string typestr="command"
+ default="/bin/true"
+ optional
+
+text "
+ Execute this command before trying to create a new snapshot
+ If this command returns with a non-zero exit status, do not
+ perform the backup. One 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="/bin/true"
+ optional
+
+text "
+ Execute this after a snapshot has successfully been created
+ The return value on the command is ignored. For instance one
+ could count the number of files per user and/or the disk
+ usage patterns in order to store them in a database for
+ further treatment.
+"
+option "creation_sleep" s
+#~~~~~~~~~~~~~~~~~~~~~~~~
+"sleep interval"
+
+ int typestr="minutes"
+ default="60"
+ optional
+
+text "
+ The sleep interval for snapshot creation in minutes.
+ The daemon will, in an endlees loop, create a snapshot and
+ then sleep that many minutes.
+"
+
+
+option "min_free" m
+#~~~~~~~~~~~~~~~~~~
+
+"minimal amount of free space"
+
+ int typestr="gigabytes"
+ default="50"
+ optional
+
+text "
+ If less that this many gigabytes of space is available,
+ dss will start to remove snapshots (starting from the oldest
+ snapshot) until the free disk space exeecds this value.
+"
+
+
+text "
+subcommands:
+
+ls:
+
+ Print list of existing snapshots.
+
+ Usage: ls
+
+free:
+
+ Remove old snapshots in order to free space.
+
+ Usage: free [size]
+
+ Without size parameter, print the amount of free space on the file system
+ in human-readable format.
+
+ Otherwise, remove snapshots (starting from the oldest one) until the number of
+ free space exceeds the given number of gigabytes.
+ Use with caution!
+"
--- /dev/null
+##Sketch 1 2
+document()
+layout((362.835,119.055),0)
+layer('Layer 1',1,1,0,0,(0,0,0))
+G()
+fp((1,1,0))
+le()
+lw(1)
+r(365.344,0,0,-120.099,0.422729,119.405)
+G()
+fp((0.251,0.251,1))
+le()
+lw(1)
+r(87.8038,0,0,-106.979,183.131,112.845)
+fp((0,0,1))
+le()
+lw(1)
+r(87.8038,0,0,-106.979,271.005,112.845)
+fp((0.753,0.753,1))
+le()
+lw(1)
+r(87.8038,0,0,-106.979,7.38173,112.845)
+fp((0.502,0.502,1))
+le()
+lw(1)
+r(87.8038,0,0,-106.979,95.2567,112.845)
+G()
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,279.884,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,289.891,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,299.897,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,309.903,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,319.91,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,329.916,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,339.922,38.1606)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,349.928,38.1606)
+G_()
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,51.2837,35.1326)
+G()
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,201.095,39.1696)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,218.387,39.1696)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,235.678,39.1696)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,252.969,39.1696)
+G_()
+G()
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,124.56,37.1516)
+fp((1,0,0))
+lw(1)
+e(5.04619,0,0,-5.04619,153.757,37.1516)
+G_()
+fp((0.058,0.227,0.465))
+lp((1,1,1))
+lw(2.83465)
+la2(([(-4.0, 3.0), (2.0, 0.0), (-4.0, -3.0), (-4.0, 3.0)], 1))
+b()
+bs(35.6407,57.3367,0)
+bs(335.841,57.3367,0)
+fp((1,1,1))
+le()
+lw(1)
+Fn('Times-Bold')
+Fs(36)
+txt('DSS',(154.731,69.4476))
+G_()
+G_()
+guidelayer('Guide Lines',1,0,0,1,(0,0,1))
+grid((0,0,20,20),0,(0,0,1),'Grid')
--- /dev/null
+extern char *dss_errlist[];
+extern char *dss_error_txt;
+
+__printf_2_3 void dss_log(int ll, const char* fmt,...);
+
+/**
+ * This bit indicates whether a number is considered a system error number
+ * If yes, the system errno is just the result of clearing this bit from
+ * the given number.
+ */
+#define SYSTEM_ERROR_BIT 30
+
+/** Check whether the system error bit is set. */
+#define IS_SYSTEM_ERROR(num) (!!((num) & (1 << SYSTEM_ERROR_BIT)))
+
+/** Set the system error bit for the given number. */
+#define ERRNO_TO_DSS_ERROR(num) ((num) | (1 << SYSTEM_ERROR_BIT))
+
+/** Check whether a given number is a system error number.
+ *
+ * \param num The value to be checked.
+ * \param _errno The system error number.
+ *
+ * \return True if \a num is dss' representation of the system
+ * error identified by \a _errno.
+ */
+static inline int is_errno(int num, int _errno)
+{
+ assert(num > 0 && _errno > 0);
+ return ERRNO_TO_DSS_ERROR(_errno) == num;
+}
+
+/**
+ * dss' version of strerror(3).
+ *
+ * \param num The error number.
+ *
+ * \return The error text of \a num.
+ */
+static inline char *dss_strerror(int num)
+{
+ assert(num > 0);
+ if (IS_SYSTEM_ERROR(num))
+ return strerror((num) & ((1 << SYSTEM_ERROR_BIT) - 1));
+ else
+ return dss_errlist[num];
+}
+
+static inline void log_err_msg(int loglevel, int num)
+{
+ dss_log(loglevel, "%s (%s)\n", dss_error_txt, dss_strerror(num));
+}
+
+#define DSS_ERRORS \
+ DSS_ERROR(SUCCESS, "success") \
+ DSS_ERROR(SYNTAX, "syntax error") \
+ DSS_ERROR(INVALID_COMMAND, "invalid command") \
+ DSS_ERROR(ATOI_OVERFLOW, "value too large") \
+ DSS_ERROR(STRTOLL, "unknown strtoll error") \
+ DSS_ERROR(ATOI_NO_DIGITS, "no digits found in string") \
+ DSS_ERROR(ATOI_JUNK_AT_END, "further characters after number") \
+ DSS_ERROR(INVALID_NUMBER, "invalid number") \
+ DSS_ERROR(STRFTIME, "strftime() failed") \
+ DSS_ERROR(LOCALTIME, "localtime() failed") \
+ DSS_ERROR(NULL_OPEN, "can not open /dev/null") \
+ DSS_ERROR(DUP_PIPE, "exec error: can not create pipe") \
+ DSS_ERROR(INVOLUNTARY_EXIT, "unexpected termination cause") \
+ DSS_ERROR(BAD_EXIT_CODE, "unexpected exit code") \
+ DSS_ERROR(SIGNAL_SIG_ERR, "signal() returned SIG_ERR")
+
+/**
+ * This is temporarily defined to expand to its first argument (prefixed by
+ * 'E_') and gets later redefined to expand to the error text only
+ */
+#define DSS_ERROR(err, msg) E_ ## err,
+
+enum dss_error_codes {
+ DSS_ERRORS
+};
+#undef DSS_ERROR
+#define DSS_ERROR(err, msg) msg,
+#define DEFINE_DSS_ERRLIST char *dss_errlist[] = {DSS_ERRORS}
--- /dev/null
+/** \file exec.c Helper functions for spawning new processes. */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+
+
+#include "gcc-compat.h"
+#include "error.h"
+#include "string.h"
+
+
+/**
+ * Spawn a new process and redirect fd 0, 1, and 2.
+ *
+ * \param pid Will hold the pid of the created process upon return.
+ * \param file Path of the executable to execute.
+ * \param args The argument array for the command.
+ * \param fds a Pointer to a value-result array.
+ *
+ * \return Standard.
+ *
+ * \sa null(4), pipe(2), dup2(2), fork(2), exec(3).
+ */
+int dss_exec(pid_t *pid, const char *file, char *const *const args, int *fds)
+{
+ int ret, in[2] = {-1, -1}, out[2] = {-1, -1}, err[2] = {-1, -1},
+ null = -1; /* ;) */
+
+ ret = -E_DUP_PIPE;
+ if (fds[0] > 0 && pipe(in) < 0)
+ goto err_out;
+ if (fds[1] > 0 && pipe(out) < 0)
+ goto err_out;
+ if (fds[2] > 0 && pipe(err) < 0)
+ goto err_out;
+ if (!fds[0] || !fds[1] || !fds[2]) {
+ ret = -E_NULL_OPEN;
+ null = open("/dev/null", O_RDONLY);
+ if (null < 0)
+ goto err_out;
+ }
+ if ((*pid = fork()) < 0)
+ exit(EXIT_FAILURE);
+ if (!(*pid)) { /* child */
+ if (fds[0] >= 0) {
+ if (fds[0]) {
+ close(in[1]);
+ if (in[0] != STDIN_FILENO)
+ dup2(in[0], STDIN_FILENO);
+ } else
+ dup2(null, STDIN_FILENO);
+ }
+ if (fds[1] >= 0) {
+ if (fds[1]) {
+ close(out[0]);
+ if (out[1] != STDOUT_FILENO)
+ dup2(out[1], STDOUT_FILENO);
+ } else
+ dup2(null, STDOUT_FILENO);
+ }
+ if (fds[2] >= 0) {
+ if (fds[2]) {
+ close(err[0]);
+ if (err[1] != STDERR_FILENO)
+ dup2(err[1], STDERR_FILENO);
+ } else
+ dup2(null, STDERR_FILENO);
+ }
+ if (null >= 0)
+ close(null);
+ execvp(file, args);
+ _exit(EXIT_FAILURE);
+ }
+ if (fds[0] > 0) {
+ close(in[0]);
+ *fds = in[1];
+ }
+ if (fds[1] > 0) {
+ close(out[1]);
+ *(fds + 1) = out[0];
+ }
+ if (fds[2] > 0) {
+ close(err[1]);
+ *(fds + 2) = err[0];
+ }
+ if (null >= 0)
+ close(null);
+ return 1;
+err_out:
+ make_err_msg("failed to exec %s", file);
+ if (err[0] >= 0)
+ close(err[0]);
+ if (err[1] >= 0)
+ close(err[1]);
+ if (out[0] >= 0)
+ close(out[0]);
+ if (out[1] >= 0)
+ close(out[1]);
+ if (in[0] >= 0)
+ close(in[0]);
+ if (in[1] >= 0)
+ close(in[1]);
+ if (null >= 0)
+ close(null);
+ return ret;
+}
--- /dev/null
+int dss_exec(pid_t *pid, const char *file, char *const *const args, int *fds);
+
--- /dev/null
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+#include "gcc-compat.h"
+#include "error.h"
+#include "string.h"
+
+/**
+ * Call a function for each subdirectory of the current working directory.
+ *
+ * \param dirname The directory to traverse.
+ * \param func The function to call for each subdirecrtory.
+ * \param private_data Pointer to an arbitrary data structure.
+ *
+ * For each top-level directory under \a dirname, the supplied function \a func is
+ * called. The full path of the subdirectory and the \a private_data pointer
+ * are passed to \a func.
+ *
+ * \return This function returns immediately if \a func returned a negative
+ * value. In this case \a func must set error_txt and this negative value is
+ * returned to the caller. Otherwise the function returns when all
+ * subdirectories have been passed to \a func.
+ */
+
+int for_each_subdir(int (*func)(const char *, void *), void *private_data)
+{
+ struct dirent *entry;
+ int ret;
+ DIR *dir = opendir(".");
+
+ if (!dir) {
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ make_err_msg("opendir(\".\") failed");
+ return ret;
+ }
+ while ((entry = readdir(dir))) {
+ mode_t m;
+ struct stat s;
+
+ if (!strcmp(entry->d_name, "."))
+ continue;
+ if (!strcmp(entry->d_name, ".."))
+ continue;
+ ret = lstat(entry->d_name, &s) == -1;
+ if (ret == -1) {
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ make_err_msg("lstat(\"%s\") failed", entry->d_name);
+ goto out;
+ }
+ m = s.st_mode;
+ if (!S_ISDIR(m))
+ continue;
+ ret = func(entry->d_name, private_data);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ closedir(dir);
+ return ret;
+}
+/**
+ * Wrapper for chdir(2).
+ *
+ * \param path The specified directory.
+ *
+ * \return Standard.
+ */
+int dss_chdir(const char *path)
+{
+ int ret = chdir(path);
+
+ if (ret >= 0)
+ return 1;
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ make_err_msg("chdir to %s failed", path);
+ return ret;
+}
+
+/**
+ * Set a file descriptor to non-blocking mode.
+ *
+ * \param fd The file descriptor.
+ *
+ * \return Standard.
+ */
+__must_check int mark_fd_nonblocking(int fd)
+{
+ int flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -ERRNO_TO_DSS_ERROR(errno);
+ flags = fcntl(fd, F_SETFL, ((long)flags) | O_NONBLOCK);
+ if (flags < 0)
+ return -ERRNO_TO_DSS_ERROR(errno);
+ return 1;
+}
+
--- /dev/null
+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);
+/**
+ * A wrapper for rename(2).
+ *
+ * \param old_path The source path.
+ * \param new_path The destination path.
+ *
+ * \return Standard.
+ *
+ * \sa rename(2).
+ */
+_static_inline_ int dss_rename(const char *old_path, const char *new_path)
+{
+ int ret;
+
+ if (rename(old_path, new_path) >= 0)
+ return 1;
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ make_err_msg("rename %s -> %s failed", old_path, new_path);
+ return ret;
+}
--- /dev/null
+# define inline inline __attribute__ ((always_inline))
+# define __noreturn __attribute__ ((noreturn))
+# define __malloc __attribute__ ((malloc))
+# define __a_unused __attribute__ ((unused))
+# define likely(x) __builtin_expect (!!(x), 1)
+# define unlikely(x) __builtin_expect (!!(x), 0)
+/*
+ * p is the number of the "format string" parameter, and q is
+ * the number of the first variadic parameter
+ */
+# define __printf(p,q) __attribute__ ((format (printf, p, q)))
+/*
+ * as direct use of __printf(p,q) confuses doxygen, here are two extra macros
+ * for those values p,q that are actually used by paraslash.
+ */
+#define __printf_1_2 __printf(1,2)
+#define __printf_2_3 __printf(2,3)
+
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3)
+# define __must_check __attribute__ ((warn_unused_result))
+# else
+# define __must_check /* no warn_unused_result */
+# endif
+
+#define _static_inline_ static inline
--- /dev/null
+
+/** debug loglevel, gets really noisy */
+#define DEBUG 1
+/** still noisy, but won't fill your disk */
+#define INFO 2
+/** normal, but significant event */
+#define NOTICE 3
+/** unexpected event that can be handled */
+#define WARNING 4
+/** unhandled error condition */
+#define ERROR 5
+/** system might be unreliable */
+#define CRIT 6
+/** last message before exit */
+#define EMERG 7
+
+/** Log messages with lower priority than that will not be compiled in. */
+#define COMPILE_TIME_LOGLEVEL 0
+
+/** \cond */
+#if DEBUG > COMPILE_TIME_LOGLEVEL
+#define DSS_DEBUG_LOG(f,...) dss_log(DEBUG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_DEBUG_LOG(...) do {;} while (0)
+#endif
+
+#if INFO > COMPILE_TIME_LOGLEVEL
+#define DSS_INFO_LOG(f,...) dss_log(INFO, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_INFO_LOG(...) do {;} while (0)
+#endif
+
+#if NOTICE > COMPILE_TIME_LOGLEVEL
+#define DSS_NOTICE_LOG(f,...) dss_log(NOTICE, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_NOTICE_LOG(...) do {;} while (0)
+#endif
+
+#if WARNING > COMPILE_TIME_LOGLEVEL
+#define DSS_WARNING_LOG(f,...) dss_log(WARNING, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_WARNING_LOG(...) do {;} while (0)
+#endif
+
+#if ERROR > COMPILE_TIME_LOGLEVEL
+#define DSS_ERROR_LOG(f,...) dss_log(ERROR, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_ERROR_LOG(...) do {;} while (0)
+#endif
+
+#if CRIT > COMPILE_TIME_LOGLEVEL
+#define DSS_CRIT_LOG(f,...) dss_log(CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_CRIT_LOG(...) do {;} while (0)
+#endif
+
+#if EMERG > COMPILE_TIME_LOGLEVEL
+#define DSS_EMERG_LOG(f,...) dss_log(EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#else
+#define DSS_EMERG_LOG(...)
+#endif
--- /dev/null
+/*
+ * Copyright (C) 2004-2008 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+/** \file signal.c Signal handling functions. */
+
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+
+
+#include "gcc-compat.h"
+#include "error.h"
+#include "log.h"
+#include "string.h"
+#include "fd.h"
+
+static int signal_pipe[2];
+
+/**
+ * Initialize the signal subsystem.
+ *
+ * This function creates a pipe, the signal pipe, to deliver pending signals to
+ * the application (Bernstein's trick). It should be called during the
+ * application's startup part, followed by subsequent calls to
+ * install_sighandler() for each signal that should be caught.
+ *
+ * signal_init() installs a generic signal handler which is used for all
+ * signals simultaneously. When a signal arrives, this generic signal handler
+ * writes the corresponding signal number to the signal pipe so that the
+ * application can test for pending signals simply by checking the signal pipe
+ * for reading, e.g. by using the select(2) system call.
+ *
+ * \return This function either succeeds or calls exit(2) to terminate
+ * the current process. On success, the file descriptor of the signal pipe is
+ * returned.
+ */
+int signal_init(void)
+{
+ int ret;
+ if (pipe(signal_pipe) < 0) {
+ ret = -ERRNO_TO_DSS_ERROR(errno);
+ goto err_out;
+ }
+ ret = mark_fd_nonblocking(signal_pipe[0]);
+ if (ret < 0)
+ goto err_out;
+ ret = mark_fd_nonblocking(signal_pipe[1]);
+ if (ret < 0)
+ goto err_out;
+ return signal_pipe[0];
+err_out:
+ DSS_EMERG_LOG("%s\n", dss_strerror(-ret));
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * just write one integer to signal pipe
+ */
+static void generic_signal_handler(int s)
+{
+ write(signal_pipe[1], &s, sizeof(int));
+ //fprintf(stderr, "got sig %i, write returned %d\n", s, ret);
+}
+
+/**
+ * Reap one child.
+ *
+ * \param pid In case a child died, its pid is returned here.
+ *
+ * Call waitpid() and print a log message containing the pid and the cause of
+ * the child's death.
+ *
+ * \return A (negative) error code on errors, zero, if no child died, one
+ * otherwise. If and only if the function returns one, the content of \a pid is
+ * meaningful.
+ *
+ * \sa waitpid(2)
+ */
+int reap_child(pid_t *pid)
+{
+ int status;
+ *pid = waitpid(-1, &status, WNOHANG);
+
+ if (!*pid)
+ return 0;
+ if (*pid < 0)
+ return -ERRNO_TO_DSS_ERROR(errno);
+ if (WIFEXITED(status))
+ DSS_DEBUG_LOG("child %i exited. Exit status: %i\n", (int)*pid,
+ WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ DSS_DEBUG_LOG("child %i was killed by signal %i\n", (int)*pid,
+ WTERMSIG(status));
+ else
+ DSS_WARNING_LOG("child %i terminated abormally\n", (int)*pid);
+ return 1;
+}
+
+/**
+ * wrapper around signal(2)
+ * \param sig the number of the signal to catch
+ *
+ * This installs the generic signal handler for the given signal.
+ * \return This function returns 1 on success and \p -E_SIGNAL_SIG_ERR on errors.
+ * \sa signal(2)
+ */
+int install_sighandler(int sig)
+{
+ DSS_DEBUG_LOG("catching signal %d\n", sig);
+ return signal(sig, &generic_signal_handler) == SIG_ERR? -E_SIGNAL_SIG_ERR : 1;
+}
+
+/**
+ * return number of next pending signal
+ *
+ * This should be called if the fd for the signal pipe is ready for reading.
+ *
+ * \return On success, the number of the received signal is returned. \p
+ * -E_SIGNAL_READ is returned if a read error occurred while reading the signal
+ * pipe. If the read was interrupted by another signal the function returns 0.
+ */
+int next_signal(void)
+{
+ int s;
+ ssize_t r;
+
+ r = read(signal_pipe[0], &s, sizeof(s));
+ if (r == sizeof(s)) {
+ DSS_DEBUG_LOG("next signal: %d\n", s);
+ return s;
+ }
+ return r < 0 && (errno != EAGAIN)? 0 : -ERRNO_TO_DSS_ERROR(errno);
+}
+
+/**
+ * Close the signal pipe.
+ */
+void signal_shutdown(void)
+{
+ close(signal_pipe[1]);
+}
--- /dev/null
+/*
+ * Copyright (C) 2007-2008 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file signal.h exported symbols from signal.c */
+
+int signal_init(void);
+int install_sighandler(int);
+int reap_child(pid_t *pid);
+int next_signal(void);
+void signal_shutdown(void);
--- /dev/null
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <limits.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+#include "cmdline.h"
+#include "gcc-compat.h"
+#include "log.h"
+#include "error.h"
+
+__noreturn void clean_exit(int status);
+
+/**
+ * Write a message to a dynamically allocated string.
+ *
+ * \param fmt Usual format string.
+ * \param p Result pointer.
+ *
+ * \sa printf(3). */
+#define VSPRINTF(fmt, p) \
+{ \
+ int n; \
+ size_t size = 100; \
+ p = dss_malloc(size); \
+ while (1) { \
+ va_list ap; \
+ /* Try to print in the allocated space. */ \
+ va_start(ap, fmt); \
+ n = vsnprintf(p, size, fmt, ap); \
+ va_end(ap); \
+ /* If that worked, return the string. */ \
+ if (n > -1 && n < size) \
+ break; \
+ /* Else try again with more space. */ \
+ if (n > -1) /* glibc 2.1 */ \
+ size = n + 1; /* precisely what is needed */ \
+ else /* glibc 2.0 */ \
+ size *= 2; /* twice the old size */ \
+ p = dss_realloc(p, size); \
+ } \
+}
+
+/**
+ * dss' version of realloc().
+ *
+ * \param p Pointer to the memory block, may be \p NULL.
+ * \param size The desired new size.
+ *
+ * A wrapper for realloc(3). It calls \p exit(\p EXIT_FAILURE) on errors,
+ * i.e. there is no need to check the return value in the caller.
+ *
+ * \return A pointer to the newly allocated memory, which is suitably aligned
+ * for any kind of variable and may be different from \a p.
+ *
+ * \sa realloc(3).
+ */
+__must_check __malloc void *dss_realloc(void *p, size_t size)
+{
+ /*
+ * No need to check for NULL pointers: If p is NULL, the call
+ * to realloc is equivalent to malloc(size)
+ */
+ assert(size);
+ if (!(p = realloc(p, size))) {
+ DSS_EMERG_LOG("realloc failed (size = %zu), aborting\n",
+ size);
+ clean_exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+/**
+ * dss' version of malloc().
+ *
+ * \param size The desired new size.
+ *
+ * A wrapper for malloc(3) which exits on errors.
+ *
+ * \return A pointer to the allocated memory, which is suitably aligned for any
+ * kind of variable.
+ *
+ * \sa malloc(3).
+ */
+__must_check __malloc void *dss_malloc(size_t size)
+{
+ assert(size);
+ void *p = malloc(size);
+
+ if (!p) {
+ DSS_EMERG_LOG("malloc failed (size = %zu), aborting\n",
+ size);
+ clean_exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+/**
+ * dss' version of calloc().
+ *
+ * \param size The desired new size.
+ *
+ * A wrapper for calloc(3) which exits on errors.
+ *
+ * \return A pointer to the allocated and zeroed-out memory, which is suitably
+ * aligned for any kind of variable.
+ *
+ * \sa calloc(3)
+ */
+__must_check __malloc void *dss_calloc(size_t size)
+{
+ void *ret = dss_malloc(size);
+
+ memset(ret, 0, size);
+ return ret;
+}
+
+/**
+ * dss' version of strdup().
+ *
+ * \param s The string to be duplicated.
+ *
+ * A wrapper for strdup(3). It calls \p exit(EXIT_FAILURE) on errors, i.e.
+ * there is no need to check the return value in the caller.
+ *
+ * \return A pointer to the duplicated string. If \p s was the NULL pointer,
+ * an pointer to an empty string is returned.
+ *
+ * \sa strdup(3)
+ */
+
+__must_check __malloc char *dss_strdup(const char *s)
+{
+ char *ret;
+
+ if ((ret = strdup(s? s: "")))
+ return ret;
+ DSS_EMERG_LOG("strdup failed, aborting\n");
+ clean_exit(EXIT_FAILURE);
+}
+
+/**
+ * Allocate a sufficiently large string and print into it.
+ *
+ * \param fmt A usual format string.
+ *
+ * Produce output according to \p fmt. No artificial bound on the length of the
+ * resulting string is imposed.
+ *
+ * \return This function either returns a pointer to a string that must be
+ * freed by the caller or aborts without returning.
+ *
+ * \sa printf(3).
+ */
+__must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
+{
+ char *msg;
+
+ VSPRINTF(fmt, msg);
+ return msg;
+}
+
+__printf_1_2 void make_err_msg(const char* fmt,...)
+{
+ free(dss_error_txt);
+ VSPRINTF(fmt, dss_error_txt);
+}
+
+/**
+ * Get the home directory of the current user.
+ *
+ * \return A dynammically allocated string that must be freed by the caller. If
+ * the home directory could not be found, this function returns "/tmp".
+ */
+__must_check __malloc char *get_homedir(void)
+{
+ struct passwd *pw = getpwuid(getuid());
+ return dss_strdup(pw? pw->pw_dir : "/tmp");
+}
+
+/** \cond LLONG_MAX and LLONG_LIN might not be defined. */
+#ifndef LLONG_MAX
+#define LLONG_MAX (1 << (sizeof(long) - 1))
+#endif
+#ifndef LLONG_MIN
+#define LLONG_MIN (-LLONG_MAX - 1LL)
+#endif
+/** \endcond */
+
+/**
+ * Convert a string to a 64-bit signed integer value.
+ *
+ * \param str The string to be converted.
+ * \param value Result pointer.
+ *
+ * \return Standard.
+ *
+ * \sa strtol(3), atoi(3).
+ */
+int dss_atoi64(const char *str, int64_t *value)
+{
+ char *endptr;
+ long long tmp;
+
+ errno = 0; /* To distinguish success/failure after call */
+ tmp = strtoll(str, &endptr, 10);
+ if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN)) {
+ make_err_msg("%s", str);
+ return -E_ATOI_OVERFLOW;
+ }
+ if (errno != 0 && tmp == 0) { /* other error */
+ make_err_msg("%s", str);
+ return -E_STRTOLL;
+ }
+ if (endptr == str) {
+ make_err_msg("%s", str);
+ return -E_ATOI_NO_DIGITS;
+ }
+ if (*endptr != '\0') { /* Further characters after number */
+ make_err_msg("%s", str);
+ return -E_ATOI_JUNK_AT_END;
+ }
+ *value = tmp;
+ return 1;
+}
+
+/**
+ * Get the logname of the current user.
+ *
+ * \return A dynammically allocated string that must be freed by the caller. On
+ * errors, the string "unknown user" is returned, i.e. this function never
+ * returns \p NULL.
+ *
+ * \sa getpwuid(3).
+ */
+__must_check __malloc char *dss_logname(void)
+{
+ struct passwd *pw = getpwuid(getuid());
+ return dss_strdup(pw? pw->pw_name : "unknown_user");
+}
+
--- /dev/null
+__must_check __malloc void *dss_realloc(void *p, size_t size);
+__must_check __malloc void *dss_malloc(size_t size);
+__must_check __malloc void *dss_calloc(size_t size);
+__must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...);
+__must_check __malloc char *dss_strdup(const char *s);
+__printf_1_2 void make_err_msg(const char* fmt,...);
+__must_check __malloc char *get_homedir(void);
+int dss_atoi64(const char *str, int64_t *value);
+__must_check __malloc char *dss_logname(void);