fetch.c revision 63720
162216Sdes/*- 262216Sdes * Copyright (c) 2000 Dag-Erling Co�dan Sm�rgrav 362216Sdes * All rights reserved. 462216Sdes * 562216Sdes * Redistribution and use in source and binary forms, with or without 662216Sdes * modification, are permitted provided that the following conditions 762216Sdes * are met: 862216Sdes * 1. Redistributions of source code must retain the above copyright 962216Sdes * notice, this list of conditions and the following disclaimer 1062216Sdes * in this position and unchanged. 1162216Sdes * 2. Redistributions in binary form must reproduce the above copyright 1262216Sdes * notice, this list of conditions and the following disclaimer in the 1362216Sdes * documentation and/or other materials provided with the distribution. 1462216Sdes * 3. The name of the author may not be used to endorse or promote products 1562216Sdes * derived from this software without specific prior written permission 1662216Sdes * 1762216Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 1862216Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1962216Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2062216Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2162216Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2262216Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2362216Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2462216Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2562216Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2662216Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2762216Sdes * 2862216Sdes * $FreeBSD: head/usr.bin/fetch/fetch.c 63720 2000-07-21 14:21:29Z des $ 2962216Sdes */ 3062216Sdes 3162216Sdes#include <sys/param.h> 3262216Sdes#include <sys/stat.h> 3362216Sdes#include <sys/socket.h> 3462216Sdes 3562216Sdes#include <ctype.h> 3662216Sdes#include <err.h> 3762216Sdes#include <errno.h> 3863235Sdes#include <signal.h> 3962216Sdes#include <stdio.h> 4062216Sdes#include <stdlib.h> 4162216Sdes#include <string.h> 4262216Sdes#include <sysexits.h> 4362216Sdes#include <unistd.h> 4462216Sdes 4562216Sdes#include <fetch.h> 4662216Sdes 4762216Sdes#define MINBUFSIZE 4096 4862216Sdes 4962216Sdes/* Option flags */ 5062216Sdesint A_flag; /* -A: do not follow 302 redirects */ 5162216Sdesint a_flag; /* -a: auto retry */ 5262216Sdessize_t B_size; /* -B: buffer size */ 5362216Sdesint b_flag; /*! -b: workaround TCP bug */ 5462254Sdeschar *c_dirname; /* -c: remote directory */ 5562216Sdesint d_flag; /* -d: direct connection */ 5662216Sdesint F_flag; /* -F: restart without checking mtime */ 5762216Sdeschar *f_filename; /* -f: file to fetch */ 5862216Sdesint H_flag; /* -H: use high port */ 5962216Sdeschar *h_hostname; /* -h: host to fetch from */ 6062216Sdesint l_flag; /* -l: link rather than copy file: URLs */ 6162815Sdesint m_flag; /* -[Mm]: mirror mode */ 6262815Sdesint n_flag; /* -n: do not preserve modification time */ 6362216Sdesint o_flag; /* -o: specify output file */ 6462216Sdesint o_directory; /* output file is a directory */ 6562216Sdeschar *o_filename; /* name of output file */ 6662216Sdesint o_stdout; /* output file is stdout */ 6762216Sdesint once_flag; /* -1: stop at first successful file */ 6863501Sdesint p_flag; /* -[Pp]: use passive FTP */ 6962216Sdesint R_flag; /* -R: don't delete partially transferred files */ 7062216Sdesint r_flag; /* -r: restart previously interrupted transfer */ 7162216Sdesu_int T_secs = 0; /* -T: transfer timeout in seconds */ 7262216Sdesint s_flag; /* -s: show size, don't fetch */ 7362216Sdesoff_t S_size; /* -S: require size to match */ 7462216Sdesint t_flag; /*! -t: workaround TCP bug */ 7562216Sdesint v_level = 1; /* -v: verbosity level */ 7662216Sdesint v_tty; /* stdout is a tty */ 7762216Sdesu_int w_secs; /* -w: retry delay */ 7862216Sdesint family = PF_UNSPEC; /* -[46]: address family to use */ 7962216Sdes 8063015Sdesint sigalrm; /* SIGALRM received */ 8163015Sdesint sigint; /* SIGINT received */ 8262216Sdes 8362216Sdesu_int ftp_timeout; /* default timeout for FTP transfers */ 8462216Sdesu_int http_timeout; /* default timeout for HTTP transfers */ 8562216Sdesu_char *buf; /* transfer buffer */ 8662216Sdes 8762216Sdes 8862216Sdesvoid 8962216Sdessig_handler(int sig) 9062216Sdes{ 9163015Sdes switch (sig) { 9263015Sdes case SIGALRM: 9363015Sdes sigalrm = 1; 9463015Sdes break; 9563015Sdes case SIGINT: 9663015Sdes sigint = 1; 9763015Sdes break; 9863015Sdes } 9962216Sdes} 10062216Sdes 10162216Sdesstruct xferstat { 10262216Sdes char name[40]; 10362216Sdes struct timeval start; 10462216Sdes struct timeval end; 10562216Sdes struct timeval last; 10662216Sdes off_t size; 10762216Sdes off_t offset; 10862216Sdes off_t rcvd; 10962216Sdes}; 11062216Sdes 11162216Sdesvoid 11263046Sdesstat_display(struct xferstat *xs, int force) 11362216Sdes{ 11462216Sdes struct timeval now; 11562216Sdes 11663717Sdes if (!v_tty || !v_level) 11762216Sdes return; 11862216Sdes 11962216Sdes gettimeofday(&now, NULL); 12063046Sdes if (!force && now.tv_sec <= xs->last.tv_sec) 12162216Sdes return; 12262216Sdes xs->last = now; 12362216Sdes 12462216Sdes fprintf(stderr, "\rReceiving %s", xs->name); 12562216Sdes if (xs->size == -1) 12663046Sdes fprintf(stderr, ": %lld bytes", xs->rcvd); 12762216Sdes else 12863005Sdes fprintf(stderr, " (%lld bytes): %d%%", xs->size, 12963067Sdes (int)((100.0 * xs->rcvd) / xs->size)); 13062216Sdes} 13162216Sdes 13262216Sdesvoid 13363046Sdesstat_start(struct xferstat *xs, char *name, off_t size, off_t offset) 13463046Sdes{ 13563046Sdes snprintf(xs->name, sizeof xs->name, "%s", name); 13663046Sdes gettimeofday(&xs->start, NULL); 13763046Sdes xs->last.tv_sec = xs->last.tv_usec = 0; 13863046Sdes xs->end = xs->last; 13963046Sdes xs->size = size; 14063046Sdes xs->offset = offset; 14163067Sdes xs->rcvd = offset; 14263046Sdes stat_display(xs, 1); 14363046Sdes} 14463046Sdes 14563046Sdesvoid 14663046Sdesstat_update(struct xferstat *xs, off_t rcvd, int force) 14763046Sdes{ 14863046Sdes xs->rcvd = rcvd; 14963046Sdes stat_display(xs, 0); 15063046Sdes} 15163046Sdes 15263046Sdesvoid 15362216Sdesstat_end(struct xferstat *xs) 15462216Sdes{ 15562216Sdes double delta; 15662216Sdes double bps; 15763720Sdes 15863720Sdes if (!v_level) 15963720Sdes return; 16062216Sdes 16162216Sdes gettimeofday(&xs->end, NULL); 16262216Sdes 16363046Sdes stat_display(xs, 1); 16462216Sdes fputc('\n', stderr); 16562216Sdes delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6)) 16662216Sdes - (xs->start.tv_sec + (xs->start.tv_usec / 1.e6)); 16762216Sdes fprintf(stderr, "%lld bytes transferred in %.1f seconds ", 16863005Sdes xs->rcvd - xs->offset, delta); 16963005Sdes bps = (xs->rcvd - xs->offset) / delta; 17062216Sdes if (bps > 1024*1024) 17162216Sdes fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024)); 17262216Sdes else if (bps > 1024) 17362216Sdes fprintf(stderr, "(%.2f kBps)\n", bps / 1024); 17462216Sdes else 17562216Sdes fprintf(stderr, "(%.2f Bps)\n", bps); 17662216Sdes} 17762216Sdes 17862216Sdesint 17962216Sdesfetch(char *URL, char *path) 18062216Sdes{ 18162216Sdes struct url *url; 18262216Sdes struct url_stat us; 18362216Sdes struct stat sb; 18462216Sdes struct xferstat xs; 18562216Sdes FILE *f, *of; 18662216Sdes size_t size; 18762216Sdes off_t count; 18862216Sdes char flags[8]; 18963046Sdes int n, r; 19062216Sdes u_int timeout; 19162216Sdes 19262216Sdes f = of = NULL; 19362216Sdes 19462216Sdes /* parse URL */ 19562216Sdes if ((url = fetchParseURL(URL)) == NULL) { 19662216Sdes warnx("%s: parse error", URL); 19762216Sdes goto failure; 19862216Sdes } 19962216Sdes 20062216Sdes timeout = 0; 20162216Sdes *flags = 0; 20263345Sdes count = 0; 20362216Sdes 20462216Sdes /* common flags */ 20563353Sdes if (v_level > 1) 20662216Sdes strcat(flags, "v"); 20762216Sdes switch (family) { 20862216Sdes case PF_INET: 20962216Sdes strcat(flags, "4"); 21062216Sdes break; 21162216Sdes case PF_INET6: 21262216Sdes strcat(flags, "6"); 21362216Sdes break; 21462216Sdes } 21562216Sdes 21662216Sdes /* FTP specific flags */ 21762216Sdes if (strcmp(url->scheme, "ftp") == 0) { 21862216Sdes if (p_flag) 21962216Sdes strcat(flags, "p"); 22062216Sdes if (d_flag) 22162216Sdes strcat(flags, "d"); 22262216Sdes if (H_flag) 22362216Sdes strcat(flags, "h"); 22462216Sdes timeout = T_secs ? T_secs : ftp_timeout; 22562216Sdes } 22662216Sdes 22762216Sdes /* HTTP specific flags */ 22862216Sdes if (strcmp(url->scheme, "http") == 0) { 22962216Sdes if (d_flag) 23062216Sdes strcat(flags, "d"); 23162216Sdes if (A_flag) 23262216Sdes strcat(flags, "A"); 23362216Sdes timeout = T_secs ? T_secs : http_timeout; 23462216Sdes } 23562216Sdes 23663015Sdes /* set the protocol timeout. */ 23762216Sdes fetchTimeout = timeout; 23862216Sdes 23962216Sdes /* just print size */ 24062216Sdes if (s_flag) { 24163345Sdes if (fetchStat(url, &us, flags) == -1) 24263345Sdes goto failure; 24362216Sdes if (us.size == -1) 24462216Sdes printf("Unknown\n"); 24562216Sdes else 24662216Sdes printf("%lld\n", us.size); 24762216Sdes goto success; 24862216Sdes } 24962216Sdes 25063345Sdes /* 25163345Sdes * If the -r flag was specified, we have to compare the local and 25263345Sdes * remote files, so we should really do a fetchStat() first, but I 25363345Sdes * know of at least one HTTP server that only sends the content 25463345Sdes * size in response to GET requests, and leaves it out of replies 25563345Sdes * to HEAD requests. Also, in the (frequent) case that the local 25663345Sdes * and remote files match but the local file is truncated, we have 25763345Sdes * sufficient information *before* the compare to issue a correct 25863345Sdes * request. Therefore, we always issue a GET request as if we were 25963345Sdes * sure the local file was a truncated copy of the remote file; we 26063345Sdes * can drop the connection later if we change our minds. 26163345Sdes */ 26263345Sdes if (r_flag && !o_stdout && stat(path, &sb) != -1) 26363345Sdes url->offset = sb.st_size; 26463568Sdes else 26563568Sdes sb.st_size = 0; 26663345Sdes 26763345Sdes /* start the transfer */ 26863345Sdes if ((f = fetchXGet(url, &us, flags)) == NULL) { 26963345Sdes warnx("%s: %s", path, fetchLastErrString); 27062216Sdes goto failure; 27162216Sdes } 27263345Sdes if (sigint) 27363345Sdes goto signal; 27462216Sdes 27563345Sdes /* check that size is as expected */ 27663345Sdes if (S_size) { 27763345Sdes if (us.size == -1) { 27863345Sdes warnx("%s: size unknown", path); 27963345Sdes goto failure; 28063345Sdes } else if (us.size != S_size) { 28163345Sdes warnx("%s: size mismatch: expected %lld, actual %lld", 28263345Sdes path, S_size, us.size); 28363345Sdes goto failure; 28463345Sdes } 28563345Sdes } 28663345Sdes 28762216Sdes /* symlink instead of copy */ 28862216Sdes if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) { 28962216Sdes if (symlink(url->doc, path) == -1) { 29062216Sdes warn("%s: symlink()", path); 29162216Sdes goto failure; 29262216Sdes } 29362216Sdes goto success; 29462216Sdes } 29562216Sdes 29663717Sdes if (v_level > 1) { 29763717Sdes if (sb.st_size) 29863717Sdes warnx("local: %lld / %ld", sb.st_size, sb.st_mtime); 29963717Sdes warnx("remote: %lld / %ld", us.size, us.mtime); 30063717Sdes } 30163717Sdes 30263345Sdes /* open output file */ 30362216Sdes if (o_stdout) { 30462216Sdes /* output to stdout */ 30562216Sdes of = stdout; 30663568Sdes } else if (sb.st_size) { 30763345Sdes /* resume mode, local file exists */ 30863345Sdes if (!F_flag && us.mtime && sb.st_mtime != us.mtime) { 30963345Sdes /* no match! have to refetch */ 31063345Sdes fclose(f); 31163345Sdes url->offset = 0; 31263345Sdes if ((f = fetchXGet(url, &us, flags)) == NULL) { 31363345Sdes warnx("%s: %s", path, fetchLastErrString); 31463345Sdes goto failure; 31563345Sdes } 31663345Sdes if (sigint) 31763345Sdes goto signal; 31863345Sdes } else { 31963568Sdes if (us.size == sb.st_size) 32063568Sdes /* nothing to do */ 32163568Sdes goto success; 32263568Sdes if (sb.st_size > us.size) { 32363568Sdes /* local file too long! */ 32463568Sdes warnx("%s: local file (%lld bytes) is longer " 32563568Sdes "than remote file (%lld bytes)", 32663568Sdes path, sb.st_size, us.size); 32763568Sdes goto failure; 32863568Sdes } 32963568Sdes /* we got through, open local file and seek to offset */ 33063568Sdes /* 33163568Sdes * XXX there's a race condition here - the file we open is not 33263568Sdes * necessarily the same as the one we stat()'ed earlier... 33363568Sdes */ 33463568Sdes if ((of = fopen(path, "a")) == NULL) { 33563568Sdes warn("%s: fopen()", path); 33663568Sdes goto failure; 33763568Sdes } 33863568Sdes if (fseek(of, url->offset, SEEK_SET) == -1) { 33963568Sdes warn("%s: fseek()", path); 34063568Sdes goto failure; 34163568Sdes } 34263345Sdes } 34363345Sdes } 34463345Sdes if (m_flag && stat(path, &sb) != -1) { 34563345Sdes /* mirror mode, local file exists */ 34662216Sdes if (sb.st_size == us.size && sb.st_mtime == us.mtime) 34763345Sdes goto success; 34863345Sdes } 34963345Sdes if (!of) { 35063345Sdes /* 35163345Sdes * We don't yet have an output file; either this is a vanilla 35263345Sdes * run with no special flags, or the local and remote files 35363345Sdes * didn't match. 35463345Sdes */ 35562216Sdes if ((of = fopen(path, "w")) == NULL) { 35662216Sdes warn("%s: open()", path); 35762216Sdes goto failure; 35862216Sdes } 35962216Sdes } 36062216Sdes count = url->offset; 36162216Sdes 36262216Sdes /* start the counter */ 36362216Sdes stat_start(&xs, path, us.size, count); 36462216Sdes 36563015Sdes sigint = sigalrm = 0; 36663046Sdes 36763046Sdes /* suck in the data */ 36863046Sdes for (n = 0; !sigint && !sigalrm; ++n) { 36963046Sdes if (us.size != -1 && us.size - count < B_size) 37063046Sdes size = us.size - count; 37163046Sdes else 37263046Sdes size = B_size; 37363046Sdes if (timeout) 37463046Sdes alarm(timeout); 37563046Sdes if ((size = fread(buf, 1, size, f)) <= 0) 37663046Sdes break; 37763046Sdes stat_update(&xs, count += size, 0); 37863046Sdes if (fwrite(buf, size, 1, of) != 1) 37963046Sdes break; 38062216Sdes } 38163015Sdes 38262216Sdes if (timeout) 38362216Sdes alarm(0); 38462216Sdes 38562216Sdes stat_end(&xs); 38663015Sdes 38762216Sdes /* Set mtime of local file */ 38863046Sdes if (!n_flag && us.mtime && !o_stdout) { 38962216Sdes struct timeval tv[2]; 39062216Sdes 39163046Sdes fflush(of); 39263046Sdes tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime); 39362216Sdes tv[1].tv_sec = (long)us.mtime; 39462216Sdes tv[0].tv_usec = tv[1].tv_usec = 0; 39562216Sdes if (utimes(path, tv)) 39662216Sdes warn("%s: utimes()", path); 39762216Sdes } 39862216Sdes 39963046Sdes /* timed out or interrupted? */ 40063345Sdes signal: 40163015Sdes if (sigalrm) 40263015Sdes warnx("transfer timed out"); 40363046Sdes if (sigint) 40463015Sdes warnx("transfer interrupted"); 40563046Sdes 40663235Sdes if (!sigalrm && !sigint) { 40763235Sdes /* check the status of our files */ 40863235Sdes if (ferror(f)) 40963235Sdes warn("%s", URL); 41063235Sdes if (ferror(of)) 41163235Sdes warn("%s", path); 41263235Sdes if (ferror(f) || ferror(of)) 41363235Sdes goto failure; 41463235Sdes } 41563046Sdes 41663046Sdes /* did the transfer complete normally? */ 41763046Sdes if (us.size != -1 && count < us.size) { 41862815Sdes warnx("%s appears to be truncated: %lld/%lld bytes", 41962815Sdes path, count, us.size); 42063046Sdes goto failure_keep; 42162815Sdes } 42262815Sdes 42362216Sdes success: 42463046Sdes r = 0; 42562216Sdes goto done; 42662216Sdes failure: 42763046Sdes if (of && of != stdout && !R_flag && !r_flag) 42863046Sdes unlink(path); 42963046Sdes failure_keep: 43062216Sdes r = -1; 43162216Sdes goto done; 43262216Sdes done: 43362216Sdes if (f) 43462216Sdes fclose(f); 43562216Sdes if (of && of != stdout) 43662216Sdes fclose(of); 43762837Sdes if (url) 43862837Sdes fetchFreeURL(url); 43962216Sdes return r; 44062216Sdes} 44162216Sdes 44262216Sdesvoid 44362216Sdesusage(void) 44462216Sdes{ 44562216Sdes /* XXX badly out of synch */ 44662216Sdes fprintf(stderr, 44762216Sdes "Usage: fetch [-1AFHMPRabdlmnpqrstv] [-o outputfile] [-S bytes]\n" 44862216Sdes " [-B bytes] [-T seconds] [-w seconds]\n" 44962216Sdes " [-f file -h host [-c dir] | URL ...]\n" 45062216Sdes ); 45162216Sdes} 45262216Sdes 45362216Sdes 45462216Sdes#define PARSENUM(NAME, TYPE) \ 45562216Sdesint \ 45662216SdesNAME(char *s, TYPE *v) \ 45762216Sdes{ \ 45862216Sdes *v = 0; \ 45962216Sdes for (*v = 0; *s; s++) \ 46062216Sdes if (isdigit(*s)) \ 46162216Sdes *v = *v * 10 + *s - '0'; \ 46262216Sdes else \ 46362216Sdes return -1; \ 46462216Sdes return 0; \ 46562216Sdes} 46662216Sdes 46762216SdesPARSENUM(parseint, u_int) 46862216SdesPARSENUM(parsesize, size_t) 46962216SdesPARSENUM(parseoff, off_t) 47062216Sdes 47162216Sdesint 47262216Sdesmain(int argc, char *argv[]) 47362216Sdes{ 47462216Sdes struct stat sb; 47563235Sdes struct sigaction sa; 47662216Sdes char *p, *q, *s; 47762216Sdes int c, e, r; 47862216Sdes 47962216Sdes while ((c = getopt(argc, argv, 48062254Sdes "146AaB:bc:dFf:h:lHMmnPpo:qRrS:sT:tvw:")) != EOF) 48162216Sdes switch (c) { 48262216Sdes case '1': 48362216Sdes once_flag = 1; 48462216Sdes break; 48562216Sdes case '4': 48662216Sdes family = PF_INET; 48762216Sdes break; 48862216Sdes case '6': 48962216Sdes family = PF_INET6; 49062216Sdes break; 49162216Sdes case 'A': 49262216Sdes A_flag = 1; 49362216Sdes break; 49462216Sdes case 'a': 49562216Sdes a_flag = 1; 49662216Sdes break; 49762216Sdes case 'B': 49862216Sdes if (parsesize(optarg, &B_size) == -1) 49962216Sdes errx(1, "invalid buffer size"); 50062216Sdes break; 50162216Sdes case 'b': 50262216Sdes warnx("warning: the -b option is deprecated"); 50362216Sdes b_flag = 1; 50462216Sdes break; 50562254Sdes case 'c': 50662254Sdes c_dirname = optarg; 50762254Sdes break; 50862216Sdes case 'd': 50962216Sdes d_flag = 1; 51062216Sdes break; 51162216Sdes case 'F': 51262216Sdes F_flag = 1; 51362216Sdes break; 51462216Sdes case 'f': 51562216Sdes f_filename = optarg; 51662216Sdes break; 51762216Sdes case 'H': 51862216Sdes H_flag = 1; 51962216Sdes break; 52062216Sdes case 'h': 52162216Sdes h_hostname = optarg; 52262216Sdes break; 52362216Sdes case 'l': 52462216Sdes l_flag = 1; 52562216Sdes break; 52662216Sdes case 'o': 52762216Sdes o_flag = 1; 52862216Sdes o_filename = optarg; 52962216Sdes break; 53062216Sdes case 'M': 53162216Sdes case 'm': 53263345Sdes if (r_flag) 53363345Sdes errx(1, "the -m and -r flags are mutually exclusive"); 53462216Sdes m_flag = 1; 53562216Sdes break; 53662216Sdes case 'n': 53762815Sdes n_flag = 1; 53862216Sdes break; 53962216Sdes case 'P': 54062216Sdes case 'p': 54162216Sdes p_flag = 1; 54262216Sdes break; 54362216Sdes case 'q': 54462216Sdes v_level = 0; 54562216Sdes break; 54662216Sdes case 'R': 54762216Sdes R_flag = 1; 54862216Sdes break; 54962216Sdes case 'r': 55063345Sdes if (m_flag) 55163345Sdes errx(1, "the -m and -r flags are mutually exclusive"); 55262216Sdes r_flag = 1; 55362216Sdes break; 55462216Sdes case 'S': 55562216Sdes if (parseoff(optarg, &S_size) == -1) 55662216Sdes errx(1, "invalid size"); 55762216Sdes break; 55862216Sdes case 's': 55962216Sdes s_flag = 1; 56062216Sdes break; 56162216Sdes case 'T': 56262216Sdes if (parseint(optarg, &T_secs) == -1) 56362216Sdes errx(1, "invalid timeout"); 56462216Sdes break; 56562216Sdes case 't': 56662216Sdes t_flag = 1; 56762216Sdes warnx("warning: the -t option is deprecated"); 56862216Sdes break; 56962216Sdes case 'v': 57062216Sdes v_level++; 57162216Sdes break; 57262216Sdes case 'w': 57362216Sdes a_flag = 1; 57462216Sdes if (parseint(optarg, &w_secs) == -1) 57562216Sdes errx(1, "invalid delay"); 57662216Sdes break; 57762216Sdes default: 57862216Sdes usage(); 57962216Sdes exit(EX_USAGE); 58062216Sdes } 58162216Sdes 58262216Sdes argc -= optind; 58362216Sdes argv += optind; 58462216Sdes 58562254Sdes if (h_hostname || f_filename || c_dirname) { 58662216Sdes if (!h_hostname || !f_filename || argc) { 58762216Sdes usage(); 58862216Sdes exit(EX_USAGE); 58962216Sdes } 59062216Sdes /* XXX this is a hack. */ 59162216Sdes if (strcspn(h_hostname, "@:/") != strlen(h_hostname)) 59262216Sdes errx(1, "invalid hostname"); 59362254Sdes if (asprintf(argv, "ftp://%s/%s/%s", h_hostname, 59462254Sdes c_dirname ? c_dirname : "", f_filename) == -1) 59562216Sdes errx(1, strerror(ENOMEM)); 59662216Sdes argc++; 59762216Sdes } 59863345Sdes 59962216Sdes if (!argc) { 60062216Sdes usage(); 60162216Sdes exit(EX_USAGE); 60262216Sdes } 60362216Sdes 60462216Sdes /* allocate buffer */ 60562216Sdes if (B_size < MINBUFSIZE) 60662216Sdes B_size = MINBUFSIZE; 60762216Sdes if ((buf = malloc(B_size)) == NULL) 60862216Sdes errx(1, strerror(ENOMEM)); 60962216Sdes 61063235Sdes /* timeouts */ 61162216Sdes if ((s = getenv("FTP_TIMEOUT")) != NULL) { 61262216Sdes if (parseint(s, &ftp_timeout) == -1) { 61362216Sdes warnx("FTP_TIMEOUT is not a positive integer"); 61462216Sdes ftp_timeout = 0; 61562216Sdes } 61662216Sdes } 61762216Sdes if ((s = getenv("HTTP_TIMEOUT")) != NULL) { 61862216Sdes if (parseint(s, &http_timeout) == -1) { 61962216Sdes warnx("HTTP_TIMEOUT is not a positive integer"); 62062216Sdes http_timeout = 0; 62162216Sdes } 62262216Sdes } 62362216Sdes 62463235Sdes /* signal handling */ 62563235Sdes sa.sa_flags = 0; 62663235Sdes sa.sa_handler = sig_handler; 62763235Sdes sigemptyset(&sa.sa_mask); 62863345Sdes sigaction(SIGALRM, &sa, NULL); 62963345Sdes sa.sa_flags = SA_RESETHAND; 63063345Sdes sigaction(SIGINT, &sa, NULL); 63163345Sdes fetchRestartCalls = 0; 63263015Sdes 63362216Sdes /* output file */ 63462216Sdes if (o_flag) { 63562216Sdes if (strcmp(o_filename, "-") == 0) { 63662216Sdes o_stdout = 1; 63762216Sdes } else if (stat(o_filename, &sb) == -1) { 63862216Sdes if (errno == ENOENT) { 63962216Sdes if (argc > 1) 64062216Sdes errx(EX_USAGE, "%s is not a directory", o_filename); 64162216Sdes } else { 64262216Sdes err(EX_IOERR, "%s", o_filename); 64362216Sdes } 64462216Sdes } else { 64562216Sdes if (sb.st_mode & S_IFDIR) 64662216Sdes o_directory = 1; 64762216Sdes } 64862216Sdes } 64962216Sdes 65062216Sdes /* check if output is to a tty (for progress report) */ 65162815Sdes v_tty = isatty(STDERR_FILENO); 65262216Sdes r = 0; 65362216Sdes 65462216Sdes while (argc) { 65562216Sdes if ((p = strrchr(*argv, '/')) == NULL) 65662216Sdes p = *argv; 65762216Sdes else 65862216Sdes p++; 65962216Sdes 66062216Sdes if (!*p) 66162216Sdes p = "fetch.out"; 66262216Sdes 66362216Sdes fetchLastErrCode = 0; 66462216Sdes 66562216Sdes if (o_flag) { 66662216Sdes if (o_stdout) { 66762216Sdes e = fetch(*argv, "-"); 66862216Sdes } else if (o_directory) { 66962216Sdes asprintf(&q, "%s/%s", o_filename, p); 67062216Sdes e = fetch(*argv, q); 67162216Sdes free(q); 67262216Sdes } else { 67362216Sdes e = fetch(*argv, o_filename); 67462216Sdes } 67562216Sdes } else { 67662216Sdes e = fetch(*argv, p); 67762216Sdes } 67862216Sdes 67963015Sdes if (sigint) 68063345Sdes kill(getpid(), SIGINT); 68163015Sdes 68262216Sdes if (e == 0 && once_flag) 68362216Sdes exit(0); 68462216Sdes 68562216Sdes if (e) { 68662216Sdes r = 1; 68762216Sdes if ((fetchLastErrCode 68862216Sdes && fetchLastErrCode != FETCH_UNAVAIL 68962216Sdes && fetchLastErrCode != FETCH_MOVED 69062216Sdes && fetchLastErrCode != FETCH_URL 69162216Sdes && fetchLastErrCode != FETCH_RESOLV 69262216Sdes && fetchLastErrCode != FETCH_UNKNOWN)) { 69362216Sdes if (w_secs) { 69462216Sdes if (v_level) 69563353Sdes fprintf(stderr, "Waiting %d seconds before retrying\n", 69663353Sdes w_secs); 69762216Sdes sleep(w_secs); 69862216Sdes } 69962216Sdes if (a_flag) 70062216Sdes continue; 70162216Sdes } 70262216Sdes } 70362216Sdes 70462216Sdes argc--, argv++; 70562216Sdes } 70662216Sdes 70762216Sdes exit(r); 70862216Sdes} 709