summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWayne Davison <wayne@opencoder.net>2020-07-12 18:32:41 -0700
committerWayne Davison <wayne@opencoder.net>2020-07-12 18:32:41 -0700
commitaf531cf787995f6a3bc381cd1da1988192e7ef59 (patch)
tree67248749da8b2c0017136562041390318e60edaf
parentd495e343c0671de620193987ae9de3f02a611b4a (diff)
downloadrsync-af531cf787995f6a3bc381cd1da1988192e7ef59.tar.gz
Add the --stop-after & --stop-at options.
-rw-r--r--NEWS.md4
-rw-r--r--configure.ac2
-rw-r--r--io.c9
-rw-r--r--options.c181
-rw-r--r--rsync.1.md41
5 files changed, 234 insertions, 3 deletions
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