From: Andre Noll Date: Mon, 13 Nov 2017 16:23:14 +0000 (+0100) Subject: Merge branch 'maint' X-Git-Tag: v1.0.0~19 X-Git-Url: http://git.tuebingen.mpg.de/?p=dss.git;a=commitdiff_plain;h=e64460eb7c840399e46fc54da3b348fee33e550d;hp=404aae666e5f8d7ce511639cbe4727a7f2e690cc Merge branch 'maint' * maint: Fix snapshot removal reason. --- diff --git a/.gitignore b/.gitignore index 9a6163d..1bb2a25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ Makefile.deps -*.[oa] -cmdline.[ch] +*.o +*.swp +dss.lsg.* dss dss.1 dss.1.html +dss.png +index.html diff --git a/INSTALL b/INSTALL index db3d1c5..09d8505 100644 --- a/INSTALL +++ b/INSTALL @@ -1,27 +1,30 @@ +dss is known to compile on Linux, FreeBSD and NetBSD. However, it is +run-tested only on Linux. + +Note that [lopsub](http://people.tuebingen.mpg.de/maan/lopsub) +is required to compile dss. + Type make -in the dss source directory to build the dss executable and copy it -to some directory that is included in your PATH, e.g. to `$HOME/bin` -or to `/usr/local/bin`. +in the dss source directory to build the dss executable and the man +page. If lopsub is installed in a non-standard path, you may need to +run `make` as follows: -Note that [gnu -gengetopt](https://www.gnu.org/software/gengetopt/gengetopt.html) -is required to compile dss. + make CPPFLAGS=-I$HOME/lopsub/include LDFLAGS=-L$HOME/lopsub/lib + +Then type -Optionally, type + sudo make install - make man +to install in /usr/local, or -to create the man page of dss. This invokes help2man so make sure -that help2man is installed on your system. Note that the man page is -just the nroff variant of the output of `dss --detailed-help`. + make install PREFIX=/somewhere/else -dss is known to compile on Linux, MacOS, Solaris, FreeBSD and -NetBSD. However, it is run-tested only on Linux. +to install in /somewhere/else. -Also make sure that [rsync](http://rsync.samba.org/) is installed on +Also make sure that [rsync](https://rsync.samba.org/) is installed on your system. Version 2.6.1 or newer is required. Examples: @@ -48,11 +51,11 @@ as follows: Then execute the commands mkdir /baz/qux - dss --run + dss run In order to print the list of all snapshots created so far, use - dss --ls + dss ls Yes, it's really that easy. @@ -67,7 +70,7 @@ system boundaries and increases the number of snapshots. rsync-option "--exclude-from=/etc/dss.exclude" # don't cross filesystem boundaries rsync-option "--one-file-system" - # maintain 2^6 - 1 = 63 snaphots + # maintain 2^6 - 1 = 63 snapshots num-intervals "6" The /etc/dss.exclude file could look like this (see rsync(1) for diff --git a/Makefile b/Makefile index 8adb38a..f5c34af 100644 --- a/Makefile +++ b/Makefile @@ -1,51 +1,67 @@ -dss_objects := cmdline.o dss.o str.o file.o exec.o sig.o daemon.o df.o tv.o snap.o ipc.o -all: dss +PREFIX ?= /usr/local +INSTALL ?= install +INSTALL_PROGRAM ?= $(INSTALL) +INSTALL_DATA ?= $(INSTALL) -m 644 +MKDIR_P := mkdir -p + +VERSION_STRING = 0.1.7 + +dss_objects := dss.o str.o file.o exec.o sig.o daemon.o df.o tv.o snap.o ipc.o dss.lsg.o +all: dss dss.1 man: dss.1 -DEBUG_CFLAGS ?= -DEBUG_CFLAGS += -Wno-sign-compare -g -Wunused -Wundef -DEBUG_CFLAGS += -Wredundant-decls -CFLAGS ?= -CFLAGS += -Os -CFLAGS += -Wall -CFLAGS += -Wuninitialized -CFLAGS += -Wchar-subscripts -CFLAGS += -Wformat-security -CFLAGS += -Werror-implicit-function-declaration -CFLAGS += -Wmissing-format-attribute -CFLAGS += -Wunused-macros -CFLAGS += -Wunused-parameter -CFLAGS += -Wbad-function-cast -CFLAGS += -Wshadow +DSS_CPPFLAGS := -DVERSION_STRING='"$(VERSION_STRING)"' +DSS_CPPFLAGS += -Wunused-macros + +DSS_CFLAGS := -Wno-sign-compare -g -Wunused -Wundef +DSS_CFLAGS += -Wredundant-decls +DSS_CFLAGS += -Os +DSS_CFLAGS += -Wall +DSS_CFLAGS += -Wuninitialized +DSS_CFLAGS += -Wchar-subscripts +DSS_CFLAGS += -Wformat-security +DSS_CFLAGS += -Werror-implicit-function-declaration +DSS_CFLAGS += -Wmissing-format-attribute +DSS_CFLAGS += -Wunused-parameter +DSS_CFLAGS += -Wbad-function-cast +DSS_CFLAGS += -Wshadow Makefile.deps: $(wildcard *.c *.h) - gcc -MM -MG *.c > $@ + $(CC) -MM -MG $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) *.c > $@ -include Makefile.deps dss: $(dss_objects) - $(CC) -o $@ $(dss_objects) - -cmdline.o: cmdline.c cmdline.h - $(CC) -c $(CFLAGS) $< + $(CC) -o $@ $(dss_objects) $(LDFLAGS) -llopsub %.o: %.c Makefile - $(CC) -c $(CFLAGS) $(DEBUG_CFLAGS) $< - -%.png: %.dia - dia -e $@ -t png $< + $(CC) -c $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) $< -cmdline.c cmdline.h: dss.ggo - gengetopt --conf-parser < $< +dss.png: mklogo + ./mklogo > $@ -dss.1: dss dss.1.inc - help2man -h --detailed-help --include dss.1.inc -N ./$< > $@ +%.lsg.h: %.suite + lopsubgen --gen-h=$@ < $< +%.lsg.c: %.suite + lopsubgen --gen-c=$@ < $< +%.1: %.suite + lopsubgen --gen-man=$@ --version-string=$(VERSION_STRING) < $< %.1.html: %.1 groff -m man -Thtml -P -l -P -r $< | sed -e '1,/^/d; /^<\/body>/,$$d' > $@ clean: - rm -f *.o dss dss.1 dss.1.html Makefile.deps *.png *~ cmdline.c cmdline.h index.html + rm -f *.o dss dss.1 dss.1.html Makefile.deps *.png *~ index.html dss.lsg.h dss.lsg.c + +ifneq ($(findstring strip, $(MAKECMDGOALS)),) + strip_option := -s +endif +bindir := $(DESTDIR)$(PREFIX)/bin +mandir := $(DESTDIR)$(PREFIX)/share/man/man1 +install install-strip: all + $(MKDIR_P) $(bindir) $(mandir) + $(INSTALL_PROGRAM) $(strip_option) dss $(bindir) + $(INSTALL_DATA) dss.1 $(mandir) index.html: dss.1.html index.html.in INSTALL README NEWS sed -e '/@README@/,$$d' index.html.in > $@ diff --git a/NEWS b/NEWS index 1532320..de1dca6 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,28 @@ +----------------------- +x.y.z (to be announced) +----------------------- + + - Improved error diagnostics for the kill subcommand. + + - The --no-resume option has been removed. + + - The gengetopt option parser has been replaced by the + [lopsub](http://people.tuebingen.mpg.de/maan/lopsub) library. Hence + lopsub must be installed to compile this package. Also help2man is + no longer required since lopsub has built-in roff support. + + - "make install" will install the executable and the man page. + + - In run mode, dss no longer exits successfully if another instance + is already running. + + - New option --checksum to let rsync compute checksums occasionally. + + - CFLAGS, CPPFLAGS and LDFLAGS can now be used to override the flags + of the build system. + + - The dss logo is now created with ImageMagick rather than dia. + ------------------ 0.1.7 (2017-04-17) ------------------ diff --git a/README b/README index c5abb29..79b8889 100644 --- a/README +++ b/README @@ -11,7 +11,7 @@ configured, and there is no database to maintain. dss is also user-friendly because users can browse the snapshot directories without admin intervention and see the contents of the file -system at the various times a snapshot was taken. Each snaphot looks +system at the various times a snapshot was taken. Each snapshot looks like a full backup, so users can easily restore accidentally removed files by using their favorite file browser to simply copy files from the snapshot directory back to the live system. diff --git a/daemon.c b/daemon.c index 86e8906..0b201a8 100644 --- a/daemon.c +++ b/daemon.c @@ -31,17 +31,36 @@ * * \sa fork(2), setsid(2), dup(2). */ -void daemon_init(void) +int daemon_init(void) { pid_t pid; - int null; + int null, fd[2]; DSS_INFO_LOG(("daemonizing\n")); + if (pipe(fd) < 0) + goto err; pid = fork(); if (pid < 0) goto err; - if (pid) - exit(EXIT_SUCCESS); /* parent exits */ + if (pid) { + /* + * The parent process exits once it has received one byte from + * the reading end of the pipe. If the child exits before it + * was able to complete its setup (acquire the lock on the + * semaphore), the read() below will return zero. In this case + * we let the parent die unsuccessfully. + */ + char c; + int ret; + close(fd[1]); + ret = read(fd[0], &c, 1); + if (ret <= 0) { + DSS_EMERG_LOG(("child terminated unexpectedly\n")); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + } + close(fd[0]); /* become session leader */ if (setsid() < 0) goto err; @@ -56,7 +75,7 @@ void daemon_init(void) if (dup2(null, STDERR_FILENO) < 0) goto err; close(null); - return; + return fd[1]; err: DSS_EMERG_LOG(("fatal: %s\n", strerror(errno))); exit(EXIT_FAILURE); diff --git a/daemon.h b/daemon.h index aead8e8..e36e37c 100644 --- a/daemon.h +++ b/daemon.h @@ -1,7 +1,7 @@ /** \file daemon.h exported symbols from daemon.c */ -void daemon_init(void); +int daemon_init(void); FILE *open_log(const char *logfile_name); void close_log(FILE* logfile); void log_welcome(int loglevel); diff --git a/dss.1.inc b/dss.1.inc deleted file mode 100644 index 74bd4fb..0000000 --- a/dss.1.inc +++ /dev/null @@ -1,5 +0,0 @@ -[NAME] -dss \- dyadic snapshot scheduler -[SEE ALSO] -.BR ssh (1) , -.BR rsync (1) diff --git a/dss.c b/dss.c index a895019..ecd6ac3 100644 --- a/dss.c +++ b/dss.c @@ -21,10 +21,11 @@ #include #include #include - +#include +#include +#include #include "gcc-compat.h" -#include "cmdline.h" #include "log.h" #include "str.h" #include "err.h" @@ -36,9 +37,34 @@ #include "tv.h" #include "snap.h" #include "ipc.h" +#include "dss.lsg.h" + +#define CMD_PTR(_cname) lls_cmd(LSG_DSS_CMD_ ## _cname, dss_suite) +#define OPT_RESULT(_cname, _oname) (lls_opt_result(\ + LSG_DSS_ ## _cname ## _OPT_ ## _oname, (CMD_PTR(_cname) == CMD_PTR(DSS))? lpr : sublpr)) +#define OPT_GIVEN(_cname, _oname) (lls_opt_given(OPT_RESULT(_cname, _oname))) +#define OPT_STRING_VAL(_cname, _oname) (lls_string_val(0, \ + OPT_RESULT(_cname, _oname))) +#define OPT_UINT32_VAL(_cname, _oname) (lls_uint32_val(0, \ + OPT_RESULT(_cname, _oname))) + +struct dss_user_data {int (*handler)(void);}; +#define EXPORT_CMD_HANDLER(_cmd) const struct dss_user_data \ + lsg_dss_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; + +/* + * Command line and active options. We need to keep a copy of the parsed + * command line options for the SIGHUP case where we merge the command line + * options and the new config file options. + */ +static struct lls_parse_result *cmdline_lpr, *lpr; -/** Command line and config file options. */ -static struct gengetopt_args_info conf; +/** Parsed subcommand options. */ +static struct lls_parse_result *cmdline_sublpr, *sublpr; +/** Wether daemon_init() was called. */ +static bool daemonized; /** Non-NULL if we log to a file. */ static FILE *logfile; /** The read end of the signal pipe */ @@ -75,6 +101,7 @@ static const char *hook_status_description[] = {HOOK_STATUS_ARRAY}; static int disk_space_low(struct disk_space *ds) { struct disk_space ds_struct; + uint32_t val; if (!ds) { int ret = get_disk_space(".", &ds_struct); @@ -82,14 +109,17 @@ static int disk_space_low(struct disk_space *ds) return ret; ds = &ds_struct; } - if (conf.min_free_mb_arg) - if (ds->free_mb < conf.min_free_mb_arg) + val = OPT_UINT32_VAL(DSS, MIN_FREE_MB); + if (val != 0) + if (ds->free_mb < val) return 1; - if (conf.min_free_percent_arg) - if (ds->percent_free < conf.min_free_percent_arg) + val = OPT_UINT32_VAL(DSS, MIN_FREE_PERCENT); + if (val != 0) + if (ds->percent_free < val) return 1; - if (conf.min_free_percent_inodes_arg) - if (ds->percent_free_inodes < conf.min_free_percent_inodes_arg) + val = OPT_UINT32_VAL(DSS, MIN_FREE_PERCENT_INODES); + if (val != 0) + if (ds->percent_free_inodes < val) return 1; return 0; } @@ -97,12 +127,13 @@ static int disk_space_low(struct disk_space *ds) static void dump_dss_config(const char *msg) { const char dash[] = "-----------------------------"; + char *lopsub_dump; int ret; FILE *log = logfile? logfile : stderr; struct disk_space ds; int64_t now = get_current_time(); - if (conf.loglevel_arg > INFO) + if (OPT_UINT32_VAL(DSS, LOGLEVEL) > INFO) return; fprintf(log, "%s <%s config> %s\n", dash, msg, dash); @@ -118,8 +149,14 @@ static void dump_dss_config(const char *msg) /* we continue on errors from get_disk_space */ - fprintf(log, "\n*** command line and config file options ***\n\n"); - cmdline_parser_dump(log, &conf); + fprintf(log, "\n*** non-default options ***\n\n"); + lopsub_dump = lls_dump_parse_result(lpr, CMD_PTR(DSS), true); + fprintf(log, "%s", lopsub_dump); + free(lopsub_dump); + fprintf(log, "\n*** non-default options for \"run\" ***\n\n"); + lopsub_dump = lls_dump_parse_result(lpr, CMD_PTR(RUN), true); + fprintf(log, "%s", lopsub_dump); + free(lopsub_dump); fprintf(log, "\n*** internal state ***\n\n"); fprintf(log, "pid: %d\n" @@ -132,7 +169,7 @@ static void dump_dss_config(const char *msg) "num_consecutive_rsync_errors: %d\n" , (int) getpid(), - logfile? conf.logfile_arg : "stderr", + logfile? OPT_STRING_VAL(RUN, LOGFILE) : "stderr", snapshot_currently_being_removed? snapshot_currently_being_removed->name : "(none)", path_to_last_complete_snapshot? @@ -172,28 +209,6 @@ static void dump_dss_config(const char *msg) fprintf(log, "%s %s\n", dash, msg, dash); } -/* a litte cpp magic helps to DRY */ -#define COMMANDS \ - COMMAND(ls) \ - COMMAND(create) \ - COMMAND(prune) \ - COMMAND(run) \ - COMMAND(kill) \ - COMMAND(reload) \ - -#define COMMAND(x) static int com_ ##x(void); -COMMANDS -#undef COMMAND -#define COMMAND(x) if (conf.x ##_given) return com_ ##x(); -static int call_command_handler(void) -{ - COMMANDS - DSS_EMERG_LOG(("BUG: did not find command handler\n")); - return -E_BUG; -} -#undef COMMAND -#undef COMMANDS - static int loglevel = -1; static const char *location_file = NULL; static int location_line = -1; @@ -222,15 +237,16 @@ __printf_1_2 void dss_log(const char* fmt,...) struct tm *tm; time_t t1; char str[255] = ""; + int lpr_ll = lpr? OPT_UINT32_VAL(DSS, LOGLEVEL) : WARNING; - if (loglevel < conf.loglevel_arg) + if (loglevel < lpr_ll) return; outfd = logfile? logfile : stderr; time(&t1); tm = localtime(&t1); strftime(str, sizeof(str), "%b %d %H:%M:%S", tm); fprintf(outfd, "%s ", str); - if (conf.loglevel_arg <= INFO) + if (lpr_ll <= INFO) fprintf(outfd, "%i: ", loglevel); #ifdef DSS_NO_FUNC_NAMES fprintf(outfd, "%s:%d: ", location_file, location_line); @@ -247,7 +263,7 @@ __printf_1_2 void dss_log(const char* fmt,...) */ static __printf_1_2 void dss_msg(const char* fmt,...) { - FILE *outfd = conf.daemon_given? logfile : stdout; + FILE *outfd = logfile? logfile : stdout; va_list argp; va_start(argp, fmt); vfprintf(outfd, fmt, argp); @@ -258,8 +274,8 @@ static char *get_config_file_name(void) { char *home, *config_file; - if (conf.config_file_given) - return dss_strdup(conf.config_file_arg); + if (OPT_GIVEN(DSS, CONFIG_FILE)) + return dss_strdup(OPT_STRING_VAL(DSS, CONFIG_FILE)); home = get_homedir(); config_file = make_message("%s/.dssrc", home); free(home); @@ -275,7 +291,7 @@ static int send_signal(int sig) free(config_file); if (ret < 0) return ret; - if (conf.dry_run_given) { + if (OPT_GIVEN(DSS, DRY_RUN)) { dss_msg("%d\n", (int)pid); return 0; } @@ -285,26 +301,100 @@ static int send_signal(int sig) return 1; } +struct signal_info { + const char * const name; + int num; +}; + +/* + * The table below was taken 2016 from proc/sig.c of procps-3.2.8. Copyright + * 1998-2003 by Albert Cahalan, GPLv2. + */ +static const struct signal_info signal_table[] = { + {"ABRT", SIGABRT}, /* IOT */ + {"ALRM", SIGALRM}, + {"BUS", SIGBUS}, + {"CHLD", SIGCHLD}, /* CLD */ + {"CONT", SIGCONT}, + {"FPE", SIGFPE}, + {"HUP", SIGHUP}, + {"ILL", SIGILL}, + {"INT", SIGINT}, + {"KILL", SIGKILL}, + {"PIPE", SIGPIPE}, +#ifdef SIGPOLL + {"POLL", SIGPOLL}, /* IO */ +#endif + {"PROF", SIGPROF}, +#ifdef SIGPWR + {"PWR", SIGPWR}, +#endif + {"QUIT", SIGQUIT}, + {"SEGV", SIGSEGV}, +#ifdef SIGSTKFLT + {"STKFLT", SIGSTKFLT}, +#endif + {"STOP", SIGSTOP}, + {"SYS", SIGSYS}, /* UNUSED */ + {"TERM", SIGTERM}, + {"TRAP", SIGTRAP}, + {"TSTP", SIGTSTP}, + {"TTIN", SIGTTIN}, + {"TTOU", SIGTTOU}, + {"URG", SIGURG}, + {"USR1", SIGUSR1}, + {"USR2", SIGUSR2}, + {"VTALRM", SIGVTALRM}, + {"WINCH", SIGWINCH}, + {"XCPU", SIGXCPU}, + {"XFSZ", SIGXFSZ} +}; + +#define SIGNAL_TABLE_SIZE (sizeof(signal_table) / sizeof(signal_table[0])) +#ifndef SIGRTMAX +#define SIGRTMAX 64 +#endif + static int com_kill(void) { - return send_signal(SIGTERM); -} + const char *arg = OPT_STRING_VAL(KILL, SIGNAL); + int ret, i; -static int com_reload(void) -{ - return send_signal(SIGHUP); + if (*arg >= '0' && *arg <= '9') { + int64_t val; + ret = dss_atoi64(arg, &val); + if (ret < 0) + return ret; + if (val < 0 || val > SIGRTMAX) + return -ERRNO_TO_DSS_ERROR(EINVAL); + return send_signal(val); + } + if (strncasecmp(arg, "sig", 3) == 0) + arg += 3; + if (strcasecmp(arg, "CLD") == 0) + return send_signal(SIGCHLD); + if (strcasecmp(arg, "IOT") == 0) + return send_signal(SIGABRT); + for (i = 0; i < SIGNAL_TABLE_SIZE; i++) + if (strcasecmp(arg, signal_table[i].name) == 0) + return send_signal(signal_table[i].num); + DSS_ERROR_LOG(("invalid sigspec: %s\n", arg)); + return -ERRNO_TO_DSS_ERROR(EINVAL); } +EXPORT_CMD_HANDLER(kill); static void dss_get_snapshot_list(struct snapshot_list *sl) { - get_snapshot_list(sl, conf.unit_interval_arg, conf.num_intervals_arg); + get_snapshot_list(sl, OPT_UINT32_VAL(DSS, UNIT_INTERVAL), + OPT_UINT32_VAL(DSS, NUM_INTERVALS)); } static int64_t compute_next_snapshot_time(void) { int64_t x = 0, now = get_current_time(), unit_interval - = 24 * 3600 * conf.unit_interval_arg, ret; - unsigned wanted = desired_number_of_snapshots(0, conf.num_intervals_arg), + = 24 * 3600 * OPT_UINT32_VAL(DSS, UNIT_INTERVAL), ret; + unsigned wanted = desired_number_of_snapshots(0, + OPT_UINT32_VAL(DSS, NUM_INTERVALS)), num_complete = 0; int i; struct snapshot *s = NULL; @@ -361,8 +451,8 @@ static void pre_create_hook(void) assert(snapshot_creation_status == HS_READY); /* make sure that the next snapshot time will be recomputed */ invalidate_next_snapshot_time(); - DSS_DEBUG_LOG(("executing %s\n", conf.pre_create_hook_arg)); - dss_exec_cmdline_pid(&create_pid, conf.pre_create_hook_arg); + DSS_DEBUG_LOG(("executing %s\n", OPT_STRING_VAL(DSS, PRE_CREATE_HOOK))); + dss_exec_cmdline_pid(&create_pid, OPT_STRING_VAL(DSS, PRE_CREATE_HOOK)); snapshot_creation_status = HS_PRE_RUNNING; } @@ -381,8 +471,8 @@ static void pre_remove_hook(struct snapshot *s, const char *why) *snapshot_currently_being_removed = *s; snapshot_currently_being_removed->name = dss_strdup(s->name); - cmd = make_message("%s %s/%s", conf.pre_remove_hook_arg, - conf.dest_dir_arg, s->name); + cmd = make_message("%s %s/%s", OPT_STRING_VAL(DSS, PRE_REMOVE_HOOK), + OPT_STRING_VAL(DSS, DEST_DIR), s->name); DSS_DEBUG_LOG(("executing %s\n", cmd)); dss_exec_cmdline_pid(&remove_pid, cmd); free(cmd); @@ -469,10 +559,11 @@ static struct snapshot *find_redundant_snapshot(struct snapshot_list *sl) int i, interval; struct snapshot *s; unsigned missing = 0; + uint32_t N = OPT_UINT32_VAL(DSS, NUM_INTERVALS); DSS_DEBUG_LOG(("looking for intervals containing too many snapshots\n")); - for (interval = conf.num_intervals_arg - 1; interval >= 0; interval--) { - unsigned keep = desired_number_of_snapshots(interval, conf.num_intervals_arg); + for (interval = N - 1; interval >= 0; interval--) { + unsigned keep = desired_number_of_snapshots(interval, N); unsigned num = sl->interval_count[interval]; struct snapshot *victim = NULL, *prev = NULL; int64_t score = LONG_MAX; @@ -522,13 +613,13 @@ static struct snapshot *find_outdated_snapshot(struct snapshot_list *sl) struct snapshot *s; DSS_DEBUG_LOG(("looking for snapshots belonging to intervals >= %d\n", - conf.num_intervals_arg)); + OPT_UINT32_VAL(DSS, NUM_INTERVALS))); FOR_EACH_SNAPSHOT(s, i, sl) { if (snapshot_is_being_created(s)) continue; if (is_reference_snapshot(s)) continue; - if (s->interval < conf.num_intervals_arg) + if (s->interval < OPT_UINT32_VAL(DSS, NUM_INTERVALS)) continue; return s; } @@ -541,7 +632,7 @@ static struct snapshot *find_oldest_removable_snapshot(struct snapshot_list *sl) struct snapshot *s, *ref = NULL; num_complete = num_complete_snapshots(sl); - if (num_complete <= conf.min_complete_arg) + if (num_complete <= OPT_UINT32_VAL(DSS, MIN_COMPLETE)) return NULL; FOR_EACH_SNAPSHOT(s, i, sl) { if (snapshot_is_being_created(s)) @@ -601,7 +692,7 @@ static int try_to_free_disk_space(void) if (tv_diff(&next_removal_check, &now, NULL) > 0) return 0; if (!low_disk_space) { - if (conf.keep_redundant_given) + if (OPT_GIVEN(DSS, KEEP_REDUNDANT)) return 0; if (snapshot_creation_status != HS_READY) return 0; @@ -619,7 +710,8 @@ static int try_to_free_disk_space(void) * snapshots than configured, plus one. This way there is always one * snapshot that can be recycled. */ - if (!low_disk_space && sl.num_snapshots <= 1 << conf.num_intervals_arg) + if (!low_disk_space && sl.num_snapshots <= + 1 << OPT_UINT32_VAL(DSS, NUM_INTERVALS)) goto out; why = "outdated"; victim = find_outdated_snapshot(&sl); @@ -653,8 +745,9 @@ out: static void post_create_hook(void) { - char *cmd = make_message("%s %s/%s", conf.post_create_hook_arg, - conf.dest_dir_arg, path_to_last_complete_snapshot); + char *cmd = make_message("%s %s/%s", + OPT_STRING_VAL(DSS, POST_CREATE_HOOK), + OPT_STRING_VAL(DSS, DEST_DIR), path_to_last_complete_snapshot); DSS_NOTICE_LOG(("executing %s\n", cmd)); dss_exec_cmdline_pid(&create_pid, cmd); free(cmd); @@ -668,8 +761,8 @@ static void post_remove_hook(void) assert(s); - cmd = make_message("%s %s/%s", conf.post_remove_hook_arg, - conf.dest_dir_arg, s->name); + cmd = make_message("%s %s/%s", OPT_STRING_VAL(DSS, POST_REMOVE_HOOK), + OPT_STRING_VAL(DSS, DEST_DIR), s->name); DSS_NOTICE_LOG(("executing %s\n", cmd)); dss_exec_cmdline_pid(&remove_pid, cmd); free(cmd); @@ -865,11 +958,12 @@ static int handle_rsync_exit(int status) if (es != 0 && es != 24) { DSS_WARNING_LOG(("rsync exit code %d, error count %d\n", es, ++num_consecutive_rsync_errors)); - if (conf.create_given) { + if (!logfile) { /* called by com_run() */ ret = -E_BAD_EXIT_CODE; goto out; } - if (num_consecutive_rsync_errors > conf.max_rsync_errors_arg) { + if (num_consecutive_rsync_errors > + OPT_UINT32_VAL(RUN, MAX_RSYNC_ERRORS)) { ret = -E_TOO_MANY_RSYNC_ERRORS; snapshot_creation_status = HS_READY; goto out; @@ -906,7 +1000,7 @@ static int handle_pre_create_hook_exit(int status) if (es) { if (!warn_count--) { DSS_NOTICE_LOG(("pre_create_hook %s returned %d\n", - conf.pre_create_hook_arg, es)); + OPT_STRING_VAL(DSS, PRE_CREATE_HOOK), es)); DSS_NOTICE_LOG(("deferring snapshot creation...\n")); warn_count = 60; /* warn only once per hour */ } @@ -960,85 +1054,160 @@ static int handle_sigchld(void) return -E_BUG; } -static int check_config(void) +static int change_to_dest_dir(void) { - if (conf.unit_interval_arg <= 0) { - DSS_ERROR_LOG(("bad unit interval: %i\n", conf.unit_interval_arg)); + int ret; + const char *dd = OPT_STRING_VAL(DSS, DEST_DIR); + + DSS_INFO_LOG(("changing cwd to %s\n", dd)); + if (chdir(dd) >= 0) + return 1; + ret = -ERRNO_TO_DSS_ERROR(errno); + DSS_ERROR_LOG(("could not change cwd to %s\n", dd)); + return ret; +} + +static int check_config(const struct lls_command *cmd) +{ + int ret; + uint32_t unit_interval = OPT_UINT32_VAL(DSS, UNIT_INTERVAL); + uint32_t num_intervals = OPT_UINT32_VAL(DSS, NUM_INTERVALS); + + if (unit_interval == 0) { + DSS_ERROR_LOG(("bad unit interval: %i\n", unit_interval)); return -E_INVALID_NUMBER; } - DSS_DEBUG_LOG(("unit interval: %i day(s)\n", conf.unit_interval_arg)); - if (conf.num_intervals_arg <= 0 || conf.num_intervals_arg > 30) { - DSS_ERROR_LOG(("bad number of intervals: %i\n", - conf.num_intervals_arg)); + DSS_DEBUG_LOG(("unit interval: %i day(s)\n", unit_interval)); + + if (num_intervals == 0 || num_intervals > 30) { + DSS_ERROR_LOG(("bad number of intervals: %i\n", num_intervals)); return -E_INVALID_NUMBER; } - DSS_DEBUG_LOG(("number of intervals: %i\n", conf.num_intervals_arg)); + if (cmd == CMD_PTR(RUN) || cmd == CMD_PTR(CREATE)) + if (!OPT_GIVEN(DSS, SOURCE_DIR)) { + DSS_ERROR_LOG(("--source-dir required\n")); + return -E_SYNTAX; + } + if (cmd == CMD_PTR(RUN) || cmd == CMD_PTR(CREATE) + || cmd == CMD_PTR(LS) || cmd == CMD_PTR(PRUNE)) { + if (!OPT_GIVEN(DSS, DEST_DIR)) { + DSS_ERROR_LOG(("--dest-dir required\n")); + return -E_SYNTAX; + } + ret = change_to_dest_dir(); + if (ret < 0) + return ret; + } + DSS_DEBUG_LOG(("number of intervals: %i\n", num_intervals)); return 1; } -/* - * Returns < 0 on errors, 0 if no config file is given and > 0 if the config - * file was read successfully. - */ -static int parse_config_file(bool sighup) +static int lopsub_error(int lopsub_ret, char **errctx) { - int ret, config_file_exists; + const char *msg = lls_strerror(-lopsub_ret); + if (*errctx) + DSS_ERROR_LOG(("%s: %s\n", *errctx, msg)); + else + DSS_ERROR_LOG(("%s\n", msg)); + free(*errctx); + *errctx = NULL; + return -E_LOPSUB; +} + +static int parse_config_file(bool sighup, const struct lls_command *cmd) +{ + int ret, fd = -1; char *config_file = get_config_file_name(); struct stat statbuf; - char *old_logfile_arg = NULL; - int old_daemon_given = 0; - - if (sighup) { - if (conf.logfile_given) - old_logfile_arg = dss_strdup(conf.logfile_arg); - old_daemon_given = conf.daemon_given; + void *map; + size_t sz; + int cf_argc; + char **cf_argv, *errctx = NULL; + struct lls_parse_result *cf_lpr, *merged_lpr, *clpr; + const char *subcmd_name; + + ret = open(config_file, O_RDONLY); + if (ret < 0) { + if (errno != ENOENT || OPT_GIVEN(DSS, CONFIG_FILE)) { + ret = -ERRNO_TO_DSS_ERROR(errno); + DSS_ERROR_LOG(("config file %s can not be opened\n", + config_file)); + goto out; + } + /* no config file -- nothing to do */ + ret = 0; + goto success; } - - config_file_exists = !stat(config_file, &statbuf); - if (!config_file_exists && conf.config_file_given) { + fd = ret; + ret = fstat(fd, &statbuf); + if (ret < 0) { ret = -ERRNO_TO_DSS_ERROR(errno); DSS_ERROR_LOG(("failed to stat config file %s\n", config_file)); - goto out; + goto close_fd; } - if (config_file_exists) { - struct cmdline_parser_params params; - params.override = sighup; - params.initialize = 0; - params.check_required = 1; - params.check_ambiguity = 0; - params.print_errors = 1; - if (sighup) { /* invalidate all rsync options */ - int i; - - for (i = 0; i < conf.rsync_option_given; i++) { - free(conf.rsync_option_arg[i]); - conf.rsync_option_arg[i] = NULL; - } - conf.rsync_option_given = 0; - } - cmdline_parser_config_file(config_file, &conf, ¶ms); + sz = statbuf.st_size; + if (sz == 0) { /* config file is empty -- nothing to do */ + ret = 0; + goto success; } - ret = check_config(); - if (ret < 0) - goto out; - if (sighup) { - /* don't change daemon mode on SIGHUP */ - conf.daemon_given = old_daemon_given; - close_log(logfile); - logfile = NULL; - if (conf.logfile_given) - free(old_logfile_arg); - else if (conf.daemon_given) { /* re-use old logfile */ - conf.logfile_arg = old_logfile_arg; - conf.logfile_given = 1; - } + map = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + ret = -ERRNO_TO_DSS_ERROR(errno); + DSS_ERROR_LOG(("failed to mmap config file %s\n", + config_file)); + goto close_fd; + } + if (cmd == CMD_PTR(DSS)) + subcmd_name = NULL; + else + subcmd_name = lls_command_name(cmd); + ret = lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx); + munmap(map, sz); + if (ret < 0) { + DSS_ERROR_LOG(("failed to convert config file %s\n", + config_file)); + ret = lopsub_error(ret, &errctx); + goto close_fd; + } + cf_argc = ret; + ret = lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx); + lls_free_argv(cf_argv); + if (ret < 0) { + ret = lopsub_error(ret, &errctx); + goto close_fd; + } + clpr = cmd == CMD_PTR(DSS)? cmdline_lpr : cmdline_sublpr; + if (sighup) /* config file overrides command line */ + ret = lls_merge(cf_lpr, clpr, cmd, &merged_lpr, &errctx); + else /* command line options overrride config file options */ + ret = lls_merge(clpr, cf_lpr, cmd, &merged_lpr, &errctx); + lls_free_parse_result(cf_lpr, cmd); + if (ret < 0) { + ret = lopsub_error(ret, &errctx); + goto close_fd; } - if (conf.logfile_given && conf.run_given && conf.daemon_given) { - logfile = open_log(conf.logfile_arg); - log_welcome(conf.loglevel_arg); + ret = 1; +success: + assert(ret >= 0); + DSS_DEBUG_LOG(("loglevel: %d\n", OPT_UINT32_VAL(DSS, LOGLEVEL))); + if (cmd != CMD_PTR(DSS)) { + if (ret > 0) { + if (sublpr != cmdline_sublpr) + lls_free_parse_result(sublpr, cmd); + sublpr = merged_lpr; + } else + sublpr = cmdline_sublpr; + } else { + if (ret > 0) { + if (lpr != cmdline_lpr) + lls_free_parse_result(lpr, cmd); + lpr = merged_lpr; + } else + lpr = cmdline_lpr; } - DSS_DEBUG_LOG(("loglevel: %d\n", conf.loglevel_arg)); - ret = config_file_exists; +close_fd: + if (fd >= 0) + close(fd); out: free(config_file); if (ret < 0) @@ -1046,24 +1215,31 @@ out: return ret; } -static int change_to_dest_dir(void) -{ - DSS_INFO_LOG(("changing cwd to %s\n", conf.dest_dir_arg)); - return dss_chdir(conf.dest_dir_arg); -} - static int handle_sighup(void) { int ret; DSS_NOTICE_LOG(("SIGHUP, re-reading config\n")); dump_dss_config("old"); - ret = parse_config_file(true /* SIGHUP */); + ret = parse_config_file(true /* SIGHUP */, CMD_PTR(DSS)); + if (ret < 0) + return ret; + ret = parse_config_file(true /* SIGHUP */, CMD_PTR(RUN)); if (ret < 0) return ret; + ret = check_config(CMD_PTR(RUN)); + if (ret < 0) + return ret; + close_log(logfile); + logfile = NULL; + if (OPT_GIVEN(RUN, DAEMON) || daemonized) { + logfile = open_log(OPT_STRING_VAL(RUN, LOGFILE)); + log_welcome(OPT_UINT32_VAL(DSS, LOGLEVEL)); + daemonized = true; + } dump_dss_config("reloaded"); invalidate_next_snapshot_time(); - return change_to_dest_dir(); + return 1; } static void kill_children(void) @@ -1105,11 +1281,12 @@ out: */ static int use_rsync_locally(char *logname) { - char *h = conf.remote_host_arg; + const char *h = OPT_STRING_VAL(DSS, REMOTE_HOST); if (strcmp(h, "localhost") && strcmp(h, "127.0.0.1")) return 0; - if (conf.remote_user_given && strcmp(conf.remote_user_arg, logname)) + if (OPT_GIVEN(DSS, REMOTE_USER) && + strcmp(OPT_STRING_VAL(DSS, REMOTE_USER), logname)) return 0; return 1; } @@ -1125,8 +1302,6 @@ static int rename_resume_snap(int64_t creation_time) sl.num_snapshots = 0; ret = 0; - if (conf.no_resume_given) - goto out; dss_get_snapshot_list(&sl); /* * Snapshot recycling: We first look at the newest snapshot. If this @@ -1171,20 +1346,30 @@ out: static void create_rsync_argv(char ***argv, int64_t *num) { char *logname; - int i = 0, j; + int i = 0, j, N = OPT_GIVEN(DSS, RSYNC_OPTION); struct snapshot_list sl; + static bool seeded; dss_get_snapshot_list(&sl); assert(!name_of_reference_snapshot); name_of_reference_snapshot = name_of_newest_complete_snapshot(&sl); free_snapshot_list(&sl); - *argv = dss_malloc((15 + conf.rsync_option_given) * sizeof(char *)); + *argv = dss_malloc((15 + N) * sizeof(char *)); (*argv)[i++] = dss_strdup("rsync"); (*argv)[i++] = dss_strdup("-a"); (*argv)[i++] = dss_strdup("--delete"); - for (j = 0; j < conf.rsync_option_given; j++) - (*argv)[i++] = dss_strdup(conf.rsync_option_arg[j]); + if (!seeded) { + srandom((unsigned)time(NULL)); /* no need to be fancy here */ + seeded = true; + } + if (1000 * (random() / (RAND_MAX + 1.0)) < OPT_UINT32_VAL(DSS, CHECKSUM)) { + DSS_NOTICE_LOG(("adding --checksum to rsync options\n")); + (*argv)[i++] = dss_strdup("--checksum"); + } + for (j = 0; j < N; j++) + (*argv)[i++] = dss_strdup(lls_string_val(j, + OPT_RESULT(DSS, RSYNC_OPTION))); if (name_of_reference_snapshot) { DSS_INFO_LOG(("using %s as reference\n", name_of_reference_snapshot)); (*argv)[i++] = make_message("--link-dest=../%s", @@ -1193,11 +1378,13 @@ static void create_rsync_argv(char ***argv, int64_t *num) DSS_INFO_LOG(("no suitable reference snapshot found\n")); logname = dss_logname(); if (use_rsync_locally(logname)) - (*argv)[i++] = dss_strdup(conf.source_dir_arg); + (*argv)[i++] = dss_strdup(OPT_STRING_VAL(DSS, SOURCE_DIR)); else - (*argv)[i++] = make_message("%s@%s:%s/", conf.remote_user_given? - conf.remote_user_arg : logname, - conf.remote_host_arg, conf.source_dir_arg); + (*argv)[i++] = make_message("%s@%s:%s/", + OPT_GIVEN(DSS, REMOTE_USER)? + OPT_STRING_VAL(DSS, REMOTE_USER) : logname, + OPT_STRING_VAL(DSS, REMOTE_HOST), + OPT_STRING_VAL(DSS, SOURCE_DIR)); free(logname); *num = get_current_time(); (*argv)[i++] = incomplete_name(*num); @@ -1314,15 +1501,15 @@ out: static void exit_hook(int exit_code) { - char *argv[3]; + const char *argv[3]; pid_t pid; - argv[0] = conf.exit_hook_arg; + argv[0] = OPT_STRING_VAL(DSS, EXIT_HOOK); argv[1] = dss_strerror(-exit_code); argv[2] = NULL; DSS_NOTICE_LOG(("executing %s %s\n", argv[0], argv[1])); - dss_exec(&pid, conf.exit_hook_arg, argv); + dss_exec(&pid, argv[0], (char **)argv); } static void lock_dss_or_die(void) @@ -1339,16 +1526,41 @@ static void lock_dss_or_die(void) static int com_run(void) { - int ret; + int ret, fd = -1; + char *config_file; + pid_t pid; - lock_dss_or_die(); - if (conf.dry_run_given) { - DSS_ERROR_LOG(("dry_run not supported by this command\n")); + if (OPT_GIVEN(DSS, DRY_RUN)) { + DSS_ERROR_LOG(("dry run not supported by this command\n")); return -E_SYNTAX; } + config_file = get_config_file_name(); + ret = get_dss_pid(config_file, &pid); + free(config_file); + if (ret >= 0) { + DSS_ERROR_LOG(("pid %d\n", (int)pid)); + return -E_ALREADY_RUNNING; + } + if (OPT_GIVEN(RUN, DAEMON)) { + fd = daemon_init(); + daemonized = true; + logfile = open_log(OPT_STRING_VAL(RUN, LOGFILE)); + } + lock_dss_or_die(); + dump_dss_config("startup"); ret = install_sighandler(SIGHUP); if (ret < 0) return ret; + if (fd >= 0) { + ret = write(fd, "\0", 1); + if (ret != 1) { + DSS_ERROR_LOG(("write to daemon pipe returned %d\n", + ret)); + if (ret < 0) + return -ERRNO_TO_DSS_ERROR(errno); + return -E_BUG; + } + } ret = select_loop(); if (ret >= 0) /* impossible */ ret = -E_BUG; @@ -1356,6 +1568,7 @@ static int com_run(void) exit_hook(ret); return ret; } +EXPORT_CMD_HANDLER(run); static int com_prune(void) { @@ -1382,7 +1595,7 @@ static int com_prune(void) ret = 0; goto out; rm: - if (conf.dry_run_given) { + if (OPT_GIVEN(DSS, DRY_RUN)) { dss_msg("%s snapshot %s (interval = %i)\n", why, victim->name, victim->interval); ret = 0; @@ -1415,6 +1628,7 @@ out: free_snapshot_list(&sl); return ret; } +EXPORT_CMD_HANDLER(prune); static int com_create(void) { @@ -1422,7 +1636,7 @@ static int com_create(void) char **rsync_argv; lock_dss_or_die(); - if (conf.dry_run_given) { + if (OPT_GIVEN(DSS, DRY_RUN)) { int i; char *msg = NULL; create_rsync_argv(&rsync_argv, ¤t_snapshot_creation_time); @@ -1463,6 +1677,7 @@ out: free_rsync_argv(rsync_argv); return ret; } +EXPORT_CMD_HANDLER(create); static int com_ls(void) { @@ -1480,6 +1695,7 @@ static int com_ls(void) free_snapshot_list(&sl); return 1; } +EXPORT_CMD_HANDLER(ls); static int setup_signal_handling(void) { @@ -1496,47 +1712,95 @@ static int setup_signal_handling(void) return install_sighandler(SIGCHLD); } +static void handle_version_and_help(void) +{ + char *txt; + + if (OPT_GIVEN(DSS, DETAILED_HELP)) + txt = lls_long_help(CMD_PTR(DSS)); + else if (OPT_GIVEN(DSS, HELP)) + txt = lls_short_help(CMD_PTR(DSS)); + else if (OPT_GIVEN(DSS, VERSION)) + txt = dss_strdup(VERSION_STRING); + else + return; + printf("%s", txt); + free(txt); + exit(EXIT_SUCCESS); +} + +static void show_subcommand_summary(void) +{ + const struct lls_command *cmd; + int i; + + printf("Available subcommands:\n"); + for (i = 1; (cmd = lls_cmd(i, dss_suite)); i++) { + const char *name = lls_command_name(cmd); + const char *purpose = lls_purpose(cmd); + printf("%-10s%s\n", name, purpose); + } + exit(EXIT_SUCCESS); +} + int main(int argc, char **argv) { int ret; - struct cmdline_parser_params params; - - params.override = 0; - params.initialize = 1; - params.check_required = 0; - params.check_ambiguity = 0; - params.print_errors = 1; + const struct lls_command *cmd = CMD_PTR(DSS); + char *errctx = NULL; + unsigned num_inputs; + const struct dss_user_data *ud = NULL; - cmdline_parser_ext(argc, argv, &conf, ¶ms); /* aborts on errors */ - ret = parse_config_file(0); - ret = parse_config_file(false /* no SIGHUP */); + ret = lls_parse(argc, argv, cmd, &cmdline_lpr, &errctx); + if (ret < 0) { + ret = lopsub_error(ret, &errctx); + goto out; + } + lpr = cmdline_lpr; + ret = parse_config_file(false /* no SIGHUP */, cmd); if (ret < 0) goto out; - if (ret == 0) { /* no config file given */ - /* - * Parse the command line options again, but this time check - * that all required options are given. - */ - params.override = 1; - params.initialize = 1; - params.check_required = 1; - params.check_ambiguity = 1; - params.print_errors = 1; - cmdline_parser_ext(argc, argv, &conf, ¶ms); /* aborts on errors */ + handle_version_and_help(); + num_inputs = lls_num_inputs(lpr); + if (num_inputs == 0) + show_subcommand_summary(); + ret = lls_lookup_subcmd(argv[argc - num_inputs], dss_suite, &errctx); + if (ret < 0) { + ret = lopsub_error(ret, &errctx); + goto out; } - if (conf.daemon_given) - daemon_init(); - ret = change_to_dest_dir(); + cmd = lls_cmd(ret, dss_suite); + ret = lls_parse(num_inputs, argv + argc - num_inputs, cmd, + &cmdline_sublpr, &errctx); + if (ret < 0) { + ret = lopsub_error(ret, &errctx); + goto out; + } + sublpr = cmdline_sublpr; + ret = parse_config_file(false /* no SIGHUP */, cmd); + if (ret < 0) + goto out; + ret = check_config(cmd); if (ret < 0) goto out; - dump_dss_config("startup"); ret = setup_signal_handling(); if (ret < 0) goto out; - ret = call_command_handler(); + ud = lls_user_data(cmd); + ret = ud->handler(); signal_shutdown(); out: - if (ret < 0) + if (ret < 0) { + if (errctx) + DSS_ERROR_LOG(("%s\n", errctx)); DSS_EMERG_LOG(("%s\n", dss_strerror(-ret))); + } + free(errctx); + lls_free_parse_result(lpr, CMD_PTR(DSS)); + if (lpr != cmdline_lpr) + lls_free_parse_result(cmdline_lpr, CMD_PTR(DSS)); + lls_free_parse_result(sublpr, cmd); + if (sublpr != cmdline_sublpr) + lls_free_parse_result(cmdline_sublpr, cmd); exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/dss.dia b/dss.dia deleted file mode 100644 index a02f700..0000000 --- a/dss.dia +++ /dev/null @@ -1,960 +0,0 @@ - - - - - - - - - - - - - #A4# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #DSS# - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dss.ggo b/dss.ggo deleted file mode 100644 index 12871ea..0000000 --- a/dss.ggo +++ /dev/null @@ -1,483 +0,0 @@ -# Copyright (C) 2008-2010 Andre Noll -# -# Licensed under the GPL v2. For licencing details see COPYING. - -package "dss" -version "0.1.7" -purpose "the dyadic snapshot scheduler - -dss creates hardlink-based snapshots of a given directory on a remote -or local host using rsync's link-dest feature. -" - -######################### -section "General options" -######################### - -option "config-file" c -#~~~~~~~~~~~~~~~~~~~~~ -"(default='~/.dssrc')" -string typestr="filename" -optional -details=" - Options may be given at the command line or in the - configuration file. As usual, if an option is given both at - the command line and in the configuration file, the command - line option takes precedence. - - However, there is an important exception to this rule: - If the --run option was given (see below) then dss honors - SIGHUP and re-reads its configuration file whenever it - receives this signal. In this case the options in the config - file override any options that were previously given at the - command line. This allows to change the configuration of a - running dss process on the fly by sending SIGHUP. -" - -option "daemon" d -#~~~~~~~~~~~~~~~~ -"Run as background daemon" -flag off -details=" - This option is mostly useful in conjunction with the -R option - described below. - - Note that it is not possible to change whether dss runs as background - daemon by sending SIGHUP. -" - -option "dry-run" D -#~~~~~~~~~~~~~~~~~ -"Only print what would be done" -flag off -details=" - This flag does not make sense for all commands. The run - command refuses to start if this option was given. The ls - command silently ignores this flag. -" - -################# -section "Logging" -################# - -option "loglevel" l -#~~~~~~~~~~~~~~~~~~ -"Set loglevel (0-6)" -int typestr="level" -default="3" -optional -details=" - Lower values mean more verbose logging. -" - -option "logfile" - -#~~~~~~~~~~~~~~~~~ -"Logfile for the dss daemon process" -string typestr="filename" -optional -default="/dev/null" -details = " - This option is only honored if both --run and --daemon are - given. Otherwise it is silently ignored and log output is written - to stderr. - - The default value means that nothing will be logged in daemon mode - unless this option is given. -" - -################## -section "Commands" -################## - -defgroup "command" -#================= -groupdesc=" - dss supports a couple of commands each of which corresponds - to a different command line option. Exactly one of these - options must be given. - -" -required - -groupoption "create" C -#~~~~~~~~~~~~~~~~~~~~~ -"Create a new snapshot" -group="command" -details=" - Execute the rsync command to create a new snapshot. Note that - this command does not care about free disk space. -" - -groupoption "prune" P -#~~~~~~~~~~~~~~~~~~~~ -"Remove redundant and outdated snapshots" -group="command" -details=" - A snapshot is considered outdated if its interval number - is greater or equal than the specified number of unit - intervals. See the \"Intervals\" section below for the precise - definition of these terms. - - A snapshot is said to be redundant if it belongs to an - interval that already contains more than the desired number - of snapshots. - - The prune command gets rid of both outdated and redundant - snapshots. -" - -groupoption "ls" L -#~~~~~~~~~~~~~~~~~ -"Print a list of all snapshots" -group="command" -details=" - The list will contain all snapshots no matter of their state, - i. e. incomplete snapshots and snapshots being deleted will - also be listed. -" - -groupoption "run" R -#~~~~~~~~~~~~~~~~~~ -"Start creating and pruning snapshots" -group="command" -details=" - This is the main mode of operation. Snapshots will be created - in an endless loop as needed and pruned automatically. The loop - only terminates on fatal errors or if a terminating signal was - received. See also the --exit-hook option. -" - -groupoption "kill" K -#~~~~~~~~~~~~~~~~~~~ -"Kill a running dss process" -group="command" -details=" - This sends SIGTERM to the dss process that corresponds to the - given config file. If --dry-run is given, the PID of the dss - process is written to stdout, but no signal is sent. -" - -groupoption "reload" - -#~~~~~~~~~~~~~~~~~~~~~ -"force a running dss process to reload its config file" -group="command" -details=" - This differs from --kill only in that SIGHUP rather than SIGTERM - is sent to the dss process. -" - -############################### -section "Rsync-related options" -############################### - -option "remote-host" H -#~~~~~~~~~~~~~~~~~~~~~ -"Remote host" -string typestr="hostname" -default="localhost" -optional -details=" - If this option is given and its value differs from the local - host, then rsync uses ssh. Make sure there is no password - needed for the ssh connection. To achieve that, use public key - authentication for ssh and, if needed, set the remote user name - by using the --remote-user option. -" - -option "remote-user" U -#~~~~~~~~~~~~~~~~~~~~~ -"Remote user name (default: current user)" -string typestr="username" -optional -details=" - Set this if the user running dss is different from the - user at the remote host when using ssh. -" - -option "source-dir" - -#~~~~~~~~~~~~~~~~~~~~ -"The data directory" -string typestr="dirname" -required -details=" - The directory on the remote host from which snapshots are - taken. Of course, the user specified as --remote-user must - have read access to this directory. -" - -option "dest-dir" - -#~~~~~~~~~~~~~~~~~~ -"Snapshot dir" -string typestr="dirname" -required -details=" - The destination directory on the local host where snapshots - will be written. This must be writable by the user who runs - dss. -" - -option "no-resume" - -#~~~~~~~~~~~~~~~~~~~ -"Do not try to resume from previous runs" -flag off -details = " - Starting from version 0.1.4, dss tries to resume from a - previously cancelled dss instance by default. It does so by - looking at the status of the most recently created snapshot. If - this snapshot status is incomplete, its directory is reused - as the destination directory for a subsequent rsync run. - - The --no-resume option deactivates this feature so that a new - directory is always used as the rsync destination directory. -" - -option "rsync-option" O -#~~~~~~~~~~~~~~~~~~~~~~ -"Further rsync options" -string typestr="option" -optional -multiple -details=" - This option may be given multiple times. The given argument is - passed verbatim to the rsync command. Note that in order to use - rsync options that require an argument, you have to specify the - option and its argument as separate --rsync-options, like this: - - --rsync-option --exclude --rsync-option /proc -" - -option "max-rsync-errors" - -"Terminate after this many rsync failures" -int typestr="count" -default="10" -optional -details=" - Only relevant when operating in --run mode (see above). If - the rsync process exits with a fatal error, dss restarts - the command in the hope that the problem is transient - and subsequent rsync runs succeed. After the given number - of consecutive rsync error exits, however, dss gives up, - executes the exit hook and terminates. Set this to zero if - dss should exit immediately on the first rsync error. - - The only non-fatal error is when rsync exits with code 24. This - indicates a partial transfer due to vanished source files - and happens frequently when snapshotting a directory which - is concurrently being modified. -" - -################### -section "Intervals" -################### - -option "unit-interval" u -#~~~~~~~~~~~~~~~~~~~~~~~ -"The duration of a unit interval" -int typestr="days" -default="4" -optional -details=" - Snapshot aging is implemented in terms of intervals. There are two - command line options related to intervals: the duration u of a unit - interval and the number of unit intervals, denoted n below. - - dss removes snapshots older than n times u and tries to keep 2^(n - - k - 1) snapshots in interval k, where the interval number k counts - from zero to n - 1, with zero being the most recent unit interval. - - Hence the oldest snapshot will at most be u * n days old (4 days * - 5 intervals = 20 days, if default values are used). Moreover, there - are at most 2^n - 1 snapshots in total (2^5 - 1 = 31 by default). Note - that for this to work out your system must be fast enough to create at - least 2^(n - 1) snapshots per unit interval (16 snapshots in 4 days = - one snapshot in 6 hours), because this is the number of snapshots in - interval zero. -" - -option "num-intervals" n -#~~~~~~~~~~~~~~~~~~~~~~~ -"The number of unit intervals" -int typestr="num" -default="5" -optional -details=" - Note that increasing this number by one doubles the total number of - snapshots. See the documentation of --unit-interval above. -" - -############### -section "Hooks" -############### - -option "pre-create-hook" r -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Executed before snapshot creation" -string typestr="command" -default = "true" -optional -details=" - Execute this command before trying to create a new snapshot. - If this command returns with a non-zero exit status, no - snapshot is being created and the operation is retried later. - - For example, one might want to execute a script that checks - whether all snapshot-related file systems are properly mounted. - - Another possible application of this is to return non-zero - during office hours in order to not slow down the file systems - by taking snapshots. -" - -option "post-create-hook" o -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Executed after snapshot creation" -string typestr="command" -default = "true" -optional -details=" - Execute this after a snapshot has successfully been - created. The full path of the newly created snapshot is - passed to the hook as the first argument. The exit code of - this hook is ignored. - - For instance this hook can be used to count the number of - files per user and/or the disk usage patterns in order to - store them in a database for further analysis. -" - -option "pre-remove-hook" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Executed before snapshot removal" -string typestr="command" -default = "true" -optional -details=" - Execute this command before removing a snapshot. The full - path to the snapshot about to be deleted is passed to the - command as the first argument. If the command returns with - a non-zero exit status, no snapshot is being removed and the - operation is retried later. - - For example, one might want to execute a script that checks - whether the snapshot to be deleted is currently used by - another process, e.g. by a tape-based backup system that runs - concurrently to dss. - - Another possible application of this is to record disk-usage - patterns before and after snapshot removal. -" - -option "post-remove-hook" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Executed after snapshot removal" -string typestr="command" -default = "true" -optional -details=" - Execute this after a snapshot has successfully been removed. As - for the pre-remove hook, the full path of the removed snapshot - is passed to the hook as the first argument. The exit code - of this hook is ignored. -" - -option "exit-hook" e -#~~~~~~~~~~~~~~~~~~~ -"Executed if run command exits" -string typestr="command" -default = "true" -optional -details=" - This hook is only used if the --run command was given which - instructs dss to run in an endless loop. The exit-hook gets - executed whenever this endless loop terminates. The reason - for terminating the loop is passed as the first argument. - - One possible application for this hook is to send email to the - system administrator to let her know that no more snapshots - are going to be created. -" - -############################### -section "Disk space monitoring" -############################### - -option "min-free-mb" m -#~~~~~~~~~~~~~~~~~~~~~ -"Minimal amount of free disk space" -int typestr="megabytes" -default="100" -optional -details=" - If disk space on the file system containing the destination - directory gets low, \"dss --run\" will suspend the currently - running rsync process and will start to remove snapshots in - order to free disk space. This option specifies the minimal - amount of free disk space. If less than the given number of - megabytes is available, snapshots are being deleted. See also - the --min_free_percent and the min-free-percent-inodes options. - - A value of zero deactivates this check. -" - -option "min-free-percent" p -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Minimal percent of free disk space" -int typestr="percent" -default="2" -optional -details=" - See --min-free-mb. Note that it is not recommended to set both - --min-free-mb and --min-free-percent to zero as this will - cause your file system to fill up quickly. -" -option "min-free-percent-inodes" i -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Minimal percent of free inodes" -int typestr="percent" -default="0" -optional -details=" - Specify the minimum amount of free inodes on the file system - containing the destination dir. If less than that many inodes - are free, snapshot removal kicks in just as in case of low - disk space. - - Note that not every file system supports the concept of inodes. - Moreover it is not possible to reliably detect whether this is - the case. Therefore this feature is disabled by default. It's - safe to enable it for ext2/ext3/ext4 file systems on linux - though. - - A value of zero (the default) deactivates this check. -" - -option "keep-redundant" k -#~~~~~~~~~~~~~~~~~~~~~~~~ -"Prune by disk space only" -flag off -details=" - By default, redundant and outdated snapshots are removed automatically - to keep the number of snapshots in harmony with the configured - policy. If this flag is given, dss removes such snapshots only if - disk space or number of free inodes becomes low. -" - -option "min-complete" - -#~~~~~~~~~~~~~~~~~~~~~~ -"Minimal number of complete snapshots to keep" -int typestr = "num" -default = "1" -optional -details = " - This option is only relevant if snapshots must be deleted - because disk space gets low. - - dss refuses to remove old snapshots if there are fewer complete - snapshots left than the given number. The default value of one - guarantees that at least one complete snapshot is available - at all times. - - If only complete snapshot are left, and there is not - enough disk space available for another snapshot, the program - terminates with a \"No space left on device\" error. -" diff --git a/dss.suite b/dss.suite new file mode 100644 index 0000000..e69cc28 --- /dev/null +++ b/dss.suite @@ -0,0 +1,499 @@ +[suite dss] +caption = Subcommands + +[supercommand dss] + [description] + dss creates hardlink-based snapshots of a given directory on a remote + or local host using rsync's link-dest feature. + [/description] + purpose = the dyadic snapshot scheduler + synopsis = [global-options...] [--] [ [subcommand-options...]] + + [option general-options-section] + summary = General options + flag ignored + [option help] + summary = print help and exit + short_opt = h + [option detailed-help] + summary = print help, including all details, and exit + [option version] + summary = print version and exit + short_opt = V + [option config-file] + short_opt = c + summary = use alternative config file (default: ~/.dssrc) + typestr = path + arg_info = required_arg + arg_type = string + [help] + Options may be given at the command line or in the configuration + file. As usual, if an option is given both at the command line and + in the configuration file, the command line option takes precedence. + + However, there is one exception to this rule: The run subcommand + re-reads the configuration file when it receives the HUP signal. In + this case the options in the config file override any options that + were previously given at the command line. This allows to change the + configuration of a running dss process by sending SIGHUP. + [/help] + [option loglevel] + short_opt = l + summary = set loglevel (0-6) + typestr = level + arg_info = required_arg + arg_type = uint32 + default_val = 4 + [help] + Lower values mean more verbose logging. + [/help] + [option dry-run] + short_opt = n + summary = only print what would be done + [help] + This flag does not make sense for all subcommands. The run subcommand + refuses to start if this option was given while the ls subcommand + silently ignores the flag. + [/help] + [option source-dir] + summary = the remote directory to snapshot + typestr = dirname + arg_info = required_arg + arg_type = string + [help] + The directory on the remote host from which snapshots are taken. + Of course, the user specified as --remote-user must have read access + to this directory. + + This option is mandatory for the create and run subcommands: It must + be given at the command line or in the config file. + [/help] + [option dest-dir] + summary = where snapshots are stored + typestr = dirname + arg_info = required_arg + arg_type = string + [help] + The destination directory on the local host where snapshots will be + written. This must be writable by the user who runs dss. + + This option is mandatory for all subcommands except kill. + [/help] + [option Rsync-options] + summary = Controlling how rsync is run + flag ignored + [help] + These options are only relevant to the run and the create subcommands. + [/help] + [option remote-host] + short_opt = H + summary = host to take snapshots from + typestr = hostname + arg_info = required_arg + arg_type = string + default_val = localhost + [help] + If this option is given and its value differs from the local + host, then rsync uses ssh. Make sure there is no password + needed for the ssh connection. To achieve that, use public key + authentication for ssh and, if needed, set the remote user name + by using the --remote-user option. + [/help] + [option remote-user] + short_opt = U + summary = Remote user name (default: current user) + arg_info = required_arg + typestr = username + arg_type = string + [help] + Set this if the user that runs dss is different from the user on the + remote host. + [/help] + [option checksum] + summary = run rsync with --checksum occasionally + typestr = permille + arg_info = required_arg + arg_type = uint32 + default_val = 0 + [help] + If a file on the backup becomes corrupt in a way that file size + and modification time still match the original file, rsync will not + consider the file for transfer ("quick check"). Hence the corruption + stays on the backup until the file is modified on the source. + The --checksum option of rsync disables the quick check and compares + the contents of each file, fixing such corruptions. Since computing + the checksums adds a significant slowdown due to a lot of disk I/O, + the option is not enabled by default. + + The argument to the --checksum option of dss is a number between 0 + and 1000, inclusively, which determines the probability of adding + --checksum to the rsync options each time a snapshot is created. The + default value zero means to never add the option. The value 100 will + create every tenth snapshot (on average) using checksums, and the + value 1000 will always pass --checksum to rsync. + [/help] + [option rsync-option] + short_opt = O + summary = further rsync options + typestr = option + arg_info = required_arg + arg_type = string + flag multiple + [help] + This option may be given multiple times. The given argument is + passed verbatim to the rsync command. Note that in order to use + rsync options that require an argument, you have to specify the + option and its argument as separate --rsync-options, like this: + + --rsync-option --exclude --rsync-option /proc + [/help] + [option intervals] + summary = Fine tuning the number of snapshots per time unit + flag ignored + [help] + Snapshot aging is implemented in terms of intervals. There are two + command line options related to intervals: the duration u of a unit + interval and the number of unit intervals, denoted n below. + + dss removes snapshots older than n times u and tries to keep 2^(n - + k - 1) snapshots in interval k, where the interval number k counts + from zero to n - 1, with zero being the most recent unit interval. + + Hence the oldest snapshot will at most be u * n days old (4 days * + 5 intervals = 20 days, if default values are used). Moreover, there + are at most 2^n - 1 snapshots in total (2^5 - 1 = 31 by default). Note + that for this to work out your system must be fast enough to create at + least 2^(n - 1) snapshots per unit interval (16 snapshots in 4 days = + one snapshot in 6 hours), because this is the number of snapshots in + interval zero. + [/help] + [option unit-interval] + short_opt = u + summary = the duration of a unit interval + typestr = days + arg_info = required_arg + arg_type = uint32 + default_val = 4 + [help] + Increasing this number instructs dss to create fewer snapshots per + time unit while the number of snapshots to keep stays the same. + [/help] + [option num-intervals] + short_opt = n + summary = the number of unit intervals + typestr = num + arg_info = required_arg + arg_type = uint32 + default_val = 5 + [help] + Increasing this number by one doubles the total number of + snapshots. + [/help] + [option hooks] + summary = Commands to be run on certain events + flag ignored + [help] + All hooks default to "true". That is, the true(1) utility (which + always returns with exit code zero) is executed if the hook command + is not specified. + [/help] + [option pre-create-hook] + short_opt = r + summary = executed before a snapshot is created + typestr = command + arg_info = required_arg + arg_type = string + default_val = true + [help] + This command is executed before dss runs rsync to create a new + snapshot. If the command returns with a non-zero exit status, no + snapshot will be created and the operation is retried later. + + For example, the command could execute a script that checks whether + all snapshot-related file systems are mounted. + + Another possible application of the pre-create hook is to return + non-zero during office hours in order to not slow down the file + systems by taking snapshots. + [/help] + [option post-create-hook] + summary = executed after a snapshot has been created + typestr = command + arg_info = required_arg + arg_type = string + default_val = true + [help] + This is only executed if a snapshot has successfully been created. The + full path of the newly created snapshot is passed to the hook as the + first argument. The exit code of this hook is ignored. + + For instance this hook could count the number of files per user + and/or compute disk usage patterns to be stored in a database for + further analysis. + [/help] + [option pre-remove-hook] + summary = executed before a snapshot is removed + typestr = command + arg_info = required_arg + arg_type = string + default_val = true + [help] + The full path to the snapshot which is about to be removed is passed + to the command as the first argument. If the command returns with + a non-zero exit status, the snapshot is not going to be removed and + the operation is retried later. + + For example, one could execute a script that checks whether the + snapshot to be deleted is currently used by another process, e.g. by + a tape-based backup system that runs concurrently to dss. + + Another possible application of this is to record disk-usage + patterns before and after snapshot removal. + [/help] + [option post-remove-hook] + summary = executed after snapshot removal + typestr = command + arg_info = required_arg + arg_type = string + default_val = true + [help] + As for the pre-remove hook, the full path of the removed snapshot is + passed to the hook as the first argument. The exit code of this hook + is ignored. + [/help] + [option exit-hook] + summary = executed before the run command exits + typestr = command + arg_info = required_arg + arg_type = string + default_val = true + [help] + This hook is only relevant to the run subcommand. It is executed just + before dss terminates. The reason for termination is passed as the + first argument. + + One possible application for this hook is to send email to the system + administrator to let her know that no more snapshots are going to + be created. + [/help] + + [option disk-space-monitoring] + summary = Disk space monitoring + flag ignored + [help] + The options of this section control the aggressiveness of snapshot + removal. That is, they define under which circumstances existing + snapshots are removed. These options are only relevant to the run + and the prune subcommands. + [/help] + [option min-free-mb] + short_opt = m + summary = minimal amount of free disk space + arg_info = required_arg + arg_type = uint32 + typestr = megabytes + default_val = 100 + [help] + If disk space on the file system containing the destination + directory gets low, the run subcommand suspends the currently + running rsync process and starts to remove snapshots in order to + free disk space. This option specifies the minimal amount of free + disk space. If less than the given number of megabytes is available, + snapshots are being deleted. See also the --min_free_percent and the + min-free-percent-inodes options below. + + A value of zero deactivates this check. + [/help] + [option min-free-percent] + short_opt = p + summary = minimal percentage of free disk space + arg_info = required_arg + arg_type = uint32 + typestr = percent + default_val = 2 + [help] + This is like --min-free-mb but allows to specify the amount of + free disk space as a percentage. It is not recommended to set both + --min-free-mb and --min-free-percent to zero as this will cause your + file system to fill up quickly. + [/help] + [option min-free-percent-inodes] + short_opt = i + summary = minimal percent of free inodes + arg_info = required_arg + arg_type = uint32 + typestr = percent + default_val = 0 + [help] + The minimum amount of free inodes on the file system containing the + destination dir. If the percentage of free inodes drops below the + given value, snapshot removal kicks in like in case of low disk space. + + The number of free inodes is determined from the f_ffree field of + the statvfs structure. However, some file systems set this field to + zero, indicating that the number of inodes is basically unlimited. + Moreover it is not possible to reliably detect whether this is the + case. Therefore this feature is disabled by default. It's safe to + enable it for ext2/ext3/ext4 file systems on linux though. + + A value of zero (the default) deactivates this check. + [/help] + [option keep-redundant] + short_opt = k + summary = prune by disk space only + [help] + By default, redundant and outdated snapshots are removed automatically + to keep the number of snapshots in harmony with the configured + policy. If this flag is given, dss removes such snapshots only if + disk space or number of free inodes becomes low. + [/help] + [option min-complete] + summary = minimal number of complete snapshots to keep + arg_info = optional_arg + arg_type = uint32 + typestr = num + default_val = 1 + [help] + This option is only relevant if snapshots must be deleted because + disk space gets low. + + dss refuses to remove old snapshots if there are fewer complete + snapshots left than the given number. The default value of one + guarantees that at least one complete snapshot is available at + all times. + + If only complete snapshots are left, and there is not enough + disk space available for another snapshot, the program terminates + with a "No space left on device" error. + [/help] + +[introduction] + dss supports the subcommands described below. If no subcommand is + given, the list of available subcommands is shown and the program + terminates successfully without performing any further action. +[/introduction] + +[subcommand run] + purpose = start creating and pruning snapshots + [description] + This is the main mode of operation. Snapshots are created in an endless + loop as needed and pruned automatically. The loop only terminates on + fatal errors or if a terminating signal was received. See also the + --exit-hook option. + [/description] + [option daemon] + short_opt = d + summary = run as background daemon + [help] + If this option is given, the dss command detaches from the console + and continues to run in the background. It is not possible to let + a daemonized process re-attach to the console by editing the config + file and sending SIGHUP. However, log output may be redirected to a + different file in this way. + + See --logfile. + [/help] + [option logfile] + short_opt = l + summary = where to write log output + arg_info = required_arg + arg_type = string + typestr = path + default_val = /dev/null + [help] + This option is only honored if --daemon is given, in which case + log messages go to the given file. Otherwise the option is silently + ignored and log output is written to stderr. + [/help] + [option max-rsync-errors] + summary = terminate after this many rsync failures + typestr = count + arg_info = required_arg + arg_type = uint32 + default_val = 10 + [help] + If the rsync process exits with a fatal error, dss restarts the command + in the hope that the problem is transient and subsequent rsync runs + succeed. After the given number of consecutive rsync error exits, + however, dss gives up, executes the exit hook and terminates. Set + this to zero if dss should exit immediately on the first rsync error. + + The only non-fatal error is when rsync exits with code 24. This + indicates a partial transfer due to vanished source files and happens + frequently when snapshotting a directory which is concurrently being + modified. + [/help] +[subcommand create] + purpose = execute rsync once to create a new snapshot + [description] + This command does not check the amount free disk space. The pre-create + and post-create hooks are honored, however. + + Specify --dry-run to see the rsync command which is executed to create + snapshots. + [/description] +[subcommand prune] + purpose = remove redundant and outdated snapshots + [description] + A snapshot is considered outdated if its interval number is greater or + equal than the specified number of unit intervals. See --unit-interval + and --num-intervals above. + + A snapshot is said to be redundant if the interval it belongs to + contains more than the configured number of snapshots. + + The prune command gets rid of both outdated and redundant snapshots. At + most one snapshot is removed per invocation. If --dry-run is given, the + subcommand only prints the snapshot that would be removed. + [/description] +[subcommand ls] + purpose = print the list of all snapshots + [description] + The list contains all existing snapshots, no matter of their state. + Incomplete snapshots and snapshots being deleted will also be listed. + [/description] +[subcommand kill] + purpose = send a signal to a running dss process + [description] + This sends a signal to the dss process that corresponds to the given + config file. If --dry-run is given, the PID of the dss process is + written to stdout, but no signal is sent. + [/description] + [option signal] + short_opt = s + summary = send the given signal rather than SIGTERM + typestr = signal + arg_info = required_arg + arg_type = string + default_val = SIGTERM + [help] + Like for kill(1), alternate signals may be specified in three ways: as + a signal number (e.g., 9), the signal name (e.g., KILL), or the signal + name prefixed with "SIG" (e.g., SIGKILL). In the latter two forms, + the signal name and the prefix are case insensitive, so "sigkill" + works as well. + + Sending SIGHUP causes the running dss process to reload its config file. + [/help] + +[section copyright] + Written by Andre Noll + .br + Copyright (C) 2008 - present Andre Noll + .br + License: GNU GPL version 2 + .br + This is free software: you are free to change and redistribute it. + .br + There is NO WARRANTY, to the extent permitted by law. + .br + Report bugs to + .MT + Andre Noll + .ME +[/section] + +[section see also] + .BR ssh (1) , + .BR rsync (1) +[/section] diff --git a/err.h b/err.h index e7aced0..3d4e136 100644 --- a/err.h +++ b/err.h @@ -56,7 +56,9 @@ static inline char *dss_strerror(int num) DSS_ERROR(SIGNAL, "caught terminating signal"), \ DSS_ERROR(BUG, "values of beta might cause dom!"), \ DSS_ERROR(NOT_RUNNING, "dss not running"), \ - DSS_ERROR(TOO_MANY_RSYNC_ERRORS, "too many consecutive rsync errors") + DSS_ERROR(ALREADY_RUNNING, "dss already running"), \ + DSS_ERROR(TOO_MANY_RSYNC_ERRORS, "too many consecutive rsync errors"), \ + DSS_ERROR(LOPSUB, "lopsub error") /** * This is temporarily defined to expand to its first argument (prefixed by diff --git a/file.c b/file.c index 8494408..d606c16 100644 --- a/file.c +++ b/file.c @@ -69,19 +69,6 @@ out: closedir(dir); return ret; } -/** - * Wrapper for chdir(2). - * - * \param path The specified directory. - * - * \return Standard. - */ -int dss_chdir(const char *path) -{ - if (chdir(path) >= 0) - return 1; - return -ERRNO_TO_DSS_ERROR(errno); -} /** * Set a file descriptor to non-blocking mode. diff --git a/file.h b/file.h index c462b10..140a505 100644 --- a/file.h +++ b/file.h @@ -3,7 +3,6 @@ * * Licensed under the GPL v2. For licencing details see COPYING. */ -int dss_chdir(const char *path); int for_each_subdir(int (*func)(const char *, void *), void *private_data); __must_check int mark_fd_nonblocking(int fd); /** diff --git a/ipc.c b/ipc.c index c55554c..183c76e 100644 --- a/ipc.c +++ b/ipc.c @@ -258,11 +258,11 @@ static int get_key_or_die(const char *config_file) return ret; } -static int mutex_get(int key, int flags) +static int mutex_get(key_t key, int flags) { int ret; - DSS_DEBUG_LOG(("getting semaphore 0x%x\n", key)); + DSS_DEBUG_LOG(("getting semaphore 0x%lx\n", (long)key)); ret = semget(key, 2, flags); if (ret < 0) return -ERRNO_TO_DSS_ERROR(errno); @@ -282,31 +282,6 @@ static int do_semop(int id, struct sembuf *sops, int num) return -ERRNO_TO_DSS_ERROR(errno); } -static int mutex_lock(int id) -{ - struct sembuf sops[4]; - - DSS_DEBUG_LOG(("locking\n")); - - sops[0].sem_num = 0; - sops[0].sem_op = 0; - sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT; - - sops[1].sem_num = 0; - sops[1].sem_op = 1; - sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT; - - sops[2].sem_num = 1; - sops[2].sem_op = 0; - sops[2].sem_flg = SEM_UNDO | IPC_NOWAIT; - - sops[3].sem_num = 1; - sops[3].sem_op = 1; - sops[3].sem_flg = SEM_UNDO | IPC_NOWAIT; - - return do_semop(id, sops, 4); -} - static bool mutex_is_locked(int id) { struct sembuf sops; @@ -326,23 +301,44 @@ static bool mutex_is_locked(int id) int lock_dss(char *config_file) { - int ret, key = get_key_or_die(config_file); + int ret, id; + struct sembuf sops[4]; + key_t key = get_key_or_die(config_file); ret = mutex_get(key, IPC_CREAT | 0600); if (ret < 0) return ret; - return mutex_lock(ret); + id = ret; + + sops[0].sem_num = 0; + sops[0].sem_op = 0; + sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT; + + sops[1].sem_num = 0; + sops[1].sem_op = 1; + sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT; + + sops[2].sem_num = 1; + sops[2].sem_op = 0; + sops[2].sem_flg = SEM_UNDO | IPC_NOWAIT; + + sops[3].sem_num = 1; + sops[3].sem_op = 1; + sops[3].sem_flg = SEM_UNDO | IPC_NOWAIT; + + return do_semop(id, sops, 4); } int get_dss_pid(char *config_file, pid_t *pid) { - int ret, semid, key = get_key_or_die(config_file); + int ret, semid; + key_t key = get_key_or_die(config_file); if (pid) *pid = 0; ret = mutex_get(key, 0); if (ret < 0) - return ret; + return ret == -ERRNO_TO_DSS_ERROR(ENOENT)? -E_NOT_RUNNING : ret; semid = ret; ret = semctl(semid, 1, GETPID); if (ret < 0) diff --git a/mklogo b/mklogo new file mode 100755 index 0000000..54c8c95 --- /dev/null +++ b/mklogo @@ -0,0 +1,100 @@ +#!/bin/bash + +# Script for Image Magick that writes the dss logo in png format +# to stdout. + +set -u + +width=320 +height=110 +border=5 +radius1=4 +radius2=5 +circle_y=90 +arrow_y=70 +text_x=128 +text_y=55 +pointsize=36 + +declare -a circle arrow rectangle text + +make_circles() +{ + local inner='stroke black fill red circle' + local outer='stroke black fill black circle' + local num=1 + local idx=0 + local y=$circle_y + local step x0 i j idx num + + for ((i = 0; i < 4; i++)); do + step=$(((width / 4 + num) / (num + 1))) + x0=$((border + width / 4 * i)) + for ((j = 1; j <= $num; j++)); do + x=$((x0 + j * $step)) + circle[$idx]='-draw'; let idx++ + circle[$idx]="$outer $x,$y,$((x + radius2)),$y"; let idx++ + circle[$idx]='-draw'; let idx++ + circle[$idx]="$inner $x,$y,$((x + radius1)),$y"; let idx++ + done + num=$((num * 2)) + done + #echo "${circle[@]}"; exit 0 +} + +make_arrow() +{ + local arrow_head='l -15,-5 +5,+5 -5,+5 +15,-5 z' + local idx=0 + local x0 x1 y + + arrow[$idx]='-draw'; let idx++ + x0=$((3 * border)); x1=$((width - 2 * border)) + y=$arrow_y + arrow[$idx]="stroke white line $x0,$y $x1,$y"; let idx++ + arrow[$idx]='-draw'; let idx++ + x0=$((width - 2 * border)) + arrow[$idx]="stroke white fill white path 'M $x0,$y $arrow_head'" + #echo "${arrow[@]}"; exit 0 +} + +make_rectangles() +{ + local idx=0 + local x x0 x1 y y0 y1 i red_green color + + rectangle[$idx]='-draw'; let idx++ + x=$((width + 2 * border)) + y=$((height + 2 * border)) + rectangle[$idx]="stroke yellow fill yellow rectangle 0,0 $x,$y"; let idx++ + for ((i = 0; i < 4; i++)); do + rectangle[$idx]='-draw'; let idx++ + red_green="$(printf '%02x' $(((3 - i) * 60)))" + color="#${red_green}${red_green}ff" + x0=$((border + i * width / 4)); x1=$((x0 + width / 4 - 1)) + y0=$border; y1=$((y0 + height)) + rectangle[$idx]="stroke $color fill $color rectangle $x0,$y0 $x1,$y1" + let idx++ + done + #echo "${rectangle[@]}"; exit 0 +} + +make_text() +{ + text=(-pointsize $pointsize -draw \ + "fill white text $text_x,$text_y DSS") + #echo "${text[@]}"; exit 0 +} + +make_rectangles +make_arrow +make_circles +make_text + +convert -size $((width + 2 * border))x$((height + 2 * border)) \ + -background none xc: \ + "${rectangle[@]}" \ + "${arrow[@]}" \ + "${circle[@]}" \ + "${text[@]}" \ + png:-