John Taylor's patch for implementing --time-limit and --stop-at, reworked to be simpler and more efficient by Wayne Davison. Do we need configure support for mktime()? To use this patch, run these commands for a successful build: patch -p1 = stop_at_utime) { + rprintf(FERROR, "run-time limit exceeded\n"); + exit_cleanup(RERR_TIMEOUT); + } + + if (!io_timeout || am_receiver) + return; + if (allow_keepalive && we_send_keepalive_messages) { /* This may put data into iobuf.msg w/o flushing. */ maybe_send_keepalive(t, False); diff --git a/options.c b/options.c --- a/options.c +++ b/options.c @@ -112,6 +112,7 @@ size_t bwlimit_writemax = 0; int ignore_existing = 0; int ignore_non_existing = 0; int need_messages_from_generator = 0; +time_t stop_at_utime = 0; int max_delete = INT_MIN; OFF_T max_size = 0; OFF_T min_size = 0; @@ -776,6 +777,8 @@ void usage(enum logcode F) rprintf(F," --password-file=FILE read daemon-access password from FILE\n"); rprintf(F," --list-only list the files instead of copying them\n"); rprintf(F," --bwlimit=RATE limit socket I/O bandwidth\n"); + rprintf(F," --stop-at=y-m-dTh:m Stop rsync at year-month-dayThour:minute\n"); + rprintf(F," --time-limit=MINS Stop rsync after MINS minutes have elapsed\n"); rprintf(F," --write-batch=FILE write a batched update to FILE\n"); rprintf(F," --only-write-batch=FILE like --write-batch but w/o updating destination\n"); rprintf(F," --read-batch=FILE read a batched update from FILE\n"); @@ -800,6 +803,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE, OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, + OPT_STOP_AT, OPT_TIME_LIMIT, OPT_SERVER, OPT_REFUSED_BASE = 9000}; static struct poptOption long_options[] = { @@ -989,6 +993,8 @@ static struct poptOption long_options[] = { {"no-timeout", 0, POPT_ARG_VAL, &io_timeout, 0, 0, 0 }, {"contimeout", 0, POPT_ARG_INT, &connect_timeout, 0, 0, 0 }, {"no-contimeout", 0, POPT_ARG_VAL, &connect_timeout, 0, 0, 0 }, + {"stop-at", 0, POPT_ARG_STRING, 0, OPT_STOP_AT, 0, 0 }, + {"time-limit", 0, POPT_ARG_STRING, 0, OPT_TIME_LIMIT, 0, 0 }, {"rsh", 'e', POPT_ARG_STRING, &shell_cmd, 0, 0, 0 }, {"rsync-path", 0, POPT_ARG_STRING, &rsync_path, 0, 0, 0 }, {"temp-dir", 'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 }, @@ -1764,6 +1770,36 @@ int parse_arguments(int *argc_p, const char ***argv_p) return 0; #endif + case OPT_STOP_AT: + arg = poptGetOptArg(pc); + if ((stop_at_utime = parse_time(arg)) == (time_t)-1) { + snprintf(err_buf, sizeof err_buf, + "invalid --stop-at format: %s\n", + arg); + rprintf(FERROR, "ERROR: %s", err_buf); + return 0; + } + if (stop_at_utime < time(NULL)) { + snprintf(err_buf, sizeof err_buf, + "--stop-at time is in the past: %s\n", + arg); + rprintf(FERROR, "ERROR: %s", err_buf); + return 0; + } + break; + + case OPT_TIME_LIMIT: + arg = poptGetOptArg(pc); + if ((stop_at_utime = atol(arg) * 60) <= 0) { + snprintf(err_buf, sizeof err_buf, + "invalid --time-limit value: %s\n", + arg); + rprintf(FERROR, "ERROR: %s", err_buf); + return 0; + } + stop_at_utime += time(NULL); + break; + default: /* A large opt value means that set_refuse_options() * turned this option off. */ @@ -2480,6 +2516,15 @@ void server_options(char **args, int *argc_p) args[ac++] = arg; } + if (stop_at_utime) { + long mins = (stop_at_utime - time(NULL)) / 60; + if (mins <= 0) + mins = 1; + if (asprintf(&arg, "--time-limit=%ld", mins) < 0) + goto oom; + args[ac++] = arg; + } + if (backup_dir) { args[ac++] = "--backup-dir"; args[ac++] = backup_dir; diff --git a/rsync.yo b/rsync.yo --- a/rsync.yo +++ b/rsync.yo @@ -431,6 +431,8 @@ to the detailed description below for a complete description. verb( --password-file=FILE read daemon-access password from FILE --list-only list the files instead of copying them --bwlimit=RATE limit socket I/O bandwidth + --stop-at=y-m-dTh:m Stop rsync at year-month-dayThour:minute + --time-limit=MINS Stop rsync after MINS minutes have elapsed --write-batch=FILE write a batched update to FILE --only-write-batch=FILE like --write-batch but w/o updating dest --read-batch=FILE read a batched update from FILE @@ -2309,6 +2311,19 @@ files can show up as being rapidly sent when the data is quickly buffered, while other can show up as very slow when the flushing of the output buffer occurs. This may be fixed in a future version. +dit(bf(--stop-at=y-m-dTh:m)) This option allows you to specify at what +time to stop rsync, in year-month-dayThour:minute numeric format (e.g. +2004-12-31T23:59). You can specify a 2 or 4-digit year. You can also +leave off various items and the result will be the next possible time +that matches the specified data. For example, "1-30" specifies the next +January 30th (at midnight), "04:00" specifies the next 4am, "1" +specifies the next 1st of the month at midnight, and ":59" specifies the +next 59th minute after the hour. If you prefer, you may separate the +date numbers using slashes instead of dashes. + +dit(bf(--time-limit=MINS)) This option allows you to specify the maximum +number of minutes rsync will run for. + dit(bf(--write-batch=FILE)) Record a file that can later be applied to another identical destination with bf(--read-batch). See the "BATCH MODE" section for details, and also the bf(--only-write-batch) option. diff --git a/util.c b/util.c --- a/util.c +++ b/util.c @@ -123,6 +123,133 @@ NORETURN void overflow_exit(const char *str) exit_cleanup(RERR_MALLOC); } +/* Allow the user to specify a time in the format yyyy-mm-ddThh:mm while + * also allowing abbreviated data. For instance, if the time is omitted, + * it defaults to midnight. If the date is omitted, it defaults to the + * next possible date in the future with the specified time. Even the + * year or year-month can be omitted, again defaulting to the next date + * in the future that matches the specified information. A 2-digit year + * is also OK, as is using '/' instead of '-'. */ +time_t parse_time(const char *arg) +{ + const char *cp; + time_t val, now = time(NULL); + struct tm t, *today = localtime(&now); + int in_date, n; + + memset(&t, 0, sizeof t); + t.tm_year = t.tm_mon = t.tm_mday = -1; + t.tm_hour = t.tm_min = t.tm_isdst = -1; + cp = arg; + if (*cp == 'T' || *cp == 't' || *cp == ':') { + cp++; + in_date = 0; + } else + in_date = 1; + for ( ; ; cp++) { + if (!isDigit(cp)) + return -1; + + n = 0; + do { + n = n * 10 + *cp++ - '0'; + } while (isDigit(cp)); + + if (*cp == ':') + in_date = 0; + if (in_date) { + if (t.tm_year != -1) + return -1; + t.tm_year = t.tm_mon; + t.tm_mon = t.tm_mday; + t.tm_mday = n; + if (!*cp) + break; + if (*cp == 'T' || *cp == 't') { + if (!cp[1]) + break; + in_date = 0; + } else if (*cp != '-' && *cp != '/') + return -1; + continue; + } + if (t.tm_hour != -1) + return -1; + t.tm_hour = t.tm_min; + t.tm_min = n; + if (!*cp) + break; + if (*cp != ':') + return -1; + } + + in_date = 0; + if (t.tm_year < 0) { + t.tm_year = today->tm_year; + in_date = 1; + } else if (t.tm_year < 100) { + while (t.tm_year < today->tm_year) + t.tm_year += 100; + } else + t.tm_year -= 1900; + if (t.tm_mon < 0) { + t.tm_mon = today->tm_mon; + in_date = 2; + } else + t.tm_mon--; + if (t.tm_mday < 0) { + t.tm_mday = today->tm_mday; + in_date = 3; + } + + n = 0; + if (t.tm_min < 0) { + t.tm_hour = t.tm_min = 0; + } else if (t.tm_hour < 0) { + if (in_date != 3) + return -1; + in_date = 0; + t.tm_hour = today->tm_hour; + n = 60*60; + } + + if (t.tm_hour > 23 || t.tm_min > 59 + || t.tm_mon < 0 || t.tm_mon >= 12 + || t.tm_mday < 1 || t.tm_mday > 31 + || (val = mktime(&t)) == (time_t)-1) + return -1; + + if (val <= now && in_date) { + tweak_date: + switch (in_date) { + case 3: + t.tm_mday++; + break; + case 2: + if (++t.tm_mon == 12) + t.tm_mon = 0; + else + break; + case 1: + t.tm_year++; + break; + } + if ((val = mktime(&t)) == (time_t)-1) { + if (in_date == 3 && t.tm_mday > 28) { + t.tm_mday = 1; + in_date = 2; + goto tweak_date; + } + return -1; + } + } + if (n) { + while (val <= now) + val += n; + } + return val; +} + int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode) { #ifndef CAN_SET_SYMLINK_TIMES