fetch.c revision 63501
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 63501 2000-07-19 09:12:36Z 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
11663046Sdes    if (!v_tty)
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;
15762216Sdes
15862216Sdes    gettimeofday(&xs->end, NULL);
15962216Sdes
16063046Sdes    stat_display(xs, 1);
16162216Sdes    fputc('\n', stderr);
16262216Sdes    delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6))
16362216Sdes	- (xs->start.tv_sec + (xs->start.tv_usec / 1.e6));
16462216Sdes    fprintf(stderr, "%lld bytes transferred in %.1f seconds ",
16563005Sdes	    xs->rcvd - xs->offset, delta);
16663005Sdes    bps = (xs->rcvd - xs->offset) / delta;
16762216Sdes    if (bps > 1024*1024)
16862216Sdes	fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024));
16962216Sdes    else if (bps > 1024)
17062216Sdes	fprintf(stderr, "(%.2f kBps)\n", bps / 1024);
17162216Sdes    else
17262216Sdes	fprintf(stderr, "(%.2f Bps)\n", bps);
17362216Sdes}
17462216Sdes
17562216Sdesint
17662216Sdesfetch(char *URL, char *path)
17762216Sdes{
17862216Sdes    struct url *url;
17962216Sdes    struct url_stat us;
18062216Sdes    struct stat sb;
18162216Sdes    struct xferstat xs;
18262216Sdes    FILE *f, *of;
18362216Sdes    size_t size;
18462216Sdes    off_t count;
18562216Sdes    char flags[8];
18663046Sdes    int n, r;
18762216Sdes    u_int timeout;
18862216Sdes
18962216Sdes    f = of = NULL;
19062216Sdes
19162216Sdes    /* parse URL */
19262216Sdes    if ((url = fetchParseURL(URL)) == NULL) {
19362216Sdes	warnx("%s: parse error", URL);
19462216Sdes	goto failure;
19562216Sdes    }
19662216Sdes
19762216Sdes    timeout = 0;
19862216Sdes    *flags = 0;
19963345Sdes    count = 0;
20062216Sdes
20162216Sdes    /* common flags */
20263353Sdes    if (v_level > 1)
20362216Sdes	strcat(flags, "v");
20462216Sdes    switch (family) {
20562216Sdes    case PF_INET:
20662216Sdes	strcat(flags, "4");
20762216Sdes	break;
20862216Sdes    case PF_INET6:
20962216Sdes	strcat(flags, "6");
21062216Sdes	break;
21162216Sdes    }
21262216Sdes
21362216Sdes    /* FTP specific flags */
21462216Sdes    if (strcmp(url->scheme, "ftp") == 0) {
21562216Sdes	if (p_flag)
21662216Sdes	    strcat(flags, "p");
21762216Sdes	if (d_flag)
21862216Sdes	    strcat(flags, "d");
21962216Sdes	if (H_flag)
22062216Sdes	    strcat(flags, "h");
22162216Sdes	timeout = T_secs ? T_secs : ftp_timeout;
22262216Sdes    }
22362216Sdes
22462216Sdes    /* HTTP specific flags */
22562216Sdes    if (strcmp(url->scheme, "http") == 0) {
22662216Sdes	if (d_flag)
22762216Sdes	    strcat(flags, "d");
22862216Sdes	if (A_flag)
22962216Sdes	    strcat(flags, "A");
23062216Sdes	timeout = T_secs ? T_secs : http_timeout;
23162216Sdes    }
23262216Sdes
23363015Sdes    /* set the protocol timeout. */
23462216Sdes    fetchTimeout = timeout;
23562216Sdes
23662216Sdes    /* just print size */
23762216Sdes    if (s_flag) {
23863345Sdes	if (fetchStat(url, &us, flags) == -1)
23963345Sdes	    goto failure;
24062216Sdes	if (us.size == -1)
24162216Sdes	    printf("Unknown\n");
24262216Sdes	else
24362216Sdes	    printf("%lld\n", us.size);
24462216Sdes	goto success;
24562216Sdes    }
24662216Sdes
24763345Sdes    /*
24863345Sdes     * If the -r flag was specified, we have to compare the local and
24963345Sdes     * remote files, so we should really do a fetchStat() first, but I
25063345Sdes     * know of at least one HTTP server that only sends the content
25163345Sdes     * size in response to GET requests, and leaves it out of replies
25263345Sdes     * to HEAD requests. Also, in the (frequent) case that the local
25363345Sdes     * and remote files match but the local file is truncated, we have
25463345Sdes     * sufficient information *before* the compare to issue a correct
25563345Sdes     * request. Therefore, we always issue a GET request as if we were
25663345Sdes     * sure the local file was a truncated copy of the remote file; we
25763345Sdes     * can drop the connection later if we change our minds.
25863345Sdes     */
25963345Sdes    if (r_flag && !o_stdout && stat(path, &sb) != -1)
26063345Sdes	url->offset = sb.st_size;
26163345Sdes
26263345Sdes    /* start the transfer */
26363345Sdes    if ((f = fetchXGet(url, &us, flags)) == NULL) {
26463345Sdes	warnx("%s: %s", path, fetchLastErrString);
26562216Sdes	goto failure;
26662216Sdes    }
26763345Sdes    if (sigint)
26863345Sdes	goto signal;
26962216Sdes
27063345Sdes    /* check that size is as expected */
27163345Sdes    if (S_size) {
27263345Sdes	if (us.size == -1) {
27363345Sdes	    warnx("%s: size unknown", path);
27463345Sdes	    goto failure;
27563345Sdes	} else if (us.size != S_size) {
27663345Sdes	    warnx("%s: size mismatch: expected %lld, actual %lld",
27763345Sdes		  path, S_size, us.size);
27863345Sdes	    goto failure;
27963345Sdes	}
28063345Sdes    }
28163345Sdes
28262216Sdes    /* symlink instead of copy */
28362216Sdes    if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
28462216Sdes	if (symlink(url->doc, path) == -1) {
28562216Sdes	    warn("%s: symlink()", path);
28662216Sdes	    goto failure;
28762216Sdes	}
28862216Sdes	goto success;
28962216Sdes    }
29062216Sdes
29163345Sdes    /* open output file */
29262216Sdes    if (o_stdout) {
29362216Sdes	/* output to stdout */
29462216Sdes	of = stdout;
29563345Sdes    } else if (url->offset) {
29663345Sdes	/* resume mode, local file exists */
29763345Sdes	if (!F_flag && us.mtime && sb.st_mtime != us.mtime) {
29863345Sdes	    /* no match! have to refetch */
29963345Sdes	    fclose(f);
30063345Sdes	    url->offset = 0;
30163345Sdes	    if ((f = fetchXGet(url, &us, flags)) == NULL) {
30263345Sdes		warnx("%s: %s", path, fetchLastErrString);
30363345Sdes		goto failure;
30463345Sdes	    }
30563345Sdes	    if (sigint)
30663345Sdes		goto signal;
30763345Sdes	} else {
30863345Sdes	    us.size += url->offset;
30963345Sdes	}
31062216Sdes	if (us.size == sb.st_size)
31163345Sdes	    /* nothing to do */
31262216Sdes	    goto success;
31363345Sdes	if (sb.st_size > us.size) {
31463345Sdes	    /* local file too long! */
31563345Sdes	    warnx("%s: local file (%lld bytes) is longer "
31663345Sdes		  "than remote file (%lld bytes)",
31763345Sdes		  path, sb.st_size, us.size);
31862216Sdes	    goto failure;
31962216Sdes	}
32063345Sdes	/* we got through, open local file in append mode */
32163345Sdes	/*
32263345Sdes	 * XXX there's a race condition here - the file we open is not
32363345Sdes         * necessarily the same as the one we stat()'ed earlier...
32463345Sdes	 */
32562216Sdes	if ((of = fopen(path, "a")) == NULL) {
32662216Sdes	    warn("%s: open()", path);
32762216Sdes	    goto failure;
32862216Sdes	}
32963345Sdes    }
33063345Sdes    if (m_flag && stat(path, &sb) != -1) {
33163345Sdes	/* mirror mode, local file exists */
33262216Sdes	if (sb.st_size == us.size && sb.st_mtime == us.mtime)
33363345Sdes	    goto success;
33463345Sdes    }
33563345Sdes    if (!of) {
33663345Sdes	/*
33763345Sdes	 * We don't yet have an output file; either this is a vanilla
33863345Sdes	 * run with no special flags, or the local and remote files
33963345Sdes	 * didn't match.
34063345Sdes	 */
34162216Sdes	if ((of = fopen(path, "w")) == NULL) {
34262216Sdes	    warn("%s: open()", path);
34362216Sdes	    goto failure;
34462216Sdes	}
34562216Sdes    }
34662216Sdes    count = url->offset;
34762216Sdes
34862216Sdes    /* start the counter */
34962216Sdes    stat_start(&xs, path, us.size, count);
35062216Sdes
35163015Sdes    sigint = sigalrm = 0;
35263046Sdes
35363046Sdes    /* suck in the data */
35463046Sdes    for (n = 0; !sigint && !sigalrm; ++n) {
35563046Sdes	if (us.size != -1 && us.size - count < B_size)
35663046Sdes	    size = us.size - count;
35763046Sdes	else
35863046Sdes	    size = B_size;
35963046Sdes	if (timeout)
36063046Sdes	    alarm(timeout);
36163046Sdes	if ((size = fread(buf, 1, size, f)) <= 0)
36263046Sdes	    break;
36363046Sdes	stat_update(&xs, count += size, 0);
36463046Sdes	if (fwrite(buf, size, 1, of) != 1)
36563046Sdes	    break;
36662216Sdes    }
36763015Sdes
36862216Sdes    if (timeout)
36962216Sdes	alarm(0);
37062216Sdes
37162216Sdes    stat_end(&xs);
37263015Sdes
37362216Sdes    /* Set mtime of local file */
37463046Sdes    if (!n_flag && us.mtime && !o_stdout) {
37562216Sdes	struct timeval tv[2];
37662216Sdes
37763046Sdes	fflush(of);
37863046Sdes	tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
37962216Sdes	tv[1].tv_sec = (long)us.mtime;
38062216Sdes	tv[0].tv_usec = tv[1].tv_usec = 0;
38162216Sdes	if (utimes(path, tv))
38262216Sdes	    warn("%s: utimes()", path);
38362216Sdes    }
38462216Sdes
38563046Sdes    /* timed out or interrupted? */
38663345Sdes signal:
38763015Sdes    if (sigalrm)
38863015Sdes	warnx("transfer timed out");
38963046Sdes    if (sigint)
39063015Sdes	warnx("transfer interrupted");
39163046Sdes
39263235Sdes    if (!sigalrm && !sigint) {
39363235Sdes	/* check the status of our files */
39463235Sdes	if (ferror(f))
39563235Sdes	    warn("%s", URL);
39663235Sdes	if (ferror(of))
39763235Sdes	    warn("%s", path);
39863235Sdes	if (ferror(f) || ferror(of))
39963235Sdes	    goto failure;
40063235Sdes    }
40163046Sdes
40263046Sdes    /* did the transfer complete normally? */
40363046Sdes    if (us.size != -1 && count < us.size) {
40462815Sdes	warnx("%s appears to be truncated: %lld/%lld bytes",
40562815Sdes	      path, count, us.size);
40663046Sdes	goto failure_keep;
40762815Sdes    }
40862815Sdes
40962216Sdes success:
41063046Sdes    r = 0;
41162216Sdes    goto done;
41262216Sdes failure:
41363046Sdes    if (of && of != stdout && !R_flag && !r_flag)
41463046Sdes	unlink(path);
41563046Sdes failure_keep:
41662216Sdes    r = -1;
41762216Sdes    goto done;
41862216Sdes done:
41962216Sdes    if (f)
42062216Sdes	fclose(f);
42162216Sdes    if (of && of != stdout)
42262216Sdes	fclose(of);
42362837Sdes    if (url)
42462837Sdes	fetchFreeURL(url);
42562216Sdes    return r;
42662216Sdes}
42762216Sdes
42862216Sdesvoid
42962216Sdesusage(void)
43062216Sdes{
43162216Sdes    /* XXX badly out of synch */
43262216Sdes    fprintf(stderr,
43362216Sdes	    "Usage: fetch [-1AFHMPRabdlmnpqrstv] [-o outputfile] [-S bytes]\n"
43462216Sdes	    "             [-B bytes] [-T seconds] [-w seconds]\n"
43562216Sdes	    "             [-f file -h host [-c dir] | URL ...]\n"
43662216Sdes	);
43762216Sdes}
43862216Sdes
43962216Sdes
44062216Sdes#define PARSENUM(NAME, TYPE)		\
44162216Sdesint					\
44262216SdesNAME(char *s, TYPE *v)			\
44362216Sdes{					\
44462216Sdes    *v = 0;				\
44562216Sdes    for (*v = 0; *s; s++)		\
44662216Sdes	if (isdigit(*s))		\
44762216Sdes	    *v = *v * 10 + *s - '0';	\
44862216Sdes	else				\
44962216Sdes	    return -1;			\
45062216Sdes    return 0;				\
45162216Sdes}
45262216Sdes
45362216SdesPARSENUM(parseint, u_int)
45462216SdesPARSENUM(parsesize, size_t)
45562216SdesPARSENUM(parseoff, off_t)
45662216Sdes
45762216Sdesint
45862216Sdesmain(int argc, char *argv[])
45962216Sdes{
46062216Sdes    struct stat sb;
46163235Sdes    struct sigaction sa;
46262216Sdes    char *p, *q, *s;
46362216Sdes    int c, e, r;
46462216Sdes
46562216Sdes    while ((c = getopt(argc, argv,
46662254Sdes		       "146AaB:bc:dFf:h:lHMmnPpo:qRrS:sT:tvw:")) != EOF)
46762216Sdes	switch (c) {
46862216Sdes	case '1':
46962216Sdes	    once_flag = 1;
47062216Sdes	    break;
47162216Sdes	case '4':
47262216Sdes	    family = PF_INET;
47362216Sdes	    break;
47462216Sdes	case '6':
47562216Sdes	    family = PF_INET6;
47662216Sdes	    break;
47762216Sdes	case 'A':
47862216Sdes	    A_flag = 1;
47962216Sdes	    break;
48062216Sdes	case 'a':
48162216Sdes	    a_flag = 1;
48262216Sdes	    break;
48362216Sdes	case 'B':
48462216Sdes	    if (parsesize(optarg, &B_size) == -1)
48562216Sdes		errx(1, "invalid buffer size");
48662216Sdes	    break;
48762216Sdes	case 'b':
48862216Sdes	    warnx("warning: the -b option is deprecated");
48962216Sdes	    b_flag = 1;
49062216Sdes	    break;
49162254Sdes	case 'c':
49262254Sdes	    c_dirname = optarg;
49362254Sdes	    break;
49462216Sdes	case 'd':
49562216Sdes	    d_flag = 1;
49662216Sdes	    break;
49762216Sdes	case 'F':
49862216Sdes	    F_flag = 1;
49962216Sdes	    break;
50062216Sdes	case 'f':
50162216Sdes	    f_filename = optarg;
50262216Sdes	    break;
50362216Sdes	case 'H':
50462216Sdes	    H_flag = 1;
50562216Sdes	    break;
50662216Sdes	case 'h':
50762216Sdes	    h_hostname = optarg;
50862216Sdes	    break;
50962216Sdes	case 'l':
51062216Sdes	    l_flag = 1;
51162216Sdes	    break;
51262216Sdes	case 'o':
51362216Sdes	    o_flag = 1;
51462216Sdes	    o_filename = optarg;
51562216Sdes	    break;
51662216Sdes	case 'M':
51762216Sdes	case 'm':
51863345Sdes	    if (r_flag)
51963345Sdes		errx(1, "the -m and -r flags are mutually exclusive");
52062216Sdes	    m_flag = 1;
52162216Sdes	    break;
52262216Sdes	case 'n':
52362815Sdes	    n_flag = 1;
52462216Sdes	    break;
52562216Sdes	case 'P':
52662216Sdes	case 'p':
52762216Sdes	    p_flag = 1;
52862216Sdes	    break;
52962216Sdes	case 'q':
53062216Sdes	    v_level = 0;
53162216Sdes	    break;
53262216Sdes	case 'R':
53362216Sdes	    R_flag = 1;
53462216Sdes	    break;
53562216Sdes	case 'r':
53663345Sdes	    if (m_flag)
53763345Sdes		errx(1, "the -m and -r flags are mutually exclusive");
53862216Sdes	    r_flag = 1;
53962216Sdes	    break;
54062216Sdes	case 'S':
54162216Sdes	    if (parseoff(optarg, &S_size) == -1)
54262216Sdes		errx(1, "invalid size");
54362216Sdes	    break;
54462216Sdes	case 's':
54562216Sdes	    s_flag = 1;
54662216Sdes	    break;
54762216Sdes	case 'T':
54862216Sdes	    if (parseint(optarg, &T_secs) == -1)
54962216Sdes		errx(1, "invalid timeout");
55062216Sdes	    break;
55162216Sdes	case 't':
55262216Sdes	    t_flag = 1;
55362216Sdes	    warnx("warning: the -t option is deprecated");
55462216Sdes	    break;
55562216Sdes	case 'v':
55662216Sdes	    v_level++;
55762216Sdes	    break;
55862216Sdes	case 'w':
55962216Sdes	    a_flag = 1;
56062216Sdes	    if (parseint(optarg, &w_secs) == -1)
56162216Sdes		errx(1, "invalid delay");
56262216Sdes	    break;
56362216Sdes	default:
56462216Sdes	    usage();
56562216Sdes	    exit(EX_USAGE);
56662216Sdes	}
56762216Sdes
56862216Sdes    argc -= optind;
56962216Sdes    argv += optind;
57062216Sdes
57162254Sdes    if (h_hostname || f_filename || c_dirname) {
57262216Sdes	if (!h_hostname || !f_filename || argc) {
57362216Sdes	    usage();
57462216Sdes	    exit(EX_USAGE);
57562216Sdes	}
57662216Sdes	/* XXX this is a hack. */
57762216Sdes	if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
57862216Sdes	    errx(1, "invalid hostname");
57962254Sdes	if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
58062254Sdes		     c_dirname ? c_dirname : "", f_filename) == -1)
58162216Sdes	    errx(1, strerror(ENOMEM));
58262216Sdes	argc++;
58362216Sdes    }
58463345Sdes
58562216Sdes    if (!argc) {
58662216Sdes	usage();
58762216Sdes	exit(EX_USAGE);
58862216Sdes    }
58962216Sdes
59062216Sdes    /* allocate buffer */
59162216Sdes    if (B_size < MINBUFSIZE)
59262216Sdes	B_size = MINBUFSIZE;
59362216Sdes    if ((buf = malloc(B_size)) == NULL)
59462216Sdes	errx(1, strerror(ENOMEM));
59562216Sdes
59663235Sdes    /* timeouts */
59762216Sdes    if ((s = getenv("FTP_TIMEOUT")) != NULL) {
59862216Sdes	if (parseint(s, &ftp_timeout) == -1) {
59962216Sdes	    warnx("FTP_TIMEOUT is not a positive integer");
60062216Sdes	    ftp_timeout = 0;
60162216Sdes	}
60262216Sdes    }
60362216Sdes    if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
60462216Sdes	if (parseint(s, &http_timeout) == -1) {
60562216Sdes	    warnx("HTTP_TIMEOUT is not a positive integer");
60662216Sdes	    http_timeout = 0;
60762216Sdes	}
60862216Sdes    }
60962216Sdes
61063235Sdes    /* signal handling */
61163235Sdes    sa.sa_flags = 0;
61263235Sdes    sa.sa_handler = sig_handler;
61363235Sdes    sigemptyset(&sa.sa_mask);
61463345Sdes    sigaction(SIGALRM, &sa, NULL);
61563345Sdes    sa.sa_flags = SA_RESETHAND;
61663345Sdes    sigaction(SIGINT, &sa, NULL);
61763345Sdes    fetchRestartCalls = 0;
61863015Sdes
61962216Sdes    /* output file */
62062216Sdes    if (o_flag) {
62162216Sdes	if (strcmp(o_filename, "-") == 0) {
62262216Sdes	    o_stdout = 1;
62362216Sdes	} else if (stat(o_filename, &sb) == -1) {
62462216Sdes	    if (errno == ENOENT) {
62562216Sdes		if (argc > 1)
62662216Sdes		    errx(EX_USAGE, "%s is not a directory", o_filename);
62762216Sdes	    } else {
62862216Sdes		err(EX_IOERR, "%s", o_filename);
62962216Sdes	    }
63062216Sdes	} else {
63162216Sdes	    if (sb.st_mode & S_IFDIR)
63262216Sdes		o_directory = 1;
63362216Sdes	}
63462216Sdes    }
63562216Sdes
63662216Sdes    /* check if output is to a tty (for progress report) */
63762815Sdes    v_tty = isatty(STDERR_FILENO);
63862216Sdes    r = 0;
63962216Sdes
64062216Sdes    while (argc) {
64162216Sdes	if ((p = strrchr(*argv, '/')) == NULL)
64262216Sdes	    p = *argv;
64362216Sdes	else
64462216Sdes	    p++;
64562216Sdes
64662216Sdes	if (!*p)
64762216Sdes	    p = "fetch.out";
64862216Sdes
64962216Sdes	fetchLastErrCode = 0;
65062216Sdes
65162216Sdes	if (o_flag) {
65262216Sdes	    if (o_stdout) {
65362216Sdes		e = fetch(*argv, "-");
65462216Sdes	    } else if (o_directory) {
65562216Sdes		asprintf(&q, "%s/%s", o_filename, p);
65662216Sdes		e = fetch(*argv, q);
65762216Sdes		free(q);
65862216Sdes	    } else {
65962216Sdes		e = fetch(*argv, o_filename);
66062216Sdes	    }
66162216Sdes	} else {
66262216Sdes	    e = fetch(*argv, p);
66362216Sdes	}
66462216Sdes
66563015Sdes	if (sigint)
66663345Sdes	    kill(getpid(), SIGINT);
66763015Sdes
66862216Sdes	if (e == 0 && once_flag)
66962216Sdes	    exit(0);
67062216Sdes
67162216Sdes	if (e) {
67262216Sdes	    r = 1;
67362216Sdes	    if ((fetchLastErrCode
67462216Sdes		 && fetchLastErrCode != FETCH_UNAVAIL
67562216Sdes		 && fetchLastErrCode != FETCH_MOVED
67662216Sdes		 && fetchLastErrCode != FETCH_URL
67762216Sdes		 && fetchLastErrCode != FETCH_RESOLV
67862216Sdes		 && fetchLastErrCode != FETCH_UNKNOWN)) {
67962216Sdes		if (w_secs) {
68062216Sdes		    if (v_level)
68163353Sdes			fprintf(stderr, "Waiting %d seconds before retrying\n",
68263353Sdes				w_secs);
68362216Sdes		    sleep(w_secs);
68462216Sdes		}
68562216Sdes		if (a_flag)
68662216Sdes		    continue;
68762216Sdes	    }
68862216Sdes	}
68962216Sdes
69062216Sdes	argc--, argv++;
69162216Sdes    }
69262216Sdes
69362216Sdes    exit(r);
69462216Sdes}
695