Updated patches to work with the current trunk.
[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: 24079e988fc31af4eba56cd2701fdc5a4154980d
13 diff --git a/io.c b/io.c
14 --- a/io.c
15 +++ b/io.c
16 @@ -57,6 +57,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;
22  #ifdef ICONV_OPTION
23  extern int filesfrom_convert;
24 @@ -154,16 +155,24 @@ static void check_timeout(void)
25  {
26         time_t t;
27  
28 +       if ((!io_timeout || ignore_timeout) && !stop_at_utime)
29 +               return;
30 +
31 +       t = time(NULL);
32 +
33 +       if (stop_at_utime && t >= stop_at_utime) {
34 +               rprintf(FERROR, "run-time limit exceeded\n");
35 +               exit_cleanup(RERR_TIMEOUT);
36 +       }
37 +
38         if (!io_timeout || ignore_timeout)
39                 return;
40  
41         if (!last_io_in) {
42 -               last_io_in = time(NULL);
43 +               last_io_in = t;
44                 return;
45         }
46  
47 -       t = time(NULL);
48 -
49         if (t - last_io_in >= io_timeout) {
50                 if (!am_server && !am_daemon) {
51                         rprintf(FERROR, "io timeout after %d seconds -- exiting\n",
52 diff --git a/options.c b/options.c
53 --- a/options.c
54 +++ b/options.c
55 @@ -112,6 +112,7 @@ size_t bwlimit_writemax = 0;
56  int ignore_existing = 0;
57  int ignore_non_existing = 0;
58  int need_messages_from_generator = 0;
59 +time_t stop_at_utime = 0;
60  int max_delete = INT_MIN;
61  OFF_T max_size = 0;
62  OFF_T min_size = 0;
63 @@ -776,6 +777,8 @@ void usage(enum logcode F)
64    rprintf(F,"     --password-file=FILE    read daemon-access password from FILE\n");
65    rprintf(F,"     --list-only             list the files instead of copying them\n");
66    rprintf(F,"     --bwlimit=RATE          limit socket I/O bandwidth\n");
67 +  rprintf(F,"     --stop-at=y-m-dTh:m     Stop rsync at year-month-dayThour:minute\n");
68 +  rprintf(F,"     --time-limit=MINS       Stop rsync after MINS minutes have elapsed\n");
69    rprintf(F,"     --write-batch=FILE      write a batched update to FILE\n");
70    rprintf(F,"     --only-write-batch=FILE like --write-batch but w/o updating destination\n");
71    rprintf(F,"     --read-batch=FILE       read a batched update from FILE\n");
72 @@ -800,6 +803,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
73        OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
74        OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG,
75        OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT,
76 +      OPT_STOP_AT, OPT_TIME_LIMIT,
77        OPT_SERVER, OPT_REFUSED_BASE = 9000};
78  
79  static struct poptOption long_options[] = {
80 @@ -989,6 +993,8 @@ static struct poptOption long_options[] = {
81    {"no-timeout",       0,  POPT_ARG_VAL,    &io_timeout, 0, 0, 0 },
82    {"contimeout",       0,  POPT_ARG_INT,    &connect_timeout, 0, 0, 0 },
83    {"no-contimeout",    0,  POPT_ARG_VAL,    &connect_timeout, 0, 0, 0 },
84 +  {"stop-at",          0,  POPT_ARG_STRING, 0, OPT_STOP_AT, 0, 0 },
85 +  {"time-limit",       0,  POPT_ARG_STRING, 0, OPT_TIME_LIMIT, 0, 0 },
86    {"rsh",             'e', POPT_ARG_STRING, &shell_cmd, 0, 0, 0 },
87    {"rsync-path",       0,  POPT_ARG_STRING, &rsync_path, 0, 0, 0 },
88    {"temp-dir",        'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
89 @@ -1763,6 +1769,36 @@ int parse_arguments(int *argc_p, const char ***argv_p)
90                         return 0;
91  #endif
92  
93 +               case OPT_STOP_AT:
94 +                       arg = poptGetOptArg(pc);
95 +                       if ((stop_at_utime = parse_time(arg)) == (time_t)-1) {
96 +                               snprintf(err_buf, sizeof err_buf,
97 +                                   "invalid --stop-at format: %s\n",
98 +                                   arg);
99 +                               rprintf(FERROR, "ERROR: %s", err_buf);
100 +                               return 0;
101 +                       }
102 +                       if (stop_at_utime < time(NULL)) {
103 +                               snprintf(err_buf, sizeof err_buf,
104 +                                   "--stop-at time is in the past: %s\n",
105 +                                   arg);
106 +                               rprintf(FERROR, "ERROR: %s", err_buf);
107 +                               return 0;
108 +                       }
109 +                       break;
110 +
111 +               case OPT_TIME_LIMIT:
112 +                       arg = poptGetOptArg(pc);
113 +                       if ((stop_at_utime = atol(arg) * 60) <= 0) {
114 +                               snprintf(err_buf, sizeof err_buf,
115 +                                   "invalid --time-limit value: %s\n",
116 +                                   arg);
117 +                               rprintf(FERROR, "ERROR: %s", err_buf);
118 +                               return 0;
119 +                       }
120 +                       stop_at_utime += time(NULL);
121 +                       break;
122 +
123                 default:
124                         /* A large opt value means that set_refuse_options()
125                          * turned this option off. */
126 @@ -2465,6 +2501,15 @@ void server_options(char **args, int *argc_p)
127                 args[ac++] = arg;
128         }
129  
130 +       if (stop_at_utime) {
131 +               long mins = (stop_at_utime - time(NULL)) / 60;
132 +               if (mins <= 0)
133 +                       mins = 1;
134 +               if (asprintf(&arg, "--time-limit=%ld", mins) < 0)
135 +                       goto oom;
136 +               args[ac++] = arg;
137 +       }
138 +
139         if (backup_dir) {
140                 args[ac++] = "--backup-dir";
141                 args[ac++] = backup_dir;
142 diff --git a/rsync.yo b/rsync.yo
143 --- a/rsync.yo
144 +++ b/rsync.yo
145 @@ -431,6 +431,8 @@ to the detailed description below for a complete description.  verb(
146       --password-file=FILE    read daemon-access password from FILE
147       --list-only             list the files instead of copying them
148       --bwlimit=RATE          limit socket I/O bandwidth
149 +     --stop-at=y-m-dTh:m     Stop rsync at year-month-dayThour:minute
150 +     --time-limit=MINS       Stop rsync after MINS minutes have elapsed
151       --write-batch=FILE      write a batched update to FILE
152       --only-write-batch=FILE like --write-batch but w/o updating dest
153       --read-batch=FILE       read a batched update from FILE
154 @@ -2295,6 +2297,19 @@ files can show up as being rapidly sent when the data is quickly buffered,
155  while other can show up as very slow when the flushing of the output buffer
156  occurs.  This may be fixed in a future version.
157  
158 +dit(bf(--stop-at=y-m-dTh:m)) This option allows you to specify at what
159 +time to stop rsync, in year-month-dayThour:minute numeric format (e.g.
160 +2004-12-31T23:59).  You can specify a 2 or 4-digit year.  You can also
161 +leave off various items and the result will be the next possible time
162 +that matches the specified data.  For example, "1-30" specifies the next
163 +January 30th (at midnight), "04:00" specifies the next 4am, "1"
164 +specifies the next 1st of the month at midnight, and ":59" specifies the
165 +next 59th minute after the hour.  If you prefer, you may separate the
166 +date numbers using slashes instead of dashes.
167 +
168 +dit(bf(--time-limit=MINS)) This option allows you to specify the maximum
169 +number of minutes rsync will run for.
170 +
171  dit(bf(--write-batch=FILE)) Record a file that can later be applied to
172  another identical destination with bf(--read-batch). See the "BATCH MODE"
173  section for details, and also the bf(--only-write-batch) option.
174 diff --git a/util.c b/util.c
175 --- a/util.c
176 +++ b/util.c
177 @@ -123,6 +123,133 @@ NORETURN void overflow_exit(const char *str)
178         exit_cleanup(RERR_MALLOC);
179  }
180  
181 +/* Allow the user to specify a time in the format yyyy-mm-ddThh:mm while
182 + * also allowing abbreviated data.  For instance, if the time is omitted,
183 + * it defaults to midnight.  If the date is omitted, it defaults to the
184 + * next possible date in the future with the specified time.  Even the
185 + * year or year-month can be omitted, again defaulting to the next date
186 + * in the future that matches the specified information.  A 2-digit year
187 + * is also OK, as is using '/' instead of '-'. */
188 +time_t parse_time(const char *arg)
189 +{
190 +       const char *cp;
191 +       time_t val, now = time(NULL);
192 +       struct tm t, *today = localtime(&now);
193 +       int in_date, n;
194 +
195 +       memset(&t, 0, sizeof t);
196 +       t.tm_year = t.tm_mon = t.tm_mday = -1;
197 +       t.tm_hour = t.tm_min = t.tm_isdst = -1;
198 +       cp = arg;
199 +       if (*cp == 'T' || *cp == 't' || *cp == ':') {
200 +               cp++;
201 +               in_date = 0;
202 +       } else
203 +               in_date = 1;
204 +       for ( ; ; cp++) {
205 +               if (!isDigit(cp))
206 +                       return -1;
207 +
208 +               n = 0;
209 +               do {
210 +                       n = n * 10 + *cp++ - '0';
211 +               } while (isDigit(cp));
212 +
213 +               if (*cp == ':')
214 +                       in_date = 0;
215 +               if (in_date) {
216 +                       if (t.tm_year != -1)
217 +                               return -1;
218 +                       t.tm_year = t.tm_mon;
219 +                       t.tm_mon = t.tm_mday;
220 +                       t.tm_mday = n;
221 +                       if (!*cp)
222 +                               break;
223 +                       if (*cp == 'T' || *cp == 't') {
224 +                               if (!cp[1])
225 +                                       break;
226 +                               in_date = 0;
227 +                       } else if (*cp != '-' && *cp != '/')
228 +                               return -1;
229 +                       continue;
230 +               }
231 +               if (t.tm_hour != -1)
232 +                       return -1;
233 +               t.tm_hour = t.tm_min;
234 +               t.tm_min = n;
235 +               if (!*cp)
236 +                       break;
237 +               if (*cp != ':')
238 +                       return -1;
239 +       }
240 +
241 +       in_date = 0;
242 +       if (t.tm_year < 0) {
243 +               t.tm_year = today->tm_year;
244 +               in_date = 1;
245 +       } else if (t.tm_year < 100) {
246 +               while (t.tm_year < today->tm_year)
247 +                       t.tm_year += 100;
248 +       } else
249 +               t.tm_year -= 1900;
250 +       if (t.tm_mon < 0) {
251 +               t.tm_mon = today->tm_mon;
252 +               in_date = 2;
253 +       } else
254 +               t.tm_mon--;
255 +       if (t.tm_mday < 0) {
256 +               t.tm_mday = today->tm_mday;
257 +               in_date = 3;
258 +       }
259 +
260 +       n = 0;
261 +       if (t.tm_min < 0) {
262 +               t.tm_hour = t.tm_min = 0;
263 +       } else if (t.tm_hour < 0) {
264 +               if (in_date != 3)
265 +                       return -1;
266 +               in_date = 0;
267 +               t.tm_hour = today->tm_hour;
268 +               n = 60*60;
269 +       }
270 +
271 +       if (t.tm_hour > 23 || t.tm_min > 59
272 +           || t.tm_mon < 0 || t.tm_mon >= 12
273 +           || t.tm_mday < 1 || t.tm_mday > 31
274 +           || (val = mktime(&t)) == (time_t)-1)
275 +               return -1;
276 +
277 +       if (val <= now && in_date) {
278 +           tweak_date:
279 +               switch (in_date) {
280 +               case 3:
281 +                       t.tm_mday++;
282 +                       break;
283 +               case 2:
284 +                       if (++t.tm_mon == 12)
285 +                               t.tm_mon = 0;
286 +                       else
287 +                               break;
288 +               case 1:
289 +                       t.tm_year++;
290 +                       break;
291 +               }
292 +               if ((val = mktime(&t)) == (time_t)-1) {
293 +                       if (in_date == 3 && t.tm_mday > 28) {
294 +                               t.tm_mday = 1;
295 +                               in_date = 2;
296 +                               goto tweak_date;
297 +                       }
298 +                       return -1;
299 +               }
300 +       }
301 +       if (n) {
302 +               while (val <= now)
303 +                       val += n;
304 +       }
305 +       return val;
306 +}
307 +
308  int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
309  {
310  #ifndef CAN_SET_SYMLINK_TIMES