Initial git checkin.
authorAndre Noll <maan@congo.fml.local>
Tue, 11 Mar 2008 20:00:54 +0000 (21:00 +0100)
committerAndre Noll <maan@congo.fml.local>
Tue, 11 Mar 2008 20:00:54 +0000 (21:00 +0100)
Dyadic intervals are so harmonic. Do you feel it?

17 files changed:
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
daemon.c [new file with mode: 0644]
dss.c [new file with mode: 0644]
dss.ggo [new file with mode: 0644]
dss.sk [new file with mode: 0644]
error.h [new file with mode: 0644]
exec.c [new file with mode: 0644]
exec.h [new file with mode: 0644]
fd.c [new file with mode: 0644]
fd.h [new file with mode: 0644]
gcc-compat.h [new file with mode: 0644]
log.h [new file with mode: 0644]
signal.c [new file with mode: 0644]
signal.h [new file with mode: 0644]
string.c [new file with mode: 0644]
string.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c9e16ac
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+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
+
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..63b3b9c
--- /dev/null
+++ b/README
@@ -0,0 +1,19 @@
+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.
diff --git a/daemon.c b/daemon.c
new file mode 100644 (file)
index 0000000..ba890fb
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,115 @@
+/*
+ * 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;
+}
diff --git a/dss.c b/dss.c
new file mode 100644 (file)
index 0000000..0913301
--- /dev/null
+++ b/dss.c
@@ -0,0 +1,720 @@
+#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, &params);
+       }
+       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);
+}
diff --git a/dss.ggo b/dss.ggo
new file mode 100644 (file)
index 0000000..a126dec
--- /dev/null
+++ b/dss.ggo
@@ -0,0 +1,222 @@
+
+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!
+"
diff --git a/dss.sk b/dss.sk
new file mode 100644 (file)
index 0000000..97d2531
--- /dev/null
+++ b/dss.sk
@@ -0,0 +1,94 @@
+##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')
diff --git a/error.h b/error.h
new file mode 100644 (file)
index 0000000..b9cbfec
--- /dev/null
+++ b/error.h
@@ -0,0 +1,82 @@
+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}
diff --git a/exec.c b/exec.c
new file mode 100644 (file)
index 0000000..13a0e4b
--- /dev/null
+++ b/exec.c
@@ -0,0 +1,111 @@
+/** \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;
+}
diff --git a/exec.h b/exec.h
new file mode 100644 (file)
index 0000000..d428adc
--- /dev/null
+++ b/exec.h
@@ -0,0 +1,2 @@
+int dss_exec(pid_t *pid, const char *file, char *const *const args, int *fds);
+
diff --git a/fd.c b/fd.c
new file mode 100644 (file)
index 0000000..db0433f
--- /dev/null
+++ b/fd.c
@@ -0,0 +1,104 @@
+#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;
+}
+
diff --git a/fd.h b/fd.h
new file mode 100644 (file)
index 0000000..fa14ad2
--- /dev/null
+++ b/fd.h
@@ -0,0 +1,23 @@
+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;
+}
diff --git a/gcc-compat.h b/gcc-compat.h
new file mode 100644 (file)
index 0000000..61c3c88
--- /dev/null
@@ -0,0 +1,25 @@
+# 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
diff --git a/log.h b/log.h
new file mode 100644 (file)
index 0000000..e140515
--- /dev/null
+++ b/log.h
@@ -0,0 +1,61 @@
+
+/** 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
diff --git a/signal.c b/signal.c
new file mode 100644 (file)
index 0000000..160730f
--- /dev/null
+++ b/signal.c
@@ -0,0 +1,151 @@
+/*
+ * 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]);
+}
diff --git a/signal.h b/signal.h
new file mode 100644 (file)
index 0000000..93fa8a8
--- /dev/null
+++ b/signal.h
@@ -0,0 +1,13 @@
+/*
+ * 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);
diff --git a/string.c b/string.c
new file mode 100644 (file)
index 0000000..cf6d0b1
--- /dev/null
+++ b/string.c
@@ -0,0 +1,246 @@
+#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");
+}
+
diff --git a/string.h b/string.h
new file mode 100644 (file)
index 0000000..41afe8f
--- /dev/null
+++ b/string.h
@@ -0,0 +1,9 @@
+__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);