From eefbddd33850c724d7b5a07b484b00d0b6821961 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sat, 19 Jan 2019 12:16:24 +0100 Subject: [PATCH 01/16] Redo the logo as svg. Scalable vector graphics are much nicer for web pages than bitmaps. The mklogo script can be removed and there is no more dependency on ImageMagick. What a deal. The new logo was made with vi. It is a bit smaller than the old logo but looks similar. --- .gitignore | 1 - Makefile | 5 +-- dss.svg | 65 ++++++++++++++++++++++++++++++++ index.html.in | 2 +- mklogo | 101 -------------------------------------------------- 5 files changed, 67 insertions(+), 107 deletions(-) create mode 100644 dss.svg delete mode 100755 mklogo diff --git a/.gitignore b/.gitignore index 1bb2a25..4aafb48 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ dss.lsg.* dss dss.1 dss.1.html -dss.png index.html diff --git a/Makefile b/Makefile index 2d0a6bb..ef00f50 100644 --- a/Makefile +++ b/Makefile @@ -38,9 +38,6 @@ dss: $(dss_objects) %.o: %.c Makefile $(CC) -c $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) $< -dss.png: mklogo - ./mklogo > $@ - %.lsg.h: %.suite lopsubgen --gen-h=$@ < $< %.lsg.c: %.suite @@ -52,7 +49,7 @@ dss.png: mklogo 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 *~ index.html dss.lsg.h dss.lsg.c + rm -f *.o dss dss.1 dss.1.html Makefile.deps *~ index.html dss.lsg.h dss.lsg.c ifneq ($(findstring strip, $(MAKECMDGOALS)),) strip_option := -s diff --git a/dss.svg b/dss.svg new file mode 100644 index 0000000..fb69bd7 --- /dev/null +++ b/dss.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + DSS + + + + + + + + + + + + + + + + + + + + + diff --git a/index.html.in b/index.html.in index 42606ca..b505761 100644 --- a/index.html.in +++ b/index.html.in @@ -12,7 +12,7 @@
- dss + dss

The dyadic snapshot scheduler

