dss
dss.1
dss.1.html
-dss.png
index.html
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
%.o: %.c Makefile
$(CC) -c $(DSS_CPPFLAGS) $(CPPFLAGS) $(DSS_CFLAGS) $(CFLAGS) $<
-dss.png: mklogo
- ./mklogo > $@
-
%.lsg.h: %.suite
lopsubgen --gen-h=$@ < $<
%.lsg.c: %.suite
groff -m man -Thtml -P -l -P -r $< | sed -e '1,/^<body>/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
+------------------
+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)
------------------
/* become session leader */
if (setsid() < 0)
goto err;
- umask(0);
null = open("/dev/null", O_RDWR);
if (null < 0)
goto err;
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;
ref = s;
continue;
}
- DSS_INFO_LOG(("oldest removable snapshot: %s\n", s->name));
return s;
}
assert(ref);
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;
struct snapshot_list sl;
struct snapshot *victim;
struct timeval now;
- const char *why;
+ char *why;
int low_disk_space;
ret = disk_space_low(NULL);
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)
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);
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;
close(fd);
out:
free(config_file);
- if (ret < 0)
- DSS_EMERG_LOG(("%s\n", dss_strerror(-ret)));
return ret;
}
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? "/" : ""
);
}
}
{
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 *));
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)
struct snapshot_list sl;
struct snapshot *victim;
struct disk_space ds;
- const char *why;
+ char *why;
+ bool try_hard;
lock_dss_or_die();
- ret = get_disk_space(".", &ds);
+ ret = change_to_dest_dir();
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);
- why = "outdated";
- victim = find_outdated_snapshot(&sl);
- if (victim)
- goto rm;
- why = "redundant";
- victim = find_redundant_snapshot(&sl);
- if (victim)
- goto rm;
- ret = 0;
- goto out;
-rm:
+ victim = find_removable_snapshot(&sl, try_hard, &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;
- if (snapshot_removal_status != HS_SUCCESS)
- goto out;
+ goto free_why;
+ 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;
- ret = 1;
-out:
+free_why:
+ free(why);
+free_sl:
free_snapshot_list(&sl);
return ret;
}
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;
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;
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]
+ [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]
--- /dev/null
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="154"
+ height="100"
+>
+ <defs>
+ <rect id="rect"
+ stroke-width="0"
+ x="0"
+ y="0"
+ width="37"
+ height="94"
+ />
+ </defs>
+ <defs>
+ <circle id="circle"
+ stroke-width="1"
+ stroke="black"
+ fill="#a22"
+ cx="0"
+ cy="0"
+ r="4"
+ />
+ </defs>
+ <marker
+ id="arrow"
+ stroke="white"
+ fill="white"
+ viewBox="0 0 10 10" refX="5" refY="5"
+ markerWidth="4" markerHeight="4"
+ orient="auto-start-reverse">
+ <path d="M 0 0 L 10 5 L 0 10 z" />
+ </marker>
+ <use fill="#bbf" xlink:href="#rect" transform="translate(0, 0)"/>
+ <use fill="#99f" xlink:href="#rect" transform="translate(36, 0)"/>
+ <use fill="#44f" xlink:href="#rect" transform="translate(72, 0)"/>
+ <use fill="#11f" xlink:href="#rect" transform="translate(108, 0)"/>
+
+ <text x="47" y="40" stroke="white" fill="white" font-size="25">DSS</text>
+
+ <line
+ x1="7" y1="50" x2="127" y2="50" stroke-width="3"
+ stroke="white"
+ marker-end="url(#arrow)"
+ />
+ <use xlink:href="#circle" x="18" y="77"/>
+
+ <use xlink:href="#circle" x="48" y="77"/>
+ <use xlink:href="#circle" x="60" y="77"/>
+
+ <use xlink:href="#circle" x="77" y="77"/>
+ <use xlink:href="#circle" x="86" y="77"/>
+ <use xlink:href="#circle" x="95" y="77"/>
+ <use xlink:href="#circle" x="104" y="77"/>
+
+ <use xlink:href="#circle" x="113" y="70"/>
+ <use xlink:href="#circle" x="122" y="70"/>
+ <use xlink:href="#circle" x="131" y="70"/>
+ <use xlink:href="#circle" x="140" y="70"/>
+ <use xlink:href="#circle" x="114" y="85"/>
+ <use xlink:href="#circle" x="122" y="85"/>
+ <use xlink:href="#circle" x="130" y="85"/>
+ <use xlink:href="#circle" x="138" y="85"/>
+</svg>
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"), \
{
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);
<table>
<tr>
<td>
- <IMG SRC="dss.png" alt="dss">
+ <img src="dss.svg" alt="dss">
</td>
<td>
<h1>The dyadic snapshot scheduler</h1>
+++ /dev/null
-#!/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:-
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)
{
*
* \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 (;;) {
__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. */