1 John Taylor's patch for implementing --time-limit and --stop-at, reworked
2 to be simpler and more efficient by Wayne Davison.
4 Do we need configure support for mktime()?
6 To use this patch, run these commands for a successful build:
8 patch -p1 <patches/time-limit.diff
9 ./configure (optional if already run)
12 based-on: 7c8f180900432e646c0a4bd02e2c4033068dbb7c
13 diff --git a/io.c b/io.c
16 @@ -58,6 +58,7 @@ extern int remove_source_files;
17 extern int preserve_hard_links;
18 extern BOOL extra_flist_sending_enabled;
19 extern struct stats stats;
20 +extern time_t stop_at_utime;
21 extern struct file_list *cur_flist;
23 extern int filesfrom_convert;
24 @@ -168,11 +169,19 @@ static void check_timeout(BOOL allow_keepalive)
25 * generator might be blocked trying to send checksums, it needs to
26 * know that the receiver is active). Thus, as long as one or the
27 * other is successfully doing work, the generator will not timeout. */
29 + if (!io_timeout && !stop_at_utime)
34 + if (stop_at_utime && t >= stop_at_utime) {
35 + rprintf(FERROR, "run-time limit exceeded\n");
36 + exit_cleanup(RERR_TIMEOUT);
42 if (allow_keepalive) {
43 /* This may put data into iobuf.msg w/o flushing. */
44 maybe_send_keepalive(t, 0);
45 diff --git a/options.c b/options.c
48 @@ -113,6 +113,7 @@ size_t bwlimit_writemax = 0;
49 int ignore_existing = 0;
50 int ignore_non_existing = 0;
51 int need_messages_from_generator = 0;
52 +time_t stop_at_utime = 0;
53 int max_delete = INT_MIN;
56 @@ -789,6 +790,8 @@ void usage(enum logcode F)
57 rprintf(F," --password-file=FILE read daemon-access password from FILE\n");
58 rprintf(F," --list-only list the files instead of copying them\n");
59 rprintf(F," --bwlimit=RATE limit socket I/O bandwidth\n");
60 + rprintf(F," --stop-at=y-m-dTh:m Stop rsync at year-month-dayThour:minute\n");
61 + rprintf(F," --time-limit=MINS Stop rsync after MINS minutes have elapsed\n");
62 rprintf(F," --write-batch=FILE write a batched update to FILE\n");
63 rprintf(F," --only-write-batch=FILE like --write-batch but w/o updating destination\n");
64 rprintf(F," --read-batch=FILE read a batched update from FILE\n");
65 @@ -813,6 +816,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
66 OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
67 OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG,
68 OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT,
69 + OPT_STOP_AT, OPT_TIME_LIMIT,
70 OPT_SERVER, OPT_REFUSED_BASE = 9000};
72 static struct poptOption long_options[] = {
73 @@ -1006,6 +1010,8 @@ static struct poptOption long_options[] = {
74 {"no-timeout", 0, POPT_ARG_VAL, &io_timeout, 0, 0, 0 },
75 {"contimeout", 0, POPT_ARG_INT, &connect_timeout, 0, 0, 0 },
76 {"no-contimeout", 0, POPT_ARG_VAL, &connect_timeout, 0, 0, 0 },
77 + {"stop-at", 0, POPT_ARG_STRING, 0, OPT_STOP_AT, 0, 0 },
78 + {"time-limit", 0, POPT_ARG_STRING, 0, OPT_TIME_LIMIT, 0, 0 },
79 {"rsh", 'e', POPT_ARG_STRING, &shell_cmd, 0, 0, 0 },
80 {"rsync-path", 0, POPT_ARG_STRING, &rsync_path, 0, 0, 0 },
81 {"temp-dir", 'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
82 @@ -1781,6 +1787,36 @@ int parse_arguments(int *argc_p, const char ***argv_p)
87 + arg = poptGetOptArg(pc);
88 + if ((stop_at_utime = parse_time(arg)) == (time_t)-1) {
89 + snprintf(err_buf, sizeof err_buf,
90 + "invalid --stop-at format: %s\n",
92 + rprintf(FERROR, "ERROR: %s", err_buf);
95 + if (stop_at_utime < time(NULL)) {
96 + snprintf(err_buf, sizeof err_buf,
97 + "--stop-at time is in the past: %s\n",
99 + rprintf(FERROR, "ERROR: %s", err_buf);
104 + case OPT_TIME_LIMIT:
105 + arg = poptGetOptArg(pc);
106 + if ((stop_at_utime = atol(arg) * 60) <= 0) {
107 + snprintf(err_buf, sizeof err_buf,
108 + "invalid --time-limit value: %s\n",
110 + rprintf(FERROR, "ERROR: %s", err_buf);
113 + stop_at_utime += time(NULL);
117 /* A large opt value means that set_refuse_options()
118 * turned this option off. */
119 @@ -2507,6 +2543,15 @@ void server_options(char **args, int *argc_p)
123 + if (stop_at_utime) {
124 + long mins = (stop_at_utime - time(NULL)) / 60;
127 + if (asprintf(&arg, "--time-limit=%ld", mins) < 0)
133 args[ac++] = "--backup-dir";
134 args[ac++] = backup_dir;
135 diff --git a/rsync.yo b/rsync.yo
138 @@ -447,6 +447,8 @@ to the detailed description below for a complete description. verb(
139 --password-file=FILE read daemon-access password from FILE
140 --list-only list the files instead of copying them
141 --bwlimit=RATE limit socket I/O bandwidth
142 + --stop-at=y-m-dTh:m Stop rsync at year-month-dayThour:minute
143 + --time-limit=MINS Stop rsync after MINS minutes have elapsed
144 --write-batch=FILE write a batched update to FILE
145 --only-write-batch=FILE like --write-batch but w/o updating dest
146 --read-batch=FILE read a batched update from FILE
147 @@ -2448,6 +2450,19 @@ files can show up as being rapidly sent when the data is quickly buffered,
148 while other can show up as very slow when the flushing of the output buffer
149 occurs. This may be fixed in a future version.
151 +dit(bf(--stop-at=y-m-dTh:m)) This option allows you to specify at what
152 +time to stop rsync, in year-month-dayThour:minute numeric format (e.g.
153 +2004-12-31T23:59). You can specify a 2 or 4-digit year. You can also
154 +leave off various items and the result will be the next possible time
155 +that matches the specified data. For example, "1-30" specifies the next
156 +January 30th (at midnight), "04:00" specifies the next 4am, "1"
157 +specifies the next 1st of the month at midnight, and ":59" specifies the
158 +next 59th minute after the hour. If you prefer, you may separate the
159 +date numbers using slashes instead of dashes.
161 +dit(bf(--time-limit=MINS)) This option allows you to specify the maximum
162 +number of minutes rsync will run for.
164 dit(bf(--write-batch=FILE)) Record a file that can later be applied to
165 another identical destination with bf(--read-batch). See the "BATCH MODE"
166 section for details, and also the bf(--only-write-batch) option.
167 diff --git a/util.c b/util.c
170 @@ -125,6 +125,133 @@ NORETURN void overflow_exit(const char *str)
171 exit_cleanup(RERR_MALLOC);
174 +/* Allow the user to specify a time in the format yyyy-mm-ddThh:mm while
175 + * also allowing abbreviated data. For instance, if the time is omitted,
176 + * it defaults to midnight. If the date is omitted, it defaults to the
177 + * next possible date in the future with the specified time. Even the
178 + * year or year-month can be omitted, again defaulting to the next date
179 + * in the future that matches the specified information. A 2-digit year
180 + * is also OK, as is using '/' instead of '-'. */
181 +time_t parse_time(const char *arg)
184 + time_t val, now = time(NULL);
185 + struct tm t, *today = localtime(&now);
188 + memset(&t, 0, sizeof t);
189 + t.tm_year = t.tm_mon = t.tm_mday = -1;
190 + t.tm_hour = t.tm_min = t.tm_isdst = -1;
192 + if (*cp == 'T' || *cp == 't' || *cp == ':') {
203 + n = n * 10 + *cp++ - '0';
204 + } while (isDigit(cp));
209 + if (t.tm_year != -1)
211 + t.tm_year = t.tm_mon;
212 + t.tm_mon = t.tm_mday;
216 + if (*cp == 'T' || *cp == 't') {
220 + } else if (*cp != '-' && *cp != '/')
224 + if (t.tm_hour != -1)
226 + t.tm_hour = t.tm_min;
235 + if (t.tm_year < 0) {
236 + t.tm_year = today->tm_year;
238 + } else if (t.tm_year < 100) {
239 + while (t.tm_year < today->tm_year)
243 + if (t.tm_mon < 0) {
244 + t.tm_mon = today->tm_mon;
248 + if (t.tm_mday < 0) {
249 + t.tm_mday = today->tm_mday;
254 + if (t.tm_min < 0) {
255 + t.tm_hour = t.tm_min = 0;
256 + } else if (t.tm_hour < 0) {
260 + t.tm_hour = today->tm_hour;
264 + if (t.tm_hour > 23 || t.tm_min > 59
265 + || t.tm_mon < 0 || t.tm_mon >= 12
266 + || t.tm_mday < 1 || t.tm_mday > 31
267 + || (val = mktime(&t)) == (time_t)-1)
270 + if (val <= now && in_date) {
277 + if (++t.tm_mon == 12)
285 + if ((val = mktime(&t)) == (time_t)-1) {
286 + if (in_date == 3 && t.tm_mday > 28) {
301 /* This returns 0 for success, 1 for a symlink if symlink time-setting
302 * is not possible, or -1 for any other error. */
303 int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)