From: Wayne Davison Date: Mon, 13 Jul 2020 01:32:41 +0000 (-0700) Subject: Add the --stop-after & --stop-at options. X-Git-Tag: v3.2.3pre1~97 X-Git-Url: http://git.samba.org/?a=commitdiff_plain;h=af531cf787995f6a3bc381cd1da1988192e7ef59;hp=d495e343c0671de620193987ae9de3f02a611b4a;p=rsync.git Add the --stop-after & --stop-at options. --- diff --git a/NEWS.md b/NEWS.md index 9bd98bae..30a74128 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,6 +21,10 @@ other user/group names in the transfer (instead of assuming that both sides have the same id-0 names). + - Added the `--stop-after=MINS` and `--stop-at=DATE_TIME` options (with the + `--time-limit=MINS` option accepted as an alias for `--stop-after`). This + is an enhanced version of the time-limit patch from the patches repo. + - Added some compatibility code for HPE NonStop platforms. ### INTERNAL: diff --git a/configure.ac b/configure.ac index 69c8f933..fac166c8 100644 --- a/configure.ac +++ b/configure.ac @@ -822,7 +822,7 @@ AC_FUNC_UTIME_NULL AC_FUNC_ALLOCA AC_CHECK_FUNCS(waitpid wait4 getcwd chown chmod lchmod mknod mkfifo \ fchmod fstat ftruncate strchr readlink link utime utimes lutimes strftime \ - chflags getattrlist \ + chflags getattrlist mktime \ memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \ strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \ setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \ diff --git a/io.c b/io.c index ddd20fa8..1785f832 100644 --- a/io.c +++ b/io.c @@ -60,6 +60,7 @@ extern int preserve_hard_links; extern BOOL extra_flist_sending_enabled; extern BOOL flush_ok_after_signal; extern struct stats stats; +extern time_t stop_at_utime; extern struct file_list *cur_flist; #ifdef ICONV_OPTION extern int filesfrom_convert; @@ -785,9 +786,13 @@ static char *perform_io(size_t needed, int flags) if (msgs2stderr && DEBUG_GTE(IO, 2)) rprintf(FINFO, "[%s] recv=%ld\n", who_am_i(), (long)n); - if (io_timeout) { + if (io_timeout || stop_at_utime) { last_io_in = time(NULL); - if (flags & PIO_NEED_INPUT) + if (stop_at_utime && last_io_in >= stop_at_utime) { + rprintf(FERROR, "stopping at requested limit\n"); + exit_cleanup(RERR_TIMEOUT); + } + if (io_timeout && flags & PIO_NEED_INPUT) maybe_send_keepalive(last_io_in, 0); } stats.total_read += n; diff --git a/options.c b/options.c index 1e438fb1..9ed16405 100644 --- a/options.c +++ b/options.c @@ -119,6 +119,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 = -1; OFF_T min_size = -1; @@ -664,6 +665,11 @@ static void print_info_flags(enum logcode f) #endif "prealloc", +#ifndef HAVE_MKTIME + "no " +#endif + "stop-at", + "*Optimizations", #ifndef HAVE_SIMD @@ -779,6 +785,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, + OPT_STOP_AFTER, OPT_STOP_AT, OPT_REFUSED_BASE = 9000}; static struct poptOption long_options[] = { @@ -988,6 +995,9 @@ 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-after", 0, POPT_ARG_STRING, 0, OPT_STOP_AFTER, 0, 0 }, + {"time-limit", 0, POPT_ARG_STRING, 0, OPT_STOP_AFTER, 0, 0 }, /* earlier stop-after name */ + {"stop-at", 0, POPT_ARG_STRING, 0, OPT_STOP_AT, 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 }, @@ -1192,6 +1202,9 @@ static void set_refuse_options(void) #ifndef SUPPORT_HARD_LINKS parse_one_refuse_match(0, "link-dest", list_end); #endif +#ifndef HAVE_MKTIME + parse_one_refuse_match(0, "stop-at", list_end); +#endif #ifndef ICONV_OPTION parse_one_refuse_match(0, "iconv", list_end); #endif @@ -1326,6 +1339,148 @@ failure: return -1; } +#ifdef HAVE_MKTIME +/* 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 '-'. */ +static 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, old_mday, 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 == ':') { + in_date = *cp == ':' ? 0 : -1; + cp++; + } else + in_date = 1; + for ( ; ; cp++) { + if (!isDigit(cp)) + return (time_t)-1; + n = 0; + do { + n = n * 10 + *cp++ - '0'; + } while (isDigit(cp)); + if (*cp == ':') + in_date = 0; + if (in_date > 0) { + if (t.tm_year != -1) + return (time_t)-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 = -1; + } else if (*cp != '-' && *cp != '/') + return (time_t)-1; + continue; + } + if (t.tm_hour != -1) + return (time_t)-1; + t.tm_hour = t.tm_min; + t.tm_min = n; + if (!*cp) { + if (in_date < 0) + return (time_t)-1; + break; + } + if (*cp != ':') + return (time_t)-1; + in_date = 0; + } + + 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 (time_t)-1; + in_date = 0; + t.tm_hour = today->tm_hour; + n = 60*60; + } + + /* Note that mktime() might change a too-large tm_mday into the start of + * the following month which we need to undo in the following code! */ + old_mday = t.tm_mday; + 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 (time_t)-1; + + while (in_date && (val <= now || t.tm_mday < old_mday)) { + switch (in_date) { + case 3: + old_mday = ++t.tm_mday; + break; + case 2: + if (t.tm_mday < old_mday) + t.tm_mday = old_mday; /* The month already got bumped forward */ + else if (++t.tm_mon == 12) { + t.tm_mon = 0; + t.tm_year++; + } + break; + case 1: + if (t.tm_mday < old_mday) { + /* mon==1 mday==29 got bumped to mon==2 */ + if (t.tm_mon != 2 || old_mday != 29) + return (time_t)-1; + t.tm_mon = 1; + t.tm_mday = 29; + } + t.tm_year++; + break; + } + if ((val = mktime(&t)) == (time_t)-1) { + /* This code shouldn't be needed, as mktime() should auto-round to the next month. */ + if (in_date != 3 || t.tm_mday <= 28) + return (time_t)-1; + t.tm_mday = old_mday = 1; + in_date = 2; + } + } + if (n) { + while (val <= now) + val += n; + } + return val; +} +#endif + static void create_refuse_error(int which) { const char *msg; @@ -1892,6 +2047,32 @@ int parse_arguments(int *argc_p, const char ***argv_p) return 0; #endif + case OPT_STOP_AFTER: { + long val; + arg = poptGetOptArg(pc); + stop_at_utime = time(NULL); + if ((val = atol(arg) * 60) <= 0 || val + (long)stop_at_utime < 0) { + snprintf(err_buf, sizeof err_buf, "invalid --stop-after value: %s\n", arg); + return 0; + } + stop_at_utime += val; + break; + } + +#ifdef HAVE_MKTIME + 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); + return 0; + } + if (stop_at_utime <= time(NULL)) { + snprintf(err_buf, sizeof err_buf, "--stop-at time is not in the future: %s\n", arg); + return 0; + } + break; +#endif + default: /* A large opt value means that set_refuse_options() * turned this option off. */ diff --git a/rsync.1.md b/rsync.1.md index 685c5c37..5be8029c 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -457,6 +457,8 @@ detailed description below for a complete description. --early-input=FILE use FILE for daemon's early exec input --list-only list the files instead of copying them --bwlimit=RATE limit socket I/O bandwidth +--stop-after=MINS Stop rsync after MINS minutes have elapsed +--stop-at=y-m-dTh:m Stop rsync at the specified moment in time --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 @@ -3113,6 +3115,45 @@ your home directory (remove the '=' for that). 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. +0. `--stop-after=MINS + + This option tells rsync to stop copying when the specified number of + minutes has elapsed. + + Rsync also accepts an earlier version of this option: `--time-limit=MINS`. + + For maximal flexibility, rsync does not communicate this option to the + remote rsync since it is usually enough that one side of the connection + quits as specified. This allows the option's use even when only one side + of the connection supports it. You can tell the remote side about the time + limit using `--remote-option` (`-M`), should the need arise. + +0. `--stop-at=y-m-dTh:m + + This option tells rsync to stop copying when the specified point in time + has been reached. The date & time can be fully specified in a numeric + format of year-month-dayThour:minute (e.g. 2000-12-31T23:59) in the local + timezone. You may choose to separate the date numbers using slashes + instead of dashes. + + The value can also be abbreviated in a variety of ways, such as specifying + a 2-digit year and/or leaving off various values. In all cases, the value + will be taken to be the next possible future moment where the supplied + information matches. If the value specifies the current time or a past + time, rsync exits with an error. + + For example, "1-30" specifies the next January 30th (at midnight local + time), "14:00" specifies the next 2 P.M., "1" specifies the next 1st of the + month at midnight, and ":59" specifies the next 59th minute after the hour. + + For maximal flexibility, rsync does not communicate this option to the + remote rsync since it is usually enough that one side of the connection + quits as specified. This allows the option's use even when only one side + of the connection supports it. You can tell the remote side about the time + limit using `--remote-option` (`-M`), should the need arise. Do keep in + mind that the remote host may have a different default timezone than your + local host. + 0. `--write-batch=FILE` Record a file that can later be applied to another identical destination