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