diff --git a/mklogo b/mklogo deleted file mode 100755 index d4705ba..0000000 --- a/mklogo +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 - -# 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:- -- 2.39.2 From 5874e6d96f97e2dc7bb0771408b298ae5f4e00ed Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 1 Sep 2019 19:29:39 +0200 Subject: [PATCH 02/16] prune: Fail gracefully if pre-rm hook fails. In this case wait_for_remove_process() returns non-negative and we miss to set the exit code, making the command appear to succeed even if the rm process has not been created. --- dss.c | 1 + 1 file changed, 1 insertion(+) diff --git a/dss.c b/dss.c index a219cfd..f91883a 100644 --- a/dss.c +++ b/dss.c @@ -1675,6 +1675,7 @@ rm: ret = wait_for_remove_process(); if (ret < 0) goto out; + ret = -E_HOOK_FAILED; if (snapshot_removal_status != HS_PRE_SUCCESS) goto out; } -- 2.39.2 From fa415abf954777fd0064fd5cbe222f34126bd8ed Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 10 Feb 2019 12:05:40 +0100 Subject: [PATCH 03/16] get_snapshot_list(): Add comment about sorting. Callers rely on the returned snapshot list being sorted by creation time, so let's document this fact. --- snap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap.c b/snap.c index 8de76d4..da95c1f 100644 --- a/snap.c +++ b/snap.c @@ -146,7 +146,7 @@ static int compare_snapshots(const void *a, const void *b) return NUM_COMPARE(s2->creation_time, s1->creation_time); } - +/* The returned snapshot list is sorted by creation time. */ void get_snapshot_list(struct snapshot_list *sl, int unit_interval, int num_intervals) { -- 2.39.2 From e761ab7ba49d906bbbeddabfeb95772b026c335c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 7 Feb 2019 19:05:24 +0100 Subject: [PATCH 04/16] prune: Print a message if there is nothing to prune. When the prune subcommand finds there are no snapshots to prune, the command stays silent, which is confusing. Make it print "nothing to prune" in this case. --- dss.c | 1 + 1 file changed, 1 insertion(+) diff --git a/dss.c b/dss.c index f91883a..537c243 100644 --- a/dss.c +++ b/dss.c @@ -1661,6 +1661,7 @@ static int com_prune(void) victim = find_redundant_snapshot(&sl); if (victim) goto rm; + dss_msg("nothing to prune\n"); ret = 0; goto out; rm: -- 2.39.2 From 4b924a3bd5b3dd3e5d28de86def51a25034291f1 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 10 Feb 2019 12:05:27 +0100 Subject: [PATCH 05/16] find_oldest_removable_snapshot(): Improve log message. The other three functions which return a candidate for removal print one debug message but not the name of the selected snapshot, so make find_oldest_removable_snapshot() follow this pattern too. --- dss.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dss.c b/dss.c index 537c243..db45449 100644 --- a/dss.c +++ b/dss.c @@ -651,6 +651,7 @@ static struct snapshot *find_oldest_removable_snapshot(struct snapshot_list *sl) int i, num_complete; struct snapshot *s, *ref = NULL; + DSS_DEBUG_LOG(("picking snapshot with earliest creation time\n")); num_complete = num_complete_snapshots(sl); if (num_complete <= OPT_UINT32_VAL(DSS, MIN_COMPLETE)) return NULL; @@ -661,7 +662,6 @@ static struct snapshot *find_oldest_removable_snapshot(struct snapshot_list *sl) ref = s; continue; } - DSS_INFO_LOG(("oldest removable snapshot: %s\n", s->name)); return s; } assert(ref); -- 2.39.2 From 8255344c3fe023bde18aa06e0f860752445d0735 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 1 Sep 2019 19:31:40 +0200 Subject: [PATCH 06/16] prune Simplify rm exit code logic. We can rely on the fact that wait_for_remove_process() sets snapshot_removal_status to the correct value on success. --- dss.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dss.c b/dss.c index db45449..126c366 100644 --- a/dss.c +++ b/dss.c @@ -1686,11 +1686,9 @@ rm: ret = wait_for_remove_process(); if (ret < 0) goto out; - if (snapshot_removal_status != HS_SUCCESS) - goto out; + assert(snapshot_removal_status == HS_SUCCESS); post_remove_hook(); - if (snapshot_removal_status != HS_POST_RUNNING) - goto out; + assert(snapshot_removal_status == HS_POST_RUNNING); ret = wait_for_remove_process(); if (ret < 0) goto out; -- 2.39.2 From e3f9c1dcc861be14fc9a3f33057a905047e9a926 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 7 Feb 2019 20:49:19 +0100 Subject: [PATCH 07/16] Factor out find_removable_snapshot(). Both the run and the prune subcommand contain code for picking a suitable snapshot to remove, with slightly different semantics. This is a preparatory patch for eliminating the differences between the two implementations. It introduces a new helper which can be shared. After this patch, only the run subcommand uses the helper. A subsequent patch will convert the second caller, com_prune(). --- dss.c | 96 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/dss.c b/dss.c index 126c366..b09fc96 100644 --- a/dss.c +++ b/dss.c @@ -669,6 +669,50 @@ static struct snapshot *find_oldest_removable_snapshot(struct snapshot_list *sl) return ref; } +/* returns NULL <==> *reason is set to NULL */ +static struct snapshot *find_removable_snapshot(struct snapshot_list *sl, + bool try_hard, char **reason) +{ + struct snapshot *victim; + + /* + * Don't remove anything if there is free space and we have fewer + * snapshots than configured, plus one. This way there is always one + * snapshot that can be recycled. + */ + if (!try_hard && sl->num_snapshots <= + 1 << OPT_UINT32_VAL(DSS, NUM_INTERVALS)) + goto nope; + victim = find_orphaned_snapshot(sl); + if (victim) { + *reason = make_message("orphaned"); + return victim; + } + victim = find_outdated_snapshot(sl); + if (victim) { + *reason = make_message("outdated"); + return victim; + } + if (!OPT_GIVEN(DSS, KEEP_REDUNDANT)) { + victim = find_redundant_snapshot(sl); + if (victim) { + *reason = make_message("redundant"); + return victim; + } + } + if (!try_hard) + goto nope; + DSS_WARNING_LOG(("nothing obvious to remove\n")); + victim = find_oldest_removable_snapshot(sl); + if (victim) { + *reason = make_message("oldest"); + return victim; + } +nope: + *reason = NULL; + return NULL; +} + static int rename_incomplete_snapshot(int64_t start) { char *old_name; @@ -701,7 +745,7 @@ static int try_to_free_disk_space(void) struct snapshot_list sl; struct snapshot *victim; struct timeval now; - const char *why; + char *why; int low_disk_space; ret = disk_space_low(NULL); @@ -712,55 +756,25 @@ static int try_to_free_disk_space(void) if (tv_diff(&next_removal_check, &now, NULL) > 0) return 0; if (!low_disk_space) { - if (OPT_GIVEN(DSS, KEEP_REDUNDANT)) - return 0; if (snapshot_creation_status != HS_READY) return 0; if (next_snapshot_is_due()) return 0; } - /* - * Idle and --keep_redundant not given, or low disk space. Look at - * existing snapshots. - */ + /* Idle or low disk space, look at existing snapshots. */ dss_get_snapshot_list(&sl); - ret = 0; - /* - * Don't remove anything if there is free space and we have fewer - * snapshots than configured, plus one. This way there is always one - * snapshot that can be recycled. - */ - if (!low_disk_space && sl.num_snapshots <= - 1 << OPT_UINT32_VAL(DSS, NUM_INTERVALS)) - goto out; - why = "outdated"; - victim = find_outdated_snapshot(&sl); - if (victim) - goto remove; - why = "redundant"; - victim = find_redundant_snapshot(&sl); - if (victim) - goto remove; - why = "orphaned"; - victim = find_orphaned_snapshot(&sl); + victim = find_removable_snapshot(&sl, low_disk_space, &why); + if (victim) { + pre_remove_hook(victim, why); + free(why); + } + free_snapshot_list(&sl); if (victim) - goto remove; - /* try harder only if disk space is low */ + return 1; if (!low_disk_space) - goto out; - DSS_WARNING_LOG(("disk space low and nothing obvious to remove\n")); - why = "oldest"; - victim = find_oldest_removable_snapshot(&sl); - if (victim) - goto remove; + return 0; DSS_CRIT_LOG(("uhuhu: disk space low and nothing to remove\n")); - ret = -ERRNO_TO_DSS_ERROR(ENOSPC); - goto out; -remove: - pre_remove_hook(victim, why); -out: - free_snapshot_list(&sl); - return ret; + return -ERRNO_TO_DSS_ERROR(ENOSPC); } static void post_create_hook(void) -- 2.39.2 From d4d72473957544283b6d426c3d4fa0b3f583da87 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 1 Sep 2019 19:34:54 +0200 Subject: [PATCH 08/16] Revamp com_prune(). The prune subcommand implements its own logic for picking the snapshot to remove. The algorithm is similar but not identical to how the run subcommand gets rid of snapshots. This patch eliminates this inconsistency by changing com_prune() to call the find_removable_snapshot() helper which was introduced in the previous commit. Since find_removable_snapshot() returns a dynamically allocated string via the "why" pointer, we have to introduce another label for freeing this memory. The patch also improves the help text of the prune subcommand slightly. --- dss.c | 39 ++++++++++++++++----------------------- dss.suite | 25 ++++++++++++++----------- err.h | 1 + 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/dss.c b/dss.c index b09fc96..e2ee7dd 100644 --- a/dss.c +++ b/dss.c @@ -1659,7 +1659,7 @@ static int com_prune(void) struct snapshot_list sl; struct snapshot *victim; struct disk_space ds; - const char *why; + char *why; lock_dss_or_die(); ret = get_disk_space(".", &ds); @@ -1667,47 +1667,40 @@ static int com_prune(void) return ret; log_disk_space(&ds); dss_get_snapshot_list(&sl); - why = "outdated"; - victim = find_outdated_snapshot(&sl); - if (victim) - goto rm; - why = "redundant"; - victim = find_redundant_snapshot(&sl); - if (victim) - goto rm; - dss_msg("nothing to prune\n"); - ret = 0; - goto out; -rm: + victim = find_removable_snapshot(&sl, disk_space_low(&ds), &why); + if (!victim) { + dss_msg("nothing to prune\n"); + ret = 0; + goto free_sl; + } if (OPT_GIVEN(DSS, DRY_RUN)) { - dss_msg("%s snapshot %s (interval = %i)\n", + dss_msg("picking %s snapshot %s (interval = %i)\n", why, victim->name, victim->interval); ret = 0; - goto out; + goto free_why; } pre_remove_hook(victim, why); if (snapshot_removal_status == HS_PRE_RUNNING) { ret = wait_for_remove_process(); if (ret < 0) - goto out; + goto free_why; ret = -E_HOOK_FAILED; if (snapshot_removal_status != HS_PRE_SUCCESS) - goto out; + goto free_why; } ret = exec_rm(); if (ret < 0) - goto out; + goto free_why; ret = wait_for_remove_process(); if (ret < 0) - goto out; + goto free_why; assert(snapshot_removal_status == HS_SUCCESS); post_remove_hook(); assert(snapshot_removal_status == HS_POST_RUNNING); ret = wait_for_remove_process(); - if (ret < 0) - goto out; - ret = 1; -out: +free_why: + free(why); +free_sl: free_snapshot_list(&sl); return ret; } diff --git a/dss.suite b/dss.suite index 16c7e58..2719827 100644 --- a/dss.suite +++ b/dss.suite @@ -450,18 +450,21 @@ caption = Subcommands snapshots. [/description] [subcommand prune] - purpose = remove redundant and outdated snapshots + purpose = remove 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. + A snapshot is said to be (a) outdated if its interval number is greater + or equal than the specified number of unit intervals, (b) redundant if + the interval it belongs to contains more than the configured number of + snapshots, and (c) orphaned if it is incomplete and not being created + or deleted. All other snapshots are called regular. + + Unless --dry-run is given, which just prints the snapshot that would be + removed, this subcommand gets rid of non-regular snapshots. At most + one snapshot is removed per invocation. If no such snapshot exists + and disk space is low, the subcommand also removes regular snapshots, + always picking the oldest one. + + The subcommand fails if there is another dss "run" process. [/description] [subcommand ls] purpose = print the list of all snapshots diff --git a/err.h b/err.h index 9505dfc..bd22554 100644 --- a/err.h +++ b/err.h @@ -44,6 +44,7 @@ static inline char *dss_strerror(int num) DSS_ERROR(INVALID_NUMBER, "invalid number"), \ DSS_ERROR(STRFTIME, "strftime() failed"), \ DSS_ERROR(LOCALTIME, "localtime() failed"), \ + DSS_ERROR(HOOK_FAILED, "hook failure"), \ DSS_ERROR(MOUNTPOINT, "destination directory is no mountpoint"), \ DSS_ERROR(NULL_OPEN, "can not open /dev/null"), \ DSS_ERROR(DUP_PIPE, "exec error: can not create pipe"), \ -- 2.39.2 From 71a8073e6f18ccc21880a475af8d864279a960b8 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 7 Feb 2019 23:05:34 +0100 Subject: [PATCH 09/16] Introduce prune --disk-space. The new option takes a mandatory argument of the set {check, high, low}. The latter two make the command act as if disk space was high/low without actually performing the check. This is mostly useful for debugging. --- dss.c | 17 ++++++++++++----- dss.suite | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/dss.c b/dss.c index e2ee7dd..4396e6e 100644 --- a/dss.c +++ b/dss.c @@ -1660,14 +1660,21 @@ static int com_prune(void) struct snapshot *victim; struct disk_space ds; char *why; + bool try_hard; lock_dss_or_die(); - ret = get_disk_space(".", &ds); - if (ret < 0) - return ret; - log_disk_space(&ds); + switch (OPT_UINT32_VAL(PRUNE, DISK_SPACE)) { + case FDS_LOW: try_hard = true; break; + case FDS_HIGH: try_hard = false; break; + default: + ret = get_disk_space(".", &ds); + if (ret < 0) + return ret; + log_disk_space(&ds); + try_hard = disk_space_low(&ds); + } dss_get_snapshot_list(&sl); - victim = find_removable_snapshot(&sl, disk_space_low(&ds), &why); + victim = find_removable_snapshot(&sl, try_hard, &why); if (!victim) { dss_msg("nothing to prune\n"); ret = 0; diff --git a/dss.suite b/dss.suite index 2719827..f9cf8af 100644 --- a/dss.suite +++ b/dss.suite @@ -466,6 +466,22 @@ caption = Subcommands The subcommand fails if there is another dss "run" process. [/description] + [option disk-space] + summary = act as if free disk space was high/low + arg_info = required_arg + arg_type = string + typestr = mode + values = { + FDS_CHECK = "check", + FDS_HIGH = "high", + FDS_LOW = "low" + } + default_val = check + [help] + By default, free disk space is checked and even regular snapshots + become candidates for removal if disk space is low. This option + overrides the result of the check. + [/help] [subcommand ls] purpose = print the list of all snapshots [description] -- 2.39.2 From c3ad4e36f7ee18ab6fab4595fa2728a47c1db761 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Wed, 6 Feb 2019 17:37:25 +0100 Subject: [PATCH 10/16] Simplify split_args(). Both callers pass the same value for the delimiters, so we can remove the last argument of the function. --- dss.c | 2 +- exec.c | 2 +- str.c | 15 ++++++++------- str.h | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/dss.c b/dss.c index a219cfd..217dfaf 100644 --- a/dss.c +++ b/dss.c @@ -1567,7 +1567,7 @@ static void exit_hook(int exit_code) { pid_t pid; char **argv, *tmp = dss_strdup(OPT_STRING_VAL(DSS, EXIT_HOOK)); - unsigned n = split_args(tmp, &argv, " \t"); + unsigned n = split_args(tmp, &argv); n++; argv = dss_realloc(argv, (n + 1) * sizeof(char *)); diff --git a/exec.c b/exec.c index 9e0565d..ece7ec2 100644 --- a/exec.c +++ b/exec.c @@ -56,7 +56,7 @@ void dss_exec_cmdline_pid(pid_t *pid, const char *cmdline) { char **argv, *tmp = dss_strdup(cmdline); - split_args(tmp, &argv, " \t"); + split_args(tmp, &argv); dss_exec(pid, argv[0], argv); free(argv); free(tmp); diff --git a/str.c b/str.c index 623def1..1377630 100644 --- a/str.c +++ b/str.c @@ -212,20 +212,21 @@ __must_check __malloc char *dss_logname(void) * * \param args The string to be split. * \param argv_ptr Pointer to the list of substrings. - * \param delim Delimiter. * - * This function modifies \a args by replacing each occurrence of \a delim by - * zero. A \p NULL-terminated array of pointers to char* is allocated dynamically - * and these pointers are initialized to point to the broken-up substrings - * within \a args. A pointer to this array is returned via \a argv_ptr. + * This function modifies the string given by the first argument by replacing + * all occurrences of space and '\t' characters by '\0'. A NULL-terminated + * array of pointers to char * is allocated dynamically, and these pointers are + * initialized to point to the broken-up substrings. A pointer to this array + * is returned via the last argument. * - * \return The number of substrings found in \a args. + * \return The number of substrings found. */ -unsigned split_args(char *args, char *** const argv_ptr, const char *delim) +unsigned split_args(char *args, char *** const argv_ptr) { char *p; char **argv; size_t n = 0, i, j; + const char delim[] = " \t"; p = args + strspn(args, delim); for (;;) { diff --git a/str.h b/str.h index b7f2635..7b121ef 100644 --- a/str.h +++ b/str.h @@ -8,7 +8,7 @@ __must_check __malloc char *dss_strdup(const char *s); __must_check __malloc char *get_homedir(void); int dss_atoi64(const char *str, int64_t *value); __must_check __malloc char *dss_logname(void); -unsigned split_args(char *args, char *** const argv_ptr, const char *delim); +unsigned split_args(char *args, char *** const argv_ptr); /** \cond LLONG_MAX and LLONG_LIN might not be defined. */ -- 2.39.2 From 2f2b61d159e93e3da97c21da3d644c2c549bf12c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Fri, 25 Oct 2019 13:29:50 +0200 Subject: [PATCH 11/16] Fix logic to append slash to the source directory. This bug was introduced recently in commit dd3f58c0bdf1 (create_rsync_argv(): Allocate correctly sized arg array) which redefined N. --- dss.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dss.c b/dss.c index 217dfaf..5f35435 100644 --- a/dss.c +++ b/dss.c @@ -1444,7 +1444,7 @@ static void create_rsync_argv(char ***argv, int64_t *num) OPT_STRING_VAL(DSS, REMOTE_USER) : logname, OPT_STRING_VAL(DSS, REMOTE_HOST), lls_string_val(j, OPT_RESULT(DSS, SOURCE_DIR)), - N == 1? "/" : "" + OPT_GIVEN(DSS, SOURCE_DIR) == 1? "/" : "" ); } } -- 2.39.2 From 0f8170444cf9e2bcc42b64bdcae3a38b7ac55e66 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Fri, 25 Oct 2019 13:34:49 +0200 Subject: [PATCH 12/16] daemon_init(): Do not set umask to zero. We don't need this because we actively want rsync to respect the file creation mask. This does not matter in the common case of a single source directory because the permission bits will be copied from the source directory anyway. However, for multiple source directories it does matter because with umask(0) the top level snapshot directory is created with mode 777. --- daemon.c | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon.c b/daemon.c index 709785b..ad73061 100644 --- a/daemon.c +++ b/daemon.c @@ -60,7 +60,6 @@ int daemon_init(void) /* become session leader */ if (setsid() < 0) goto err; - umask(0); null = open("/dev/null", O_RDWR); if (null < 0) goto err; -- 2.39.2 From 53920f69b9988918e3be610e432e619c996e3e99 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Fri, 8 Nov 2019 14:24:27 +0100 Subject: [PATCH 13/16] dss-1.0.1. --- Makefile | 2 +- NEWS | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ef00f50..a60a65b 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ INSTALL_PROGRAM ?= $(INSTALL) INSTALL_DATA ?= $(INSTALL) -m 644 MKDIR_P := mkdir -p -VERSION_STRING = 1.0.0 +VERSION_STRING = 1.0.1 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 diff --git a/NEWS b/NEWS index 947ba58..a973da7 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,19 @@ +------------------ +1.0.1 (2019-11-08) +------------------ +Several fixes and improvements have accumulated over the years, +so here is a new release. There are also some new features: + +- Support for multiple source directories. + +- The prune subcommand has been tweaked to use the same algorithm as + the run subcommand to decide which snapshot should be pruned. The + prune subcommand also gained the new --disk-space option. + +- When dss runs in daemon mode, it no longer sets the umask to zero. + +- The logo has been replaced by a slightly smaller one in svg format. + ------------------ 1.0.0 (2017-11-19) ------------------ -- 2.39.2 From bf0f03c00f6de518d01b6cb1e94c1d447e5cd3b1 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 17 May 2020 17:08:33 +0200 Subject: [PATCH 14/16] Avoid duplicate error message. If parse_config_file() encounters an error, it logs the error *and* returns the error code, which causes the error message to be logged again. Fix this by removing the log statement from parse_config_file(). --- dss.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/dss.c b/dss.c index f61f335..84b6751 100644 --- a/dss.c +++ b/dss.c @@ -1263,8 +1263,6 @@ close_fd: close(fd); out: free(config_file); - if (ret < 0) - DSS_EMERG_LOG(("%s\n", dss_strerror(-ret))); return ret; } -- 2.39.2 From cac14c7d96355a9ca02f69627f01ddebe699afcc Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Tue, 19 Sep 2023 16:31:49 +0200 Subject: [PATCH 15/16] Fix --config-file for relative paths. The dss lock works by first turning the given config file path argument into a canonical absolute path using dss_realpath(), then hashing this absolute path to obtain a key ID for semget(2). If the given path is relative, we have to compute the ID before changing to the destination directory because dss_realpath() needs to call stat(2) to detect symlinks, and this system call will fail if the current working directory has changed. This is currently not the case as we change to the destination directory early in check_config(). If dss_realpath() fails, we silently use the unmodified path argument for hashing to deal with the case that the default config does not exist. As a result, if relative paths are given, the key ID depends on whether or not change_to_dest_dir() was called. This is the case for the run subcommanmd, but not for the kill subcommand. Thus the kill subcommand does not work as expected if a relative path is given. Fix this by grabbing the lock before changing the working directory in all cases. --- dss.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/dss.c b/dss.c index 84b6751..0992ec6 100644 --- a/dss.c +++ b/dss.c @@ -1122,7 +1122,6 @@ static int change_to_dest_dir(void) static int check_config(void) { - int ret; uint32_t unit_interval = OPT_UINT32_VAL(DSS, UNIT_INTERVAL); uint32_t num_intervals = OPT_UINT32_VAL(DSS, NUM_INTERVALS); @@ -1147,9 +1146,6 @@ static int check_config(void) 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; @@ -1620,12 +1616,23 @@ static int com_run(void) DSS_ERROR_LOG(("pid %d\n", (int)pid)); return -E_ALREADY_RUNNING; } + /* + * Order is important here: Since daemon_init() forks, it would drop + * the lock if it had been acquired already. Changing the cwd before + * grabbing the lock causes stat(2) to fail in case a relative config + * file path was given, which results in a different key ID for + * locking. Therefore we must first daemonize, then lock, then change + * the cwd. + */ if (OPT_GIVEN(RUN, DAEMON)) { fd = daemon_init(); daemonized = true; logfile = open_log(OPT_STRING_VAL(RUN, LOGFILE)); } lock_dss_or_die(); + ret = change_to_dest_dir(); + if (ret < 0) + return ret; dump_dss_config("startup"); ret = install_sighandler(SIGHUP); if (ret < 0) @@ -1661,6 +1668,9 @@ static int com_prune(void) bool try_hard; lock_dss_or_die(); + ret = change_to_dest_dir(); + if (ret < 0) + return ret; switch (OPT_UINT32_VAL(PRUNE, DISK_SPACE)) { case FDS_LOW: try_hard = true; break; case FDS_HIGH: try_hard = false; break; @@ -1717,6 +1727,9 @@ static int com_create(void) char **rsync_argv; lock_dss_or_die(); + ret = change_to_dest_dir(); + if (ret < 0) + return ret; if (OPT_GIVEN(DSS, DRY_RUN)) { int i; char *msg = NULL; @@ -1762,11 +1775,14 @@ EXPORT_CMD_HANDLER(create); static int com_ls(void) { - int i; + int i, ret; struct snapshot_list sl; struct snapshot *s; int64_t now = get_current_time(); + ret = change_to_dest_dir(); + if (ret < 0) + return ret; dss_get_snapshot_list(&sl); FOR_EACH_SNAPSHOT(s, i, &sl) { int64_t d; -- 2.39.2 From 73e0fd24ed41db7882948ee4d7ff63119cf5a67d Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 25 Apr 2024 15:38:23 +0200 Subject: [PATCH 16/16] Support local make files. These additional targets are handy for site-local targets such as installing the web files or deploying the executable and the man page on a remote server. --- .gitignore | 1 + Makefile | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4aafb48..36dfcfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ Makefile.deps +Makefile.local *.o *.swp dss.lsg.* diff --git a/Makefile b/Makefile index a60a65b..c8242b4 100644 --- a/Makefile +++ b/Makefile @@ -71,3 +71,5 @@ index.html: dss.1.html index.html.in INSTALL README NEWS sed -e '1,/@INSTALL@/d' -e '/@MAN_PAGE@/,$$d' index.html.in >> $@ cat dss.1.html >> $@ sed -e '1,/@MAN_PAGE@/d' index.html.in >> $@ + +-include Makefile.local -- 2.39.2