]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/fetch/fetch.c
While I'm at it, break a line that was too long, remove a pointless diagnostic
[FreeBSD/FreeBSD.git] / usr.bin / fetch / fetch.c
1 /*-
2  * Copyright (c) 2000 Dag-Erling Coïdan Smørgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  *      $FreeBSD$
29  */
30
31 #include <sys/param.h>
32 #include <sys/stat.h>
33 #include <sys/socket.h>
34
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <signal.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sysexits.h>
43 #include <unistd.h>
44
45 #include <fetch.h>
46
47 #define MINBUFSIZE      4096
48
49 /* Option flags */
50 int      A_flag;        /*    -A: do not follow 302 redirects */
51 int      a_flag;        /*    -a: auto retry */
52 size_t   B_size;        /*    -B: buffer size */
53 int      b_flag;        /*!   -b: workaround TCP bug */
54 char    *c_dirname;     /*    -c: remote directory */
55 int      d_flag;        /*    -d: direct connection */
56 int      F_flag;        /*    -F: restart without checking mtime  */
57 char    *f_filename;    /*    -f: file to fetch */
58 int      H_flag;        /*    -H: use high port */
59 char    *h_hostname;    /*    -h: host to fetch from */
60 int      l_flag;        /*    -l: link rather than copy file: URLs */
61 int      m_flag;        /* -[Mm]: mirror mode */
62 int      n_flag;        /*    -n: do not preserve modification time */
63 int      o_flag;        /*    -o: specify output file */
64 int      o_directory;   /*        output file is a directory */
65 char    *o_filename;    /*        name of output file */
66 int      o_stdout;      /*        output file is stdout */
67 int      once_flag;     /*    -1: stop at first successful file */
68 int      p_flag = 1;    /* -[Pp]: use passive FTP */
69 int      R_flag;        /*    -R: don't delete partially transferred files */
70 int      r_flag;        /*    -r: restart previously interrupted transfer */
71 u_int    T_secs = 0;    /*    -T: transfer timeout in seconds */
72 int      s_flag;        /*    -s: show size, don't fetch */
73 off_t    S_size;        /*    -S: require size to match */
74 int      t_flag;        /*!   -t: workaround TCP bug */
75 int      v_level = 1;   /*    -v: verbosity level */
76 int      v_tty;         /*        stdout is a tty */
77 u_int    w_secs;        /*    -w: retry delay */
78 int      family = PF_UNSPEC;    /* -[46]: address family to use */
79
80 int      sigalrm;       /* SIGALRM received */
81 int      sigint;        /* SIGINT received */
82
83 u_int    ftp_timeout;   /* default timeout for FTP transfers */
84 u_int    http_timeout;  /* default timeout for HTTP transfers */
85 u_char  *buf;           /* transfer buffer */
86
87
88 void
89 sig_handler(int sig)
90 {
91     switch (sig) {
92     case SIGALRM:
93         sigalrm = 1;
94         break;
95     case SIGINT:
96         sigint = 1;
97         break;
98     }
99 }
100
101 struct xferstat {
102     char                 name[40];
103     struct timeval       start;
104     struct timeval       end;
105     struct timeval       last;
106     off_t                size;
107     off_t                offset;
108     off_t                rcvd;
109 };
110
111 void
112 stat_display(struct xferstat *xs, int force)
113 {
114     struct timeval now;
115     
116     if (!v_tty)
117         return;
118     
119     gettimeofday(&now, NULL);
120     if (!force && now.tv_sec <= xs->last.tv_sec)
121         return;
122     xs->last = now;
123     
124     fprintf(stderr, "\rReceiving %s", xs->name);
125     if (xs->size == -1)
126         fprintf(stderr, ": %lld bytes", xs->rcvd);
127     else
128         fprintf(stderr, " (%lld bytes): %d%%", xs->size,
129                 (int)((100.0 * xs->rcvd) / xs->size));
130 }
131
132 void
133 stat_start(struct xferstat *xs, char *name, off_t size, off_t offset)
134 {
135     snprintf(xs->name, sizeof xs->name, "%s", name);
136     gettimeofday(&xs->start, NULL);
137     xs->last.tv_sec = xs->last.tv_usec = 0;
138     xs->end = xs->last;
139     xs->size = size;
140     xs->offset = offset;
141     xs->rcvd = offset;
142     stat_display(xs, 1);
143 }
144
145 void
146 stat_update(struct xferstat *xs, off_t rcvd, int force)
147 {
148     xs->rcvd = rcvd;
149     stat_display(xs, 0);
150 }
151
152 void
153 stat_end(struct xferstat *xs)
154 {
155     double delta;
156     double bps;
157     
158     gettimeofday(&xs->end, NULL);
159     
160     stat_display(xs, 1);
161     fputc('\n', stderr);
162     delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6))
163         - (xs->start.tv_sec + (xs->start.tv_usec / 1.e6));
164     fprintf(stderr, "%lld bytes transferred in %.1f seconds ",
165             xs->rcvd - xs->offset, delta);
166     bps = (xs->rcvd - xs->offset) / delta;
167     if (bps > 1024*1024)
168         fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024));
169     else if (bps > 1024)
170         fprintf(stderr, "(%.2f kBps)\n", bps / 1024);
171     else
172         fprintf(stderr, "(%.2f Bps)\n", bps);
173 }
174
175 int
176 fetch(char *URL, char *path)
177 {
178     struct url *url;
179     struct url_stat us;
180     struct stat sb;
181     struct xferstat xs;
182     FILE *f, *of;
183     size_t size;
184     off_t count;
185     char flags[8];
186     int n, r;
187     u_int timeout;
188
189     f = of = NULL;
190
191     /* parse URL */
192     if ((url = fetchParseURL(URL)) == NULL) {
193         warnx("%s: parse error", URL);
194         goto failure;
195     }
196
197     timeout = 0;
198     *flags = 0;
199     count = 0;
200
201     /* common flags */
202     if (v_level > 1)
203         strcat(flags, "v");
204     switch (family) {
205     case PF_INET:
206         strcat(flags, "4");
207         break;
208     case PF_INET6:
209         strcat(flags, "6");
210         break;
211     }
212
213     /* FTP specific flags */
214     if (strcmp(url->scheme, "ftp") == 0) {
215         if (p_flag)
216             strcat(flags, "p");
217         if (d_flag)
218             strcat(flags, "d");
219         if (H_flag)
220             strcat(flags, "h");
221         timeout = T_secs ? T_secs : ftp_timeout;
222     }
223     
224     /* HTTP specific flags */
225     if (strcmp(url->scheme, "http") == 0) {
226         if (d_flag)
227             strcat(flags, "d");
228         if (A_flag)
229             strcat(flags, "A");
230         timeout = T_secs ? T_secs : http_timeout;
231     }
232
233     /* set the protocol timeout. */
234     fetchTimeout = timeout;
235
236     /* just print size */
237     if (s_flag) {
238         if (fetchStat(url, &us, flags) == -1)
239             goto failure;
240         if (us.size == -1)
241             printf("Unknown\n");
242         else
243             printf("%lld\n", us.size);
244         goto success;
245     }
246
247     /*
248      * If the -r flag was specified, we have to compare the local and
249      * remote files, so we should really do a fetchStat() first, but I
250      * know of at least one HTTP server that only sends the content
251      * size in response to GET requests, and leaves it out of replies
252      * to HEAD requests. Also, in the (frequent) case that the local
253      * and remote files match but the local file is truncated, we have
254      * sufficient information *before* the compare to issue a correct
255      * request. Therefore, we always issue a GET request as if we were
256      * sure the local file was a truncated copy of the remote file; we
257      * can drop the connection later if we change our minds.
258      */
259     if (r_flag && !o_stdout && stat(path, &sb) != -1)
260         url->offset = sb.st_size;
261      
262     /* start the transfer */
263     if ((f = fetchXGet(url, &us, flags)) == NULL) {
264         warnx("%s: %s", path, fetchLastErrString);
265         goto failure;
266     }
267     if (sigint)
268         goto signal;
269     
270     /* check that size is as expected */
271     if (S_size) {
272         if (us.size == -1) {
273             warnx("%s: size unknown", path);
274             goto failure;
275         } else if (us.size != S_size) {
276             warnx("%s: size mismatch: expected %lld, actual %lld",
277                   path, S_size, us.size);
278             goto failure;
279         }
280     }
281     
282     /* symlink instead of copy */
283     if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
284         if (symlink(url->doc, path) == -1) {
285             warn("%s: symlink()", path);
286             goto failure;
287         }
288         goto success;
289     }
290
291     /* open output file */
292     if (o_stdout) {
293         /* output to stdout */
294         of = stdout;
295     } else if (url->offset) {
296         /* resume mode, local file exists */
297         if (!F_flag && us.mtime && sb.st_mtime != us.mtime) {
298             /* no match! have to refetch */
299             fclose(f);
300             url->offset = 0;
301             if ((f = fetchXGet(url, &us, flags)) == NULL) {
302                 warnx("%s: %s", path, fetchLastErrString);
303                 goto failure;
304             }
305             if (sigint)
306                 goto signal;
307         } else {
308             us.size += url->offset;
309         }
310         if (us.size == sb.st_size)
311             /* nothing to do */
312             goto success;
313         if (sb.st_size > us.size) {
314             /* local file too long! */
315             warnx("%s: local file (%lld bytes) is longer "
316                   "than remote file (%lld bytes)",
317                   path, sb.st_size, us.size);
318             goto failure;
319         }
320         /* we got through, open local file in append mode */
321         /*
322          * XXX there's a race condition here - the file we open is not
323          * necessarily the same as the one we stat()'ed earlier...
324          */
325         if ((of = fopen(path, "a")) == NULL) {
326             warn("%s: open()", path);
327             goto failure;
328         }
329     }
330     if (m_flag && stat(path, &sb) != -1) {
331         /* mirror mode, local file exists */
332         if (sb.st_size == us.size && sb.st_mtime == us.mtime)
333             goto success;
334     }
335     if (!of) {
336         /*
337          * We don't yet have an output file; either this is a vanilla
338          * run with no special flags, or the local and remote files
339          * didn't match.
340          */
341         if ((of = fopen(path, "w")) == NULL) {
342             warn("%s: open()", path);
343             goto failure;
344         }
345     }
346     count = url->offset;
347
348     /* start the counter */
349     stat_start(&xs, path, us.size, count);
350
351     sigint = sigalrm = 0;
352
353     /* suck in the data */
354     for (n = 0; !sigint && !sigalrm; ++n) {
355         if (us.size != -1 && us.size - count < B_size)
356             size = us.size - count;
357         else
358             size = B_size;
359         if (timeout)
360             alarm(timeout);
361         if ((size = fread(buf, 1, size, f)) <= 0)
362             break;
363         stat_update(&xs, count += size, 0);
364         if (fwrite(buf, size, 1, of) != 1)
365             break;
366     }
367
368     if (timeout)
369         alarm(0);
370
371     stat_end(&xs);
372
373     /* Set mtime of local file */
374     if (!n_flag && us.mtime && !o_stdout) {
375         struct timeval tv[2];
376         
377         fflush(of);
378         tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
379         tv[1].tv_sec = (long)us.mtime;
380         tv[0].tv_usec = tv[1].tv_usec = 0;
381         if (utimes(path, tv))
382             warn("%s: utimes()", path);
383     }
384     
385     /* timed out or interrupted? */
386  signal:
387     if (sigalrm)
388         warnx("transfer timed out");
389     if (sigint)
390         warnx("transfer interrupted");
391     
392     if (!sigalrm && !sigint) {
393         /* check the status of our files */
394         if (ferror(f))
395             warn("%s", URL);
396         if (ferror(of))
397             warn("%s", path);
398         if (ferror(f) || ferror(of))
399             goto failure;
400     }
401
402     /* did the transfer complete normally? */
403     if (us.size != -1 && count < us.size) {
404         warnx("%s appears to be truncated: %lld/%lld bytes",
405               path, count, us.size);
406         goto failure_keep;
407     }
408     
409  success:
410     r = 0;
411     goto done;
412  failure:
413     if (of && of != stdout && !R_flag && !r_flag)
414         unlink(path);
415  failure_keep:
416     r = -1;
417     goto done;
418  done:
419     if (f)
420         fclose(f);
421     if (of && of != stdout)
422         fclose(of);
423     if (url)
424         fetchFreeURL(url);
425     return r;
426 }
427
428 void
429 usage(void)
430 {
431     /* XXX badly out of synch */
432     fprintf(stderr,
433             "Usage: fetch [-1AFHMPRabdlmnpqrstv] [-o outputfile] [-S bytes]\n"
434             "             [-B bytes] [-T seconds] [-w seconds]\n"
435             "             [-f file -h host [-c dir] | URL ...]\n"
436         );
437 }
438
439
440 #define PARSENUM(NAME, TYPE)            \
441 int                                     \
442 NAME(char *s, TYPE *v)                  \
443 {                                       \
444     *v = 0;                             \
445     for (*v = 0; *s; s++)               \
446         if (isdigit(*s))                \
447             *v = *v * 10 + *s - '0';    \
448         else                            \
449             return -1;                  \
450     return 0;                           \
451 }
452
453 PARSENUM(parseint, u_int)
454 PARSENUM(parsesize, size_t)
455 PARSENUM(parseoff, off_t)
456
457 int
458 main(int argc, char *argv[])
459 {
460     struct stat sb;
461     struct sigaction sa;
462     char *p, *q, *s;
463     int c, e, r;
464
465     while ((c = getopt(argc, argv,
466                        "146AaB:bc:dFf:h:lHMmnPpo:qRrS:sT:tvw:")) != EOF)
467         switch (c) {
468         case '1':
469             once_flag = 1;
470             break;
471         case '4':
472             family = PF_INET;
473             break;
474         case '6':
475             family = PF_INET6;
476             break;
477         case 'A':
478             A_flag = 1;
479             break;
480         case 'a':
481             a_flag = 1;
482             break;
483         case 'B':
484             if (parsesize(optarg, &B_size) == -1)
485                 errx(1, "invalid buffer size");
486             break;
487         case 'b':
488             warnx("warning: the -b option is deprecated");
489             b_flag = 1;
490             break;
491         case 'c':
492             c_dirname = optarg;
493             break;
494         case 'd':
495             d_flag = 1;
496             break;
497         case 'F':
498             F_flag = 1;
499             break;
500         case 'f':
501             f_filename = optarg;
502             break;
503         case 'H':
504             H_flag = 1;
505             break;
506         case 'h':
507             h_hostname = optarg;
508             break;
509         case 'l':
510             l_flag = 1;
511             break;
512         case 'o':
513             o_flag = 1;
514             o_filename = optarg;
515             break;
516         case 'M':
517         case 'm':
518             if (r_flag)
519                 errx(1, "the -m and -r flags are mutually exclusive");
520             m_flag = 1;
521             break;
522         case 'n':
523             n_flag = 1;
524             break;
525         case 'P':
526         case 'p':
527             p_flag = 1;
528             break;
529         case 'q':
530             v_level = 0;
531             break;
532         case 'R':
533             R_flag = 1;
534             break;
535         case 'r':
536             if (m_flag)
537                 errx(1, "the -m and -r flags are mutually exclusive");
538             r_flag = 1;
539             break;
540         case 'S':
541             if (parseoff(optarg, &S_size) == -1)
542                 errx(1, "invalid size");
543             break;
544         case 's':
545             s_flag = 1;
546             break;
547         case 'T':
548             if (parseint(optarg, &T_secs) == -1)
549                 errx(1, "invalid timeout");
550             break;
551         case 't':
552             t_flag = 1;
553             warnx("warning: the -t option is deprecated");
554             break;
555         case 'v':
556             v_level++;
557             break;
558         case 'w':
559             a_flag = 1;
560             if (parseint(optarg, &w_secs) == -1)
561                 errx(1, "invalid delay");
562             break;
563         default:
564             usage();
565             exit(EX_USAGE);
566         }
567
568     argc -= optind;
569     argv += optind;
570
571     if (h_hostname || f_filename || c_dirname) {
572         if (!h_hostname || !f_filename || argc) {
573             usage();
574             exit(EX_USAGE);
575         }
576         /* XXX this is a hack. */
577         if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
578             errx(1, "invalid hostname");
579         if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
580                      c_dirname ? c_dirname : "", f_filename) == -1)
581             errx(1, strerror(ENOMEM));
582         argc++;
583     }
584
585     if (!argc) {
586         usage();
587         exit(EX_USAGE);
588     }
589
590     /* allocate buffer */
591     if (B_size < MINBUFSIZE)
592         B_size = MINBUFSIZE;
593     if ((buf = malloc(B_size)) == NULL)
594         errx(1, strerror(ENOMEM));
595
596     /* timeouts */
597     if ((s = getenv("FTP_TIMEOUT")) != NULL) {
598         if (parseint(s, &ftp_timeout) == -1) {
599             warnx("FTP_TIMEOUT is not a positive integer");
600             ftp_timeout = 0;
601         }
602     }
603     if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
604         if (parseint(s, &http_timeout) == -1) {
605             warnx("HTTP_TIMEOUT is not a positive integer");
606             http_timeout = 0;
607         }
608     }
609
610     /* signal handling */
611     sa.sa_flags = 0;
612     sa.sa_handler = sig_handler;
613     sigemptyset(&sa.sa_mask);
614     sigaction(SIGALRM, &sa, NULL);
615     sa.sa_flags = SA_RESETHAND;
616     sigaction(SIGINT, &sa, NULL);
617     fetchRestartCalls = 0;
618     
619     /* output file */
620     if (o_flag) {
621         if (strcmp(o_filename, "-") == 0) {
622             o_stdout = 1;
623         } else if (stat(o_filename, &sb) == -1) {
624             if (errno == ENOENT) {
625                 if (argc > 1)
626                     errx(EX_USAGE, "%s is not a directory", o_filename);
627             } else {
628                 err(EX_IOERR, "%s", o_filename);
629             }
630         } else {
631             if (sb.st_mode & S_IFDIR)
632                 o_directory = 1;
633         }
634     }
635
636     /* check if output is to a tty (for progress report) */
637     v_tty = isatty(STDERR_FILENO);
638     r = 0;
639
640     while (argc) {
641         if ((p = strrchr(*argv, '/')) == NULL)
642             p = *argv;
643         else
644             p++;
645
646         if (!*p)
647             p = "fetch.out";
648         
649         fetchLastErrCode = 0;
650         
651         if (o_flag) {
652             if (o_stdout) {
653                 e = fetch(*argv, "-");
654             } else if (o_directory) {
655                 asprintf(&q, "%s/%s", o_filename, p);
656                 e = fetch(*argv, q);
657                 free(q);
658             } else {
659                 e = fetch(*argv, o_filename);
660             }
661         } else {
662             e = fetch(*argv, p);
663         }
664
665         if (sigint)
666             kill(getpid(), SIGINT);
667         
668         if (e == 0 && once_flag)
669             exit(0);
670         
671         if (e) {
672             r = 1;
673             if ((fetchLastErrCode
674                  && fetchLastErrCode != FETCH_UNAVAIL
675                  && fetchLastErrCode != FETCH_MOVED
676                  && fetchLastErrCode != FETCH_URL
677                  && fetchLastErrCode != FETCH_RESOLV
678                  && fetchLastErrCode != FETCH_UNKNOWN)) {
679                 if (w_secs) {
680                     if (v_level)
681                         fprintf(stderr, "Waiting %d seconds before retrying\n",
682                                 w_secs);
683                     sleep(w_secs);
684                 }
685                 if (a_flag)
686                     continue;
687             }
688         }
689
690         argc--, argv++;
691     }
692     
693     exit(r);
694 }