chiark / gitweb /
Backup: Honour CHIARK_BACKUP_SHAREDIR in snap/lvm script.
[chiark-utils.git] / cprogs / rcopy-repeatedly.c
1 /*
2  * rcopy-repeatedly
3  *
4  *   You say  rcopy-repeatedly local-file user@host:remote-file
5  *   and it polls for changes to local-file and copies them to
6  *   remote-file.  rcopy-repeatedly must be installed at the far end.
7  *   You can copy in either direction but not between two remote
8  *   locations.
9  *
10  *   Limitations:
11  *    * Cannot cope with files which are modified between us opening
12  *      and statting them for the first time; if the file shrinks
13  *      we may bomb out.  Workaround: use rename-in-place.
14  *    * When transferring large files, bandwidth limiter will
15  *      be `lumpy' as the whole file is transferred and then we
16  *      sleep.
17  *    * Cannot copy between two local files.  Workaround: a symlink
18  *      (but presumably there was some reason you weren't doing that)
19  *    * No ability to synchronise more than just exactly one file
20  *    * Polls.  It would be nice to use inotify or something.
21  *
22  *   Inherent limitations:
23  *    * Can only copy plain files.
24  *
25  *   See the --help for options.
26  */     
27 /*
28  * rcopy-repeatedly is
29  *  Copyright (C) 2008 Ian Jackson <ian@davenant.greenend.org.uk>
30  * and the option parser we use is
31  *  Copyright (C) 1994,1995 Ian Jackson <ian@davenant.greenend.org.uk>
32  *
33  * This is free software; you can redistribute it and/or modify
34  * it under the terms of the GNU General Public License as
35  * published by the Free Software Foundation; either version 3,
36  * or (at your option) any later version.
37  *
38  * This is distributed in the hope that it will be useful, but
39  * WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41  * GNU General Public License for more details.
42  *
43  * You should have received a copy of the GNU General Public
44  * License along with this file; if not, consult the Free Software
45  * Foundation's website at www.fsf.org, or the GNU Project website at
46  * www.gnu.org.
47  */
48 /*
49  * protocol is:
50  *   server sends banner
51  *    - "#rcopy-repeatedly#\n"
52  *    - length of declaration, as 4 hex digits, zero prefixed,
53  *      and a newline [5 bytes].  In this protocol version this
54  *      will be "0002" but client _must_ parse it.
55  *   server sends declaration
56  *    - one of "u " or "d" [1 byte]
57  *    - optionally, some more ascii text, reserved for future use
58  *      must be ignored by declaree (but not sent by declarer)
59  *    - a newline [1 byte]
60  *   client sends
61  *    - 0x02   START
62  *        n    2 bytes big endian declaration length
63  *        ...  client's declaration (ascii text, including newline)
64  8             see above
65  * then for each update
66  *   sender sends one of
67  *    - 0x03   destination file should be deleted
68  *             but note that contents must be retained by receiver
69  *             as it may be used for rle updates
70  *    - 0x04   complete new destination file follows, 64-bit length
71  *        l    8 bytes big endian length
72  *        ...  l bytes data
73  *             receiver must then reply with 0x01 ACK
74  */
75
76 #define _GNU_SOURCE
77
78 #include <stdio.h>
79 #include <time.h>
80 #include <stdarg.h>
81 #include <stdlib.h>
82 #include <stdint.h>
83 #include <string.h>
84 #include <errno.h>
85 #include <limits.h>
86 #include <assert.h>
87 #include <math.h>
88
89 #include <sys/types.h>
90 #include <sys/stat.h>
91 #include <unistd.h>
92
93 #include "myopt.h"
94
95 #define REPLMSG_ACK    0x01
96 #define REPLMSG_START  0x02
97 #define REPLMSG_RM     0x03
98 #define REPLMSG_FILE64 0x04
99
100 static const char banner[]= "#rcopy-repeatedly#\n";
101
102 static FILE *commsi, *commso;
103
104 static double max_bw_prop= 0.2;
105 static int txblocksz= INT_MAX, verbose=1;
106 static int min_interval_usec= 100000; /* 100ms */
107
108 static int nsargs;
109 static const char **sargs;
110
111 static const char *rsh_program= 0;
112 static const char *rcopy_repeatedly_program= "rcopy-repeatedly";
113 static int server_upcopy=-1; /* -1 means not yet known; 0 means download */
114   /* `up' means towards the client,
115    * since we regard the subprocess as `down' */
116
117 static int udchar;
118
119 static char mainbuf[65536]; /* must be at least 2^16 */
120
121 #define NORETURN __attribute__((noreturn))
122
123 static void vdie(int ec, const char *pfx, int eno,
124                  const char *fmt, va_list al) NORETURN;
125 static void vdie(int ec, const char *pfx, int eno,
126                  const char *fmt, va_list al) {
127   fputs("rcopy-repeatedly: ",stderr);
128   if (server_upcopy>=0) fputs("server: ",stderr);
129   if (pfx) fprintf(stderr,"%s: ",pfx);
130   vfprintf(stderr,fmt,al);
131   if (eno!=-1) fprintf(stderr,": %s",strerror(eno));
132   fputc('\n',stderr);
133   exit(ec);
134 }
135 static void die(int ec, const char *pfx, int eno,
136                 const char *fmt, ...) NORETURN;
137 static void die(int ec, const char *pfx, int eno,
138                 const char *fmt, ...) {
139   va_list al;
140   va_start(al,fmt);
141   vdie(ec,pfx,eno,fmt,al);
142 }
143
144 static void diem(void) NORETURN;
145 static void diem(void) { die(16,0,errno,"malloc failed"); }
146 static void *xmalloc(size_t sz) {
147   assert(sz);
148   void *p= malloc(sz);
149   if (!p) diem();
150   return p;
151 }
152 static void *xrealloc(void *p, size_t sz) {
153   assert(sz);
154   p= realloc(p,sz);
155   if (!p) diem();
156   return p;
157 }
158
159 static void diee(const char *fmt, ...) NORETURN;
160 static void diee(const char *fmt, ...) {
161   va_list al;
162   va_start(al,fmt);
163   vdie(12,0,errno,fmt,al);
164 }
165 static void die_protocol(const char *fmt, ...) NORETURN;
166 static void die_protocol(const char *fmt, ...) {
167   va_list al;
168   va_start(al,fmt);
169   vdie(10,"protocol error",-1,fmt,al);
170 }
171
172 static void die_badrecv(const char *what) NORETURN;
173 static void die_badrecv(const char *what) {
174   if (ferror(commsi)) diee("communication failed while receiving %s", what);
175   if (feof(commsi)) die_protocol("receiver got unexpected EOF in %s", what);
176   abort();
177 }
178 static void die_badsend(void) NORETURN;
179 static void die_badsend(void) {
180   diee("transmission failed");
181 }
182
183 static void send_flush(void) {
184   if (ferror(commso) || fflush(commso))
185     die_badsend();
186 }
187 static void sendbyte(int c) {
188   if (putc(c,commso)==EOF)
189     die_badsend();
190 }
191
192 static void mfreadcommsi(void *buf, int l, const char *what) {
193   int r= fread(buf,1,l,commsi);  if (r!=l) die_badrecv(what);
194 }
195 static void mfwritecommso(const void *buf, int l) {
196   int r= fwrite(buf,1,l,commso);  if (r!=l) die_badsend();
197 }
198
199 static void mpipe(int p[2]) { if (pipe(p)) diee("could not create pipe"); }
200 static void mdup2(int fd, int fd2) {
201   if (dup2(fd,fd2)!=fd2) diee("could not dup2(%d,%d)",fd,fd2);
202 }
203
204 typedef void copyfile_die_fn(FILE *f, const char *xi);
205
206 struct timespec ts_sendstart;
207
208 static void mgettime(struct timespec *ts) {
209   int r= clock_gettime(CLOCK_MONOTONIC, ts);
210   if (r) diee("clock_gettime failed");
211 }
212
213 static void bandlimit_sendstart(void) {
214   mgettime(&ts_sendstart);
215 }
216
217 static double mgettime_elapsed(struct timespec ts_base,
218                                struct timespec *ts_ret) {
219   mgettime(ts_ret);
220   return (ts_ret->tv_sec - ts_base.tv_sec) +
221          (ts_ret->tv_nsec - ts_base.tv_nsec)*1e-9;
222 }
223
224 static void flushstderr(void) {
225   if (ferror(stderr) || fflush(stderr))
226     diee("could not write progress to stderr");
227 }
228
229 static void verbosespinprintf(const char *fmt, ...) {
230   static const char spinnerchars[]= "/-\\";
231   static int spinnerchar;
232
233   if (!verbose)
234     return;
235
236   va_list al;
237   va_start(al,fmt);
238   fprintf(stderr,"      %c ",spinnerchars[spinnerchar]);
239   spinnerchar++; spinnerchar %= sizeof(spinnerchars)-1;
240   vfprintf(stderr,fmt,al);
241   putc('\r',stderr);
242   flushstderr();
243 }
244
245 static void bandlimit_sendend(uint64_t bytes, int *interval_usec_update) {
246   struct timespec ts_buf;
247   double elapsed= mgettime_elapsed(ts_sendstart, &ts_buf);
248   double secsperbyte_observed= elapsed / bytes;
249
250   double min_update= elapsed / max_bw_prop;
251   if (min_update > 1e3) min_update= 1e3;
252   int min_update_usec= min_update * 1e6;
253
254   if (*interval_usec_update < min_update_usec)
255     *interval_usec_update= min_update_usec;
256
257   verbosespinprintf("%12lluby %10.3fs %13.2fkby/s %8dms",
258                     (unsigned long long)bytes, elapsed,
259                     1e-3/secsperbyte_observed, *interval_usec_update/1000);
260 }
261  
262 static void copyfile(FILE *sf, copyfile_die_fn *sdie, const char *sxi,
263                      FILE *df, copyfile_die_fn *ddie, const char *dxi,
264                      uint64_t lstart, int amsender) {
265   struct timespec ts_last;
266   int now, r;
267   uint64_t l=lstart, done=0;
268
269   ts_last= ts_sendstart;
270
271   while (l>0) {
272     now= l < sizeof(mainbuf) ? l : sizeof(mainbuf);
273     if (now > txblocksz) now= txblocksz;
274
275     r= fread(mainbuf,1,now,sf);  if (r!=now) sdie(sf,sxi);
276     r= fwrite(mainbuf,1,now,df);  if (r!=now) ddie(df,dxi);
277     l -= now;
278     done += now;
279
280     if (verbose) {
281       fprintf(stderr," %3d%% \r",
282               (int)(done*100.0/lstart));
283       flushstderr();
284     }
285   }
286 }
287
288 static void copydie_inputfile(FILE *f, const char *filename) {
289   diee("read failed on source file `%s'", filename);
290 }
291 static void copydie_tmpwrite(FILE *f, const char *tmpfilename) {
292   diee("write failed to temporary receiving file `%s'", tmpfilename);
293 }
294 static void copydie_commsi(FILE *f, const char *what) {
295   die_badrecv(what);
296 }
297 static void copydie_commso(FILE *f, const char *what) {
298   die_badsend();
299 }
300   
301 static int generate_declaration(void) {
302   /* returns length; declaration is left in mainbuf */
303   char *p= mainbuf;
304   *p++= udchar;
305   *p++= '\n';
306   return p - mainbuf;
307 }
308
309 static void read_declaration(int decllen) {
310   assert(decllen <= sizeof(mainbuf));
311   if (decllen<2) die_protocol("declaration too short");
312   mfreadcommsi(mainbuf,decllen,"declaration");
313   if (mainbuf[decllen-1] != '\n')
314     die_protocol("declaration missing final newline");
315   if (mainbuf[0] != udchar)
316     die_protocol("declaration incorrect direction indicator");
317 }
318
319 static void receiver(const char *filename) {
320   FILE *newfile;
321   char *tmpfilename;
322   int r, c;
323
324   char *lastslash= strrchr(filename,'/');
325   if (!lastslash)
326     r= asprintf(&tmpfilename, ".rcopy-repeatedly.#%s#", filename);
327   else
328     r= asprintf(&tmpfilename, "%.*s/.rcopy-repeatedly.#%s#",
329                 (int)(lastslash-filename), filename, lastslash+1);
330   if (r==-1) diem();
331   
332   r= unlink(tmpfilename);
333   if (r && errno!=ENOENT)
334     diee("could not remove temporary receiving file `%s'", tmpfilename);
335   
336   for (;;) {
337     send_flush();
338     c= fgetc(commsi);
339
340     switch (c) {
341
342     case EOF:
343       if (ferror(commsi)) die_badrecv("transfer message code");
344       assert(feof(commsi));
345       return;
346
347     case REPLMSG_RM:
348       r= unlink(filename);
349       if (r && errno!=ENOENT)
350         diee("source file removed but could not remove destination file `%s'",
351              filename);
352       break;
353       
354     case REPLMSG_FILE64:
355       newfile= fopen(tmpfilename, "wb");
356       if (!newfile) diee("could not create temporary receiving file `%s'",
357                          tmpfilename);
358       uint8_t lbuf[8];
359       mfreadcommsi(lbuf,8,"FILE64 l");
360
361       uint64_t l=
362         (lbuf[0] << 28 << 28) |
363         (lbuf[1] << 24 << 24) |
364         (lbuf[2] << 16 << 24) |
365         (lbuf[3] <<  8 << 24) |
366         (lbuf[4]       << 24) |
367         (lbuf[5]       << 16) |
368         (lbuf[6]       <<  8) |
369         (lbuf[7]            ) ;
370
371       copyfile(commsi, copydie_commsi,"FILE64 file data",
372                newfile, copydie_tmpwrite,tmpfilename,
373                l, 0);
374
375       if (fclose(newfile)) diee("could not flush and close temporary"
376                                 " receiving file `%s'", tmpfilename);
377       if (rename(tmpfilename, filename))
378         diee("could not install new version of destination file `%s'",
379              filename);
380
381       sendbyte(REPLMSG_ACK);
382       break;
383
384     default:
385       die_protocol("unknown transfer message code 0x%02x",c);
386
387     }
388   }
389 }
390
391 static void sender(const char *filename) {
392   FILE *f, *fold;
393   int interval_usec, r, c;
394   struct stat stabtest, stab;
395   enum { told_nothing, told_file, told_remove } told;
396
397   interval_usec= 0;
398   fold= 0;
399   told= told_nothing;
400   
401   for (;;) {
402     if (interval_usec) {
403       send_flush();
404       usleep(interval_usec);
405     }
406     interval_usec= min_interval_usec;
407
408     r= stat(filename, &stabtest);
409     if (r) {
410       f= 0;
411     } else {
412       if (told == told_file &&
413           stabtest.st_mode  == stab.st_mode  &&
414           stabtest.st_dev   == stab.st_dev   &&
415           stabtest.st_ino   == stab.st_ino   &&
416           stabtest.st_mtime == stab.st_mtime &&
417           stabtest.st_size  == stab.st_size)
418         continue;
419       f= fopen(filename, "rb");
420     }
421     
422     if (!f) {
423       if (errno!=ENOENT) diee("could not access source file `%s'",filename);
424       if (told != told_remove) {
425         verbosespinprintf
426           (" ENOENT                                                    ");
427         sendbyte(REPLMSG_RM);
428         told= told_remove;
429       }
430       continue;
431     }
432
433     if (fold) fclose(fold);
434     fold= 0;
435
436     r= fstat(fileno(f),&stab);
437     if (r) diee("could not fstat source file `%s'",filename);
438
439     if (!S_ISREG(stab.st_mode))
440       die(8,0,-1,"source file `%s' is not a plain file",filename);
441
442     uint8_t hbuf[9]= {
443       REPLMSG_FILE64,
444       stab.st_size >> 28 >> 28,
445       stab.st_size >> 24 >> 24,
446       stab.st_size >> 16 >> 24,
447       stab.st_size >>  8 >> 24,
448       stab.st_size       >> 24,
449       stab.st_size       >> 16,
450       stab.st_size       >>  8,
451       stab.st_size
452     };
453
454     bandlimit_sendstart();
455
456     mfwritecommso(hbuf,9);
457
458     copyfile(f, copydie_inputfile,filename,
459              commso, copydie_commso,0,
460              stab.st_size, 1);
461
462     send_flush();
463
464     c= fgetc(commsi);  if (c==EOF) die_badrecv("ack");
465     if (c!=REPLMSG_ACK) die_protocol("got %#02x instead of ACK",c);
466
467     bandlimit_sendend(stab.st_size, &interval_usec);
468
469     fold= f;
470     told= told_file;
471   }
472 }
473
474 typedef struct {
475   const char *userhost, *path;
476 } FileSpecification;
477
478 static FileSpecification srcspec, dstspec;
479
480 static void of__server(const struct cmdinfo *ci, const char *val) {
481   int ncount= nsargs + 1 + !!val;
482   sargs= xrealloc(sargs, sizeof(*sargs) * ncount);
483   sargs[nsargs++]= ci->olong;
484   if (val)
485     sargs[nsargs++]= val;
486 }
487
488 static int of__server_int(const struct cmdinfo *ci, const char *val) {
489   of__server(ci,val);
490   long v;
491   char *ep;
492   errno= 0; v= strtol(val,&ep,10);
493   if (!*val || *ep || errno || v<INT_MIN || v>INT_MAX)
494     badusage("bad integer argument `%s' for --%s",val,ci->olong);
495   return v;
496 }
497
498 static void of_help(const struct cmdinfo *ci, const char *val) {
499   usagemessage();
500   if (ferror(stdout)) diee("could not write usage message to stdout");
501   exit(0);
502 }
503
504 static void of_bw(const struct cmdinfo *ci, const char *val) {
505   int pct= of__server_int(ci,val);
506   if (pct<1 || pct>100)
507     badusage("bandwidth percentage must be between 1 and 100 inclusive");
508   *(double*)ci->parg= pct * 0.01;
509 }
510
511 static void of_server_int(const struct cmdinfo *ci, const char *val) {
512   *(int*)ci->parg= of__server_int(ci,val);
513 }
514
515 void usagemessage(void) {
516   printf(
517          "usage: rcopy-repeatedly [<options>] <file> <file>\n"
518          "  <file> may be <local-file> or [<user>@]<host>:<file>\n"
519          "  exactly one of each of the two forms must be provided\n"
520          "  a file is taken as remote if it has a : before the first /\n"
521          "general options:\n"
522          "  --help\n"
523          "  --quiet | -q\n"
524          "options for bandwidth (and cpu time) control:\n"
525          "  --max-bandwidth-percent  (default %d)\n"
526          "  --tx-block-size      (default/max %d)\n"
527          "  --min-interval-usec  (default %d)\n"
528          "options for finding programs:\n"
529          "  --rcopy-repeatedly  (default: rcopy-repeatedly)\n"
530          "  --rsh-program       (default: $RCOPY_REPEATEDLY_RSH or $RSYNC_RSH or ssh)\n"
531          "options passed to server side via ssh:\n"
532          "  --receiver --sender, bandwidth control options\n",
533          (int)(max_bw_prop*100), (int)sizeof(mainbuf), min_interval_usec);
534 }
535
536 static const struct cmdinfo cmdinfos[]= {
537   { "help",     .call= of_help },
538   { "max-bandwidth-percent", 0,1,.call=of_bw,.parg=&max_bw_prop            },
539   { "tx-block-size",0,     1,.call=of_server_int, .parg=&txblocksz         },
540   { "min-interval-usec",0, 1,.call=of_server_int, .parg=&min_interval_usec },
541   { "rcopy-repeatedly",0,  1, .sassignto=&rcopy_repeatedly_program         },
542   { "rsh-program",0,       1, .sassignto=&rsh_program                      },
543   { "quiet",'q',  .iassignto= &verbose,       .arg=0                       },
544   { "receiver",   .iassignto= &server_upcopy, .arg=0                       },
545   { "sender",     .iassignto= &server_upcopy, .arg=1                       },
546   { 0 }
547 };
548
549 static void server(const char *filename) {
550   int c, l;
551   char buf[2];
552
553   udchar= server_upcopy?'u':'d';
554
555   commsi= stdin;
556   commso= stdout;
557   l= generate_declaration();
558   fprintf(commso, "%s%04x\n", banner, l);
559   mfwritecommso(mainbuf, l);
560   send_flush();
561
562   c= fgetc(commsi);
563   if (c==EOF) {
564     if (feof(commsi)) exit(14);
565     assert(ferror(commsi));  die_badrecv("initial START message");
566   }
567   if (c!=REPLMSG_START) die_protocol("initial START was %#02x instead",c);
568
569   mfreadcommsi(buf,2,"START l");
570   l= (buf[0] << 8) | buf[1];
571
572   read_declaration(l);
573
574   if (server_upcopy)
575     sender(filename);
576   else
577     receiver(filename);
578 }
579
580 static void client(void) {
581   int uppipe[2], downpipe[2], r;
582   pid_t child;
583   FileSpecification *remotespec;
584   const char *remotemode;
585
586   mpipe(uppipe);
587   mpipe(downpipe);
588
589   if (srcspec.userhost) {
590     udchar= 'u';
591     remotespec= &srcspec;
592     remotemode= "--sender";
593   } else {
594     udchar= 'd';
595     remotespec= &dstspec;
596     remotemode= "--receiver";
597   }
598
599   sargs= xrealloc(sargs, sizeof(*sargs) * (7 + nsargs));
600   memmove(sargs+5, sargs, sizeof(*sargs) * nsargs);
601   sargs[0]= rsh_program;
602   sargs[1]= remotespec->userhost;
603   sargs[2]= rcopy_repeatedly_program;
604   sargs[3]= remotemode;
605   sargs[4]= "--";
606   sargs[5+nsargs]= remotespec->path;
607   sargs[6+nsargs]= 0;
608     
609   child= fork();
610   if (child==-1) diee("fork failed");
611   if (!child) {
612     mdup2(downpipe[0],0);
613     mdup2(uppipe[1],1);
614     close(uppipe[0]); close(downpipe[0]);
615     close(uppipe[1]); close(downpipe[1]);
616
617     execvp(rsh_program, (char**)sargs);
618     diee("failed to execute rsh program `%s'",rsh_program);
619   }
620
621   commso= fdopen(downpipe[1],"wb");
622   commsi= fdopen(uppipe[0],"rb");
623   if (!commso || !commsi) diee("fdopen failed");
624   close(downpipe[0]);
625   close(uppipe[1]);
626   
627   char banbuf[sizeof(banner)-1 + 5 + 1];
628   r= fread(banbuf,1,sizeof(banbuf)-1,commsi);
629   if (ferror(commsi)) die_badrecv("read banner");
630
631   if (r!=sizeof(banbuf)-1 ||
632       memcmp(banbuf,banner,sizeof(banner)-1) ||
633       banbuf[sizeof(banner)-1 + 4] != '\n') {
634     const char **sap;
635     int count=0;
636     for (count=0, sap=sargs; *sap; sap++) count+= strlen(*sap)+1;
637     char *cmdline= xmalloc(count+1);
638     cmdline[0]=' ';
639     for (sap=sargs; *sap; sap++) {
640       strcat(cmdline," ");
641       strcat(cmdline,*sap);
642     }
643     
644     die(8,0,-1,"did not receive banner as expected -"
645         " shell dirty? ssh broken?\n"
646         " try running\n"
647         "  %s\n"
648         " and expect the first line to be\n"
649         "  %s",
650         cmdline, banner);
651   }
652   
653   banbuf[sizeof(banbuf)-1]= 0;
654   char *ep;
655   long decllen= strtoul(banbuf + sizeof(banner)-1, &ep, 16);
656   if (ep != banbuf + sizeof(banner)-1 + 4)
657     die_protocol("declaration length syntax error");
658
659   read_declaration(decllen);
660
661   int l= generate_declaration();
662   sendbyte(REPLMSG_START);
663   sendbyte((l >> 8) & 0x0ff);
664   sendbyte( l       & 0x0ff);
665   mfwritecommso(mainbuf,l);
666
667   if (remotespec==&srcspec)
668     receiver(dstspec.path);
669   else
670     sender(srcspec.path);
671 }
672
673 static void parse_file_specification(FileSpecification *fs, const char *arg,
674                                      const char *what) {
675   const char *colon;
676   
677   if (!arg) badusage("too few arguments - missing %s\n",what);
678
679   for (colon=arg; ; colon++) {
680     if (!*colon || *colon=='/') {
681       fs->userhost=0;
682       fs->path= arg;
683       return;
684     }
685     if (*colon==':') {
686       char *uh= xmalloc(colon-arg + 1);
687       memcpy(uh,arg, colon-arg);  uh[colon-arg]= 0;
688       fs->userhost= uh;
689       fs->path= colon+1;
690       return;
691     }
692   }
693 }
694
695 int main(int argc, const char *const *argv) {
696   setvbuf(stderr,0,_IOLBF,BUFSIZ);
697
698   myopt(&argv, cmdinfos);
699
700   if (!rsh_program) rsh_program= getenv("RCOPY_REPEATEDLY_RSH");
701   if (!rsh_program) rsh_program= getenv("RSYNC_RSH");
702   if (!rsh_program) rsh_program= "ssh";
703
704   if (txblocksz<1) badusage("transmit block size must be at least 1");
705   if (min_interval_usec<0) badusage("minimum update interval may not be -ve");
706
707   if (server_upcopy>=0) {
708     if (!argv[0] || argv[1])
709       badusage("server mode must have just the filename as non-option arg");
710     server(argv[0]);
711   } else {
712     parse_file_specification(&srcspec, argv[0], "source");
713     parse_file_specification(&dstspec, argv[1], "destination");
714     if (argv[2]) badusage("too many non-option arguments");
715     if (!!srcspec.userhost == !!dstspec.userhost)
716       badusage("need exactly one remote file argument");
717     client();
718   }
719   return 0;
720 }