0a378da0099928e7fb62add60d166a1202cd7aec
[rsync-patches.git] / time-limit.diff
1 John Taylor's patch for implementing --time-limit and --stop-at, reworked
2 to be simpler and more efficient by Wayne Davison.
3
4 Do we need configure support for mktime()?
5
6 To use this patch, run these commands for a successful build:
7
8     patch -p1 <patches/time-limit.diff
9     ./configure                              (optional if already run)
10     make
11
12 based-on: 63f91976112b8b2118cc17eb5fc8142175566f4f
13 diff --git a/io.c b/io.c
14 --- a/io.c
15 +++ b/io.c
16 @@ -59,6 +59,7 @@ extern int preserve_hard_links;
17  extern BOOL extra_flist_sending_enabled;
18  extern BOOL flush_ok_after_signal;
19  extern struct stats stats;
20 +extern time_t stop_at_utime;
21  extern struct file_list *cur_flist;
22  #ifdef ICONV_OPTION
23  extern int filesfrom_convert;
24 @@ -170,11 +171,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. */
28 -       if (!io_timeout)
29 +       if (!io_timeout && !stop_at_utime)
30                 return;
31  
32         t = time(NULL);
33  
34 +       if (stop_at_utime && t >= stop_at_utime) {
35 +               rprintf(FERROR, "run-time limit exceeded\n");
36 +               exit_cleanup(RERR_TIMEOUT);
37 +       }
38 +
39 +       if (!io_timeout)
40 +               return;
41 +
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
46 --- a/options.c
47 +++ 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;
54  OFF_T max_size = -1;
55  OFF_T min_size = -1;
56 @@ -791,6 +792,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  #ifdef HAVE_SETVBUF
63    rprintf(F,"     --outbuf=N|L|B          set output buffering to None, Line, or Block\n");
64  #endif
65 @@ -819,6 +822,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};
71  
72  static struct poptOption long_options[] = {
73 @@ -1012,6 +1016,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 @@ -1794,6 +1800,36 @@ int parse_arguments(int *argc_p, const char ***argv_p)
83                         return 0;
84  #endif
85  
86 +               case OPT_STOP_AT:
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",
91 +                                   arg);
92 +                               rprintf(FERROR, "ERROR: %s", err_buf);
93 +                               return 0;
94 +                       }
95 +                       if (stop_at_utime < time(NULL)) {
96 +                               snprintf(err_buf, sizeof err_buf,
97 +                                   "--stop-at time is in the past: %s\n",
98 +                                   arg);
99 +                               rprintf(FERROR, "ERROR: %s", err_buf);
100 +                               return 0;
101 +                       }
102 +                       break;
103 +
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",
109 +                                   arg);
110 +                               rprintf(FERROR, "ERROR: %s", err_buf);
111 +                               return 0;
112 +                       }
113 +                       stop_at_utime += time(NULL);
114 +                       break;
115 +
116                 default:
117                         /* A large opt value means that set_refuse_options()
118                          * turned this option off. */
119 @@ -2561,6 +2597,15 @@ void server_options(char **args, int *argc_p)
120                 args[ac++] = arg;
121         }
122  
123 +       if (stop_at_utime) {
124 +               long mins = (stop_at_utime - time(NULL)) / 60;
125 +               if (mins <= 0)
126 +                       mins = 1;
127 +               if (asprintf(&arg, "--time-limit=%ld", mins) < 0)
128 +                       goto oom;
129 +               args[ac++] = arg;
130 +       }
131 +
132         if (backup_dir) {
133                 args[ac++] = "--backup-dir";
134                 args[ac++] = backup_dir;
135 diff --git a/rsync.yo b/rsync.yo
136 --- a/rsync.yo
137 +++ b/rsync.yo
138 @@ -448,6 +448,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 @@ -2479,6 +2481,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.
150  
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.
160 +
161 +dit(bf(--time-limit=MINS)) This option allows you to specify the maximum
162 +number of minutes rsync will run for.
163 +
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
168 --- a/util.c
169 +++ b/util.c
170 @@ -114,6 +114,133 @@ void print_child_argv(const char *prefix, char **cmd)
171         rprintf(FCLIENT, " (%d args)\n", cnt);
172  }
173  
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)
182 +{
183 +       const char *cp;
184 +       time_t val, now = time(NULL);
185 +       struct tm t, *today = localtime(&now);
186 +       int in_date, n;
187 +
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;
191 +       cp = arg;
192 +       if (*cp == 'T' || *cp == 't' || *cp == ':') {
193 +               cp++;
194 +               in_date = 0;
195 +       } else
196 +               in_date = 1;
197 +       for ( ; ; cp++) {
198 +               if (!isDigit(cp))
199 +                       return -1;
200 +
201 +               n = 0;
202 +               do {
203 +                       n = n * 10 + *cp++ - '0';
204 +               } while (isDigit(cp));
205 +
206 +               if (*cp == ':')
207 +                       in_date = 0;
208 +               if (in_date) {
209 +                       if (t.tm_year != -1)
210 +                               return -1;
211 +                       t.tm_year = t.tm_mon;
212 +                       t.tm_mon = t.tm_mday;
213 +                       t.tm_mday = n;
214 +                       if (!*cp)
215 +                               break;
216 +                       if (*cp == 'T' || *cp == 't') {
217 +                               if (!cp[1])
218 +                                       break;
219 +                               in_date = 0;
220 +                       } else if (*cp != '-' && *cp != '/')
221 +                               return -1;
222 +                       continue;
223 +               }
224 +               if (t.tm_hour != -1)
225 +                       return -1;
226 +               t.tm_hour = t.tm_min;
227 +               t.tm_min = n;
228 +               if (!*cp)
229 +                       break;
230 +               if (*cp != ':')
231 +                       return -1;
232 +       }
233 +
234 +       in_date = 0;
235 +       if (t.tm_year < 0) {
236 +               t.tm_year = today->tm_year;
237 +               in_date = 1;
238 +       } else if (t.tm_year < 100) {
239 +               while (t.tm_year < today->tm_year)
240 +                       t.tm_year += 100;
241 +       } else
242 +               t.tm_year -= 1900;
243 +       if (t.tm_mon < 0) {
244 +               t.tm_mon = today->tm_mon;
245 +               in_date = 2;
246 +       } else
247 +               t.tm_mon--;
248 +       if (t.tm_mday < 0) {
249 +               t.tm_mday = today->tm_mday;
250 +               in_date = 3;
251 +       }
252 +
253 +       n = 0;
254 +       if (t.tm_min < 0) {
255 +               t.tm_hour = t.tm_min = 0;
256 +       } else if (t.tm_hour < 0) {
257 +               if (in_date != 3)
258 +                       return -1;
259 +               in_date = 0;
260 +               t.tm_hour = today->tm_hour;
261 +               n = 60*60;
262 +       }
263 +
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)
268 +               return -1;
269 +
270 +       if (val <= now && in_date) {
271 +           tweak_date:
272 +               switch (in_date) {
273 +               case 3:
274 +                       t.tm_mday++;
275 +                       break;
276 +               case 2:
277 +                       if (++t.tm_mon == 12)
278 +                               t.tm_mon = 0;
279 +                       else
280 +                               break;
281 +               case 1:
282 +                       t.tm_year++;
283 +                       break;
284 +               }
285 +               if ((val = mktime(&t)) == (time_t)-1) {
286 +                       if (in_date == 3 && t.tm_mday > 28) {
287 +                               t.tm_mday = 1;
288 +                               in_date = 2;
289 +                               goto tweak_date;
290 +                       }
291 +                       return -1;
292 +               }
293 +       }
294 +       if (n) {
295 +               while (val <= now)
296 +                       val += n;
297 +       }
298 +       return val;
299 +}
300 +
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)