From: Andre Noll Date: Fri, 6 May 2016 14:18:24 +0000 (+0200) Subject: Convert dss to lopsub. X-Git-Tag: v1.0.0~25^2~9 X-Git-Url: http://git.tuebingen.mpg.de/?p=dss.git;a=commitdiff_plain;h=3025388040c1521121255e5ae7ceabdcb1b1e421 Convert dss to lopsub. This commit ditches gengetopt for the command line and config file parsers in favor of the lopsub library. Hence from now on, lopsub must be installed in order to compile dss while gengetopt is no longer needed. The mutually exclusive gengetopt group options --create, --prune, --ls, --run, --kill and --reload are replaced by lopsub subcommands. However, the --reload and --kill options have been combined to the new "kill" subcommand which allows to send arbitrary signals to a running dss process. Due to the conversion, the syntax of the dss command changes slightly. For example, dss --run becomes dss run while dss -Rdc foo needs to be spelled as dss -c foo -- run -d so that -d is regarded as an option to the "run" subcommand rather than an option to dss. With lopsub each subcommand has its own command line and config file parser. Options to subcommands can be added to the configuration file like this: [run] daemon logfile=/var/log/dss.log As for the implementation, the bulk of the changes is the conversion of dss.ggo to the new dss.suite. The necessary adjustments to the code are relatively simple. In particular, only dss.c needs to be changed while all other .c files don't require any modifications. The examples in INSTALL are adjusted to the new syntax. The commit also drops support for Mac OS and Solaris, since lopsub is not supported on these platforms yet. --- diff --git a/.gitignore b/.gitignore index 9a6163d..977f1dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ Makefile.deps *.[oa] cmdline.[ch] +dss.lsg.* dss dss.1 dss.1.html diff --git a/INSTALL b/INSTALL index db3d1c5..f48784b 100644 --- a/INSTALL +++ b/INSTALL @@ -1,3 +1,9 @@ +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 @@ -6,21 +12,6 @@ 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`. -Note that [gnu -gengetopt](https://www.gnu.org/software/gengetopt/gengetopt.html) -is required to compile dss. - -Optionally, type - - make man - -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`. - -dss is known to compile on Linux, MacOS, Solaris, FreeBSD and -NetBSD. However, it is run-tested only on Linux. - Also make sure that [rsync](http://rsync.samba.org/) is installed on your system. Version 2.6.1 or newer is required. @@ -48,11 +39,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. diff --git a/Makefile b/Makefile index 8adb38a..42adf47 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -dss_objects := cmdline.o dss.o str.o file.o exec.o sig.o daemon.o df.o tv.o snap.o ipc.o +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 man: dss.1 @@ -24,28 +26,27 @@ Makefile.deps: $(wildcard *.c *.h) -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) $< + $(CC) -c -DVERSION_STRING='"$(VERSION_STRING)"' $(CFLAGS) $(DEBUG_CFLAGS) $< %.png: %.dia dia -e $@ -t png $< -cmdline.c cmdline.h: dss.ggo - gengetopt --conf-parser < $< - -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 + index.html: dss.1.html index.html.in INSTALL README NEWS sed -e '/@README@/,$$d' index.html.in > $@ 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 9417e26..d0dab0d 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); @@ -652,8 +744,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); @@ -667,8 +760,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); @@ -864,11 +957,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; @@ -905,7 +999,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 */ } @@ -959,85 +1053,154 @@ static int handle_sigchld(void) return -E_BUG; } -static int check_config(void) +static int change_to_dest_dir(void) +{ + const char *dd = OPT_STRING_VAL(DSS, DEST_DIR); + DSS_INFO_LOG(("changing cwd to %s\n", dd)); + return dss_chdir(dd); +} + +static int check_config(const struct lls_command *cmd) { - if (conf.unit_interval_arg <= 0) { - DSS_ERROR_LOG(("bad unit interval: %i\n", conf.unit_interval_arg)); + 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) +{ + 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, config_file_exists; + 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) @@ -1045,24 +1208,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) @@ -1104,11 +1274,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; } @@ -1168,7 +1339,7 @@ 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; dss_get_snapshot_list(&sl); @@ -1176,12 +1347,13 @@ static void create_rsync_argv(char ***argv, int64_t *num) 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]); + 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", @@ -1190,11 +1362,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); @@ -1311,15 +1485,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) @@ -1338,11 +1512,17 @@ static int com_run(void) { int ret; - 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; } + if (OPT_GIVEN(RUN, DAEMON)) { + 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; @@ -1353,6 +1533,7 @@ static int com_run(void) exit_hook(ret); return ret; } +EXPORT_CMD_HANDLER(run); static int com_prune(void) { @@ -1379,7 +1560,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; @@ -1412,6 +1593,7 @@ out: free_snapshot_list(&sl); return ret; } +EXPORT_CMD_HANDLER(prune); static int com_create(void) { @@ -1419,7 +1601,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); @@ -1460,6 +1642,7 @@ out: free_rsync_argv(rsync_argv); return ret; } +EXPORT_CMD_HANDLER(create); static int com_ls(void) { @@ -1477,6 +1660,7 @@ static int com_ls(void) free_snapshot_list(&sl); return 1; } +EXPORT_CMD_HANDLER(ls); static int setup_signal_handling(void) { @@ -1493,47 +1677,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.ggo b/dss.ggo deleted file mode 100644 index 48ec432..0000000 --- a/dss.ggo +++ /dev/null @@ -1,468 +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 "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..e7473cb --- /dev/null +++ b/dss.suite @@ -0,0 +1,476 @@ +[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 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..fc1ef19 100644 --- a/err.h +++ b/err.h @@ -56,7 +56,8 @@ 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(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