18 #include "gcc-compat.h"
27 struct gengetopt_args_info conf
;
28 char *dss_error_txt
= NULL
;
33 /* a litte cpp magic helps to DRY */
39 #define COMMAND(x) int com_ ##x(int, char * const * const);
42 #define COMMAND(x) if (conf.x ##_given) return com_ ##x(argc, argv);
43 int call_command_handler(int argc
, char * const * const argv
)
46 DSS_EMERG_LOG("BUG: did not find command handler\n");
53 * complete, not being deleted: 1204565370-1204565371.Sun_Mar_02_2008_14_33-Sun_Mar_02_2008_14_43
54 * complete, being deleted: 1204565370-1204565371.being_deleted
55 * incomplete, not being deleted: 1204565370-incomplete
56 * incomplete, being deleted: 1204565370-incomplete.being_deleted
58 enum snapshot_status_flags
{
65 int64_t creation_time
;
66 int64_t completion_time
;
67 enum snapshot_status_flags flags
;
71 int is_snapshot(const char *dirname
, int64_t now
, struct snapshot
*s
)
74 char *dash
, *dot
, *tmp
;
78 dash
= strchr(dirname
, '-');
79 if (!dash
|| !dash
[1] || dash
== dirname
)
81 for (i
= 0; dirname
[i
] != '-'; i
++)
82 if (!isdigit(dirname
[i
]))
84 tmp
= dss_strdup(dirname
);
86 ret
= dss_atoi64(tmp
, &num
);
95 s
->creation_time
= num
;
96 //DSS_DEBUG_LOG("%s start time: %lli\n", dirname, (long long)s->creation_time);
97 s
->interval
= (long long) ((now
- s
->creation_time
)
98 / conf
.unit_interval_arg
/ 24 / 3600);
99 if (!strcmp(dash
+ 1, "incomplete")) {
100 s
->completion_time
= -1;
101 s
->flags
= 0; /* neither complete, nor being deleted */
104 if (!strcmp(dash
+ 1, "incomplete.being_deleted")) {
105 s
->completion_time
= -1;
106 s
->flags
= SS_BEING_DELETED
; /* mot cpmplete, being deleted */
110 dot
= strchr(tmp
, '.');
111 if (!dot
|| !dot
[1] || dot
== tmp
)
113 for (i
= 0; tmp
[i
] != '.'; i
++)
114 if (!isdigit(tmp
[i
]))
116 tmp
= dss_strdup(dash
+ 1);
118 ret
= dss_atoi64(tmp
, &num
);
126 s
->completion_time
= num
;
127 s
->flags
= SS_COMPLETE
;
128 if (strcmp(dot
+ 1, "being_deleted"))
129 s
->flags
|= SS_BEING_DELETED
;
131 s
->name
= dss_strdup(dirname
);
135 int64_t get_current_time(void)
139 DSS_DEBUG_LOG("now: %lli\n", (long long) now
);
143 char *incomplete_name(int64_t start
)
145 return make_message("%lli-incomplete", (long long)start
);
148 char *being_deleted_name(struct snapshot
*s
)
150 if (s
->flags
& SS_COMPLETE
)
151 return make_message("%lli-%lli.being_deleted",
152 (long long)s
->creation_time
,
153 (long long)s
->completion_time
);
154 return make_message("%lli-incomplete.being_deleted",
155 (long long)s
->creation_time
);
158 int complete_name(int64_t start
, int64_t end
, char **result
)
160 struct tm start_tm
, end_tm
;
161 time_t *start_seconds
= (time_t *) (uint64_t *)&start
; /* STFU, gcc */
162 time_t *end_seconds
= (time_t *) (uint64_t *)&end
; /* STFU, gcc */
163 char start_str
[200], end_str
[200];
165 if (!localtime_r(start_seconds
, &start_tm
)) {
166 make_err_msg("%lli", (long long)start
);
169 if (!localtime_r(end_seconds
, &end_tm
)) {
170 make_err_msg("%lli", (long long)end
);
173 if (!strftime(start_str
, sizeof(start_str
), "%a_%b_%d_%Y_%H_%M_%S", &start_tm
)) {
174 make_err_msg("%lli", (long long)start
);
177 if (!strftime(end_str
, sizeof(end_str
), "%a_%b_%d_%Y_%H_%M_%S", &end_tm
)) {
178 make_err_msg("%lli", (long long)end
);
181 *result
= make_message("%lli-%lli.%s-%s", (long long) start
, (long long) end
,
186 struct snapshot_list
{
188 unsigned num_snapshots
;
190 struct snapshot
**snapshots
;
192 * Array of size num_intervals + 1
194 * It contains the number of snapshots in each interval. interval_count[num_intervals]
195 * is the number of snapshots which belong to any interval greater than num_intervals.
197 unsigned *interval_count
;
200 #define FOR_EACH_SNAPSHOT(s, i, sl) \
201 for ((i) = 0; (i) < (sl)->num_snapshots && ((s) = (sl)->snapshots[(i)]); (i)++)
205 #define NUM_COMPARE(x, y) ((int)((x) < (y)) - (int)((x) > (y)))
207 static int compare_snapshots(const void *a
, const void *b
)
209 struct snapshot
*s1
= *(struct snapshot
**)a
;
210 struct snapshot
*s2
= *(struct snapshot
**)b
;
211 return NUM_COMPARE(s2
->creation_time
, s1
->creation_time
);
214 /** Compute the minimum of \a a and \a b. */
215 #define DSS_MIN(a,b) ((a) < (b) ? (a) : (b))
217 int add_snapshot(const char *dirname
, void *private)
219 struct snapshot_list
*sl
= private;
221 int ret
= is_snapshot(dirname
, sl
->now
, &s
);
225 if (sl
->num_snapshots
>= sl
->array_size
) {
226 sl
->array_size
= 2 * sl
->array_size
+ 1;
227 sl
->snapshots
= dss_realloc(sl
->snapshots
,
228 sl
->array_size
* sizeof(struct snapshot
*));
230 sl
->snapshots
[sl
->num_snapshots
] = dss_malloc(sizeof(struct snapshot
));
231 *(sl
->snapshots
[sl
->num_snapshots
]) = s
;
232 sl
->interval_count
[DSS_MIN(s
.interval
, conf
.num_intervals_arg
)]++;
237 void get_snapshot_list(struct snapshot_list
*sl
)
239 sl
->now
= get_current_time();
240 sl
->num_snapshots
= 0;
242 sl
->snapshots
= NULL
;
243 sl
->interval_count
= dss_calloc((conf
.num_intervals_arg
+ 1) * sizeof(unsigned));
244 for_each_subdir(add_snapshot
, sl
);
245 qsort(sl
->snapshots
, sl
->num_snapshots
, sizeof(struct snapshot
*),
249 void free_snapshot_list(struct snapshot_list
*sl
)
254 FOR_EACH_SNAPSHOT(s
, i
, sl
) {
258 free(sl
->interval_count
);
263 * Print a log message about the exit status of a child.
265 void log_termination_msg(pid_t pid
, int status
)
267 if (WIFEXITED(status
))
268 DSS_INFO_LOG("child %i exited. Exit status: %i\n", (int)pid
,
269 WEXITSTATUS(status
));
270 else if (WIFSIGNALED(status
))
271 DSS_NOTICE_LOG("child %i was killed by signal %i\n", (int)pid
,
274 DSS_WARNING_LOG("child %i terminated abormally\n", (int)pid
);
277 int wait_for_process(pid_t pid
, int *status
)
281 DSS_DEBUG_LOG("Waiting for process %d to terminate\n", (int)pid
);
283 ret
= waitpid(pid
, status
, 0);
284 if (ret
>= 0 || errno
!= EINTR
)
288 ret
= -ERRNO_TO_DSS_ERROR(errno
);
289 make_err_msg("failed to wait for process %d", (int)pid
);
291 log_termination_msg(pid
, *status
);
295 int remove_snapshot(struct snapshot
*s
, pid_t
*pid
)
297 int fds
[3] = {0, 0, 0};
298 char *new_name
= being_deleted_name(s
);
299 int ret
= dss_rename(s
->name
, new_name
);
300 char *argv
[] = {"rm", "-rf", new_name
, NULL
};
304 DSS_NOTICE_LOG("removing %s (interval = %i)\n", s
->name
, s
->interval
);
305 ret
= dss_exec(pid
, argv
[0], argv
, fds
);
311 int remove_redundant_snapshot(struct snapshot_list
*sl
,
312 int dry_run
, pid_t
*pid
)
314 int ret
, i
, interval
;
316 unsigned missing
= 0;
318 DSS_INFO_LOG("looking for intervals containing too many snapshots\n");
319 for (interval
= conf
.num_intervals_arg
- 1; interval
>= 0; interval
--) {
320 unsigned keep
= 1<<(conf
.num_intervals_arg
- interval
- 1);
321 unsigned num
= sl
->interval_count
[interval
];
322 struct snapshot
*victim
= NULL
, *prev
= NULL
;
323 int64_t score
= LONG_MAX
;
326 missing
+= keep
- num
;
327 DSS_DEBUG_LOG("interval %i: keep: %u, have: %u, missing: %u\n",
328 interval
, keep
, num
, missing
);
329 if (keep
+ missing
>= num
)
331 /* redundant snapshot in this interval, pick snapshot with lowest score */
332 FOR_EACH_SNAPSHOT(s
, i
, sl
) {
335 DSS_DEBUG_LOG("checking %s\n", s
->name
);
336 if (s
->interval
> interval
) {
340 if (s
->interval
< interval
)
348 /* check if s is a better victim */
349 this_score
= s
->creation_time
- prev
->creation_time
;
350 assert(this_score
>= 0);
351 DSS_DEBUG_LOG("%s: score %lli\n", s
->name
, (long long)score
);
352 if (this_score
< score
) {
360 printf("%s would be removed (interval = %i)\n",
361 victim
->name
, victim
->interval
);
364 ret
= remove_snapshot(victim
, pid
);
365 return ret
< 0? ret
: 1;
370 int remove_old_snapshot(struct snapshot_list
*sl
, int dry_run
, pid_t
*pid
)
375 DSS_INFO_LOG("looking for snapshots belonging to intervals greater than %d\n",
376 conf
.num_intervals_arg
);
377 FOR_EACH_SNAPSHOT(s
, i
, sl
) {
378 if (s
->interval
<= conf
.num_intervals_arg
)
381 printf("%s would be removed (interval = %i)\n",
382 s
->name
, s
->interval
);
385 ret
= remove_snapshot(s
, pid
);
393 int wait_for_rm_process(pid_t pid
)
395 int status
, es
, ret
= wait_for_process(pid
, &status
);
398 if (!WIFEXITED(status
)) {
399 ret
= E_INVOLUNTARY_EXIT
;
400 make_err_msg("rm process %d died involuntary", (int)pid
);
403 es
= WEXITSTATUS(status
);
405 ret
= -E_BAD_EXIT_CODE
;
406 make_err_msg("rm process %d returned %d", (int)pid
, es
);
412 int com_run(int argc
, char * const * argv
)
417 int com_prune(int argc
, char * const * argv
)
419 int ret
, dry_run
= 0;
420 struct snapshot_list sl
;
424 make_err_msg("too many arguments");
428 if (strcmp(argv
[1], "-d")) {
429 make_err_msg("%s", argv
[1]);
435 get_snapshot_list(&sl
);
436 ret
= remove_old_snapshot(&sl
, dry_run
, &pid
);
437 free_snapshot_list(&sl
);
442 ret
= wait_for_rm_process(pid
);
447 get_snapshot_list(&sl
);
448 ret
= remove_redundant_snapshot(&sl
, dry_run
, &pid
);
449 free_snapshot_list(&sl
);
454 ret
= wait_for_rm_process(pid
);
463 struct newest_snapshot_data
{
465 int64_t newest_creation_time
;
469 int get_newest_complete(const char *dirname
, void *private)
471 struct newest_snapshot_data
*nsd
= private;
473 int ret
= is_snapshot(dirname
, nsd
->now
, &s
);
477 if (s
.creation_time
< nsd
->newest_creation_time
)
479 nsd
->newest_creation_time
= s
.creation_time
;
480 free(nsd
->newest_name
);
481 nsd
->newest_name
= s
.name
;
485 __malloc
char *name_of_newest_complete_snapshot(void)
487 struct newest_snapshot_data nsd
= {
488 .now
= get_current_time(),
489 .newest_creation_time
= -1
491 for_each_subdir(get_newest_complete
, &nsd
);
492 return nsd
.newest_name
;
495 void create_rsync_argv(char ***argv
, int64_t *num
)
497 char *logname
, *newest
= name_of_newest_complete_snapshot();
500 *argv
= dss_malloc((15 + conf
.rsync_option_given
) * sizeof(char *));
501 (*argv
)[i
++] = dss_strdup("rsync");
502 (*argv
)[i
++] = dss_strdup("-aq");
503 (*argv
)[i
++] = dss_strdup("--delete");
504 for (j
= 0; j
< conf
.rsync_option_given
; j
++)
505 (*argv
)[i
++] = dss_strdup(conf
.rsync_option_arg
[j
]);
507 DSS_INFO_LOG("using %s as reference snapshot\n", newest
);
508 (*argv
)[i
++] = make_message("--link-dest=../%s", newest
);
511 DSS_INFO_LOG("no previous snapshot found");
512 if (conf
.exclude_patterns_given
) {
513 (*argv
)[i
++] = dss_strdup("--exclude-from");
514 (*argv
)[i
++] = dss_strdup(conf
.exclude_patterns_arg
);
517 logname
= dss_logname();
518 if (conf
.remote_user_given
&& !strcmp(conf
.remote_user_arg
, logname
))
519 (*argv
)[i
++] = dss_strdup(conf
.source_dir_arg
);
521 (*argv
)[i
++] = make_message("%s@%s:%s/", conf
.remote_user_given
?
522 conf
.remote_user_arg
: logname
,
523 conf
.remote_host_arg
, conf
.source_dir_arg
);
525 *num
= get_current_time();
526 (*argv
)[i
++] = incomplete_name(*num
);
528 for (j
= 0; j
< i
; j
++)
529 DSS_DEBUG_LOG("argv[%d] = %s\n", j
, (*argv
)[j
]);
532 void free_rsync_argv(char **argv
)
535 for (i
= 0; argv
[i
]; i
++)
540 int create_snapshot(char **argv
, pid_t
*pid
)
542 int fds
[3] = {0, 0, 0};
544 return dss_exec(pid
, argv
[0], argv
, fds
);
547 int rename_incomplete_snapshot(int64_t start
)
549 char *old_name
, *new_name
;
552 ret
= complete_name(start
, get_current_time(), &new_name
);
555 old_name
= incomplete_name(start
);
556 ret
= dss_rename(old_name
, new_name
);
558 DSS_NOTICE_LOG("%s -> %s\n", old_name
, new_name
);
564 int com_create(int argc
, __a_unused
char * const * argv
)
568 int64_t snapshot_num
;
573 make_err_msg("create: no args expected, %d given", argc
- 1);
576 create_rsync_argv(&rsync_argv
, &snapshot_num
);
577 DSS_NOTICE_LOG("creating snapshot %lli\n", (long long)snapshot_num
);
578 ret
= create_snapshot(rsync_argv
, &pid
);
581 ret
= wait_for_process(pid
, &status
);
584 if (!WIFEXITED(status
)) {
585 ret
= E_INVOLUNTARY_EXIT
;
586 make_err_msg("rsync process %d died involuntary", (int)pid
);
589 es
= WEXITSTATUS(status
);
590 if (es
!= 0 && es
!= 23 && es
!= 24) {
591 ret
= -E_BAD_EXIT_CODE
;
592 make_err_msg("rsync process %d returned %d", (int)pid
, es
);
595 ret
= rename_incomplete_snapshot(snapshot_num
);
597 free_rsync_argv(rsync_argv
);
601 int com_ls(int argc
, __a_unused
char * const * argv
)
604 struct snapshot_list sl
;
608 make_err_msg("ls: no args expected, %d given", argc
- 1);
611 get_snapshot_list(&sl
);
612 FOR_EACH_SNAPSHOT(s
, i
, &sl
)
613 printf("%u\t%s\n", s
->interval
, s
->name
);
614 free_snapshot_list(&sl
);
618 /* TODO: Unlink pid file */
619 __noreturn
void clean_exit(int status
)
626 __printf_2_3
void dss_log(int ll
, const char* fmt
,...)
629 if (ll
< conf
.loglevel_arg
)
632 vfprintf(stderr
, fmt
, argp
);
636 int read_config_file(void)
642 if (conf
.config_file_given
)
643 config_file
= dss_strdup(conf
.config_file_arg
);
645 char *home
= get_homedir();
646 config_file
= make_message("%s/.dssrc", home
);
649 ret
= stat(config_file
, &statbuf
);
650 if (ret
&& conf
.config_file_given
) {
651 ret
= -ERRNO_TO_DSS_ERROR(errno
);
652 make_err_msg("failed to stat config file %s", config_file
);
656 struct cmdline_parser_params params
= {
662 cmdline_parser_config_file(config_file
, &conf
, ¶ms
);
664 if (!conf
.source_dir_given
|| !conf
.dest_dir_given
) {
666 make_err_msg("you need to specify both source_dir and dest_dir");
675 int check_config(void)
677 if (conf
.unit_interval_arg
<= 0) {
678 make_err_msg("bad unit interval: %i", conf
.unit_interval_arg
);
679 return -E_INVALID_NUMBER
;
681 DSS_DEBUG_LOG("unit interval: %i day(s)\n", conf
.unit_interval_arg
);
682 if (conf
.num_intervals_arg
<= 0) {
683 make_err_msg("bad number of intervals %i", conf
.num_intervals_arg
);
684 return -E_INVALID_NUMBER
;
686 DSS_DEBUG_LOG("number of intervals: %i\n", conf
.num_intervals_arg
);
690 int main(int argc
, char **argv
)
694 cmdline_parser(argc
, argv
, &conf
); /* aborts on errors */
695 if (conf
.inputs_num
) {
697 make_err_msg("additional non-options given");
700 ret
= read_config_file();
703 ret
= check_config();
706 ret
= dss_chdir(conf
.dest_dir_arg
);
709 ret
= call_command_handler(conf
.inputs_num
, conf
.inputs
);
712 log_err_msg(EMERG
, -ret
);
713 clean_exit(ret
>= 0? EXIT_SUCCESS
: EXIT_FAILURE
);