fetch.c revision 62254
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 62254 2000-06-29 10:32:56Z 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>
3862216Sdes#include <stdio.h>
3962216Sdes#include <stdlib.h>
4062216Sdes#include <string.h>
4162216Sdes#include <sysexits.h>
4262216Sdes#include <unistd.h>
4362216Sdes
4462216Sdes#include <fetch.h>
4562216Sdes
4662216Sdes#define MINBUFSIZE	4096
4762216Sdes
4862216Sdes/* Option flags */
4962216Sdesint	 A_flag;	/*    -A: do not follow 302 redirects */
5062216Sdesint	 a_flag;	/*    -a: auto retry */
5162216Sdessize_t	 B_size;	/*    -B: buffer size */
5262216Sdesint	 b_flag;	/*!   -b: workaround TCP bug */
5362254Sdeschar    *c_dirname;	/*    -c: remote directory */
5462216Sdesint	 d_flag;	/*    -d: direct connection */
5562216Sdesint	 F_flag;	/*    -F: restart without checking mtime  */
5662216Sdeschar	*f_filename;	/*    -f: file to fetch */
5762216Sdesint	 H_flag;	/*    -H: use high port */
5862216Sdeschar	*h_hostname;	/*    -h: host to fetch from */
5962216Sdesint	 l_flag;	/*    -l: link rather than copy file: URLs */
6062216Sdesint	 m_flag;	/* -[Mm]: set local timestamp to remote timestamp */
6162216Sdesint	 o_flag;	/*    -o: specify output file */
6262216Sdesint	 o_directory;	/*        output file is a directory */
6362216Sdeschar	*o_filename;	/*        name of output file */
6462216Sdesint	 o_stdout;	/*        output file is stdout */
6562216Sdesint	 once_flag;	/*    -1: stop at first successful file */
6662216Sdesint	 p_flag = 1;	/* -[Pp]: use passive FTP */
6762216Sdesint	 R_flag;	/*    -R: don't delete partially transferred files */
6862216Sdesint	 r_flag;	/*    -r: restart previously interrupted transfer */
6962216Sdesu_int	 T_secs = 0;	/*    -T: transfer timeout in seconds */
7062216Sdesint	 s_flag;        /*    -s: show size, don't fetch */
7162216Sdesoff_t	 S_size;        /*    -S: require size to match */
7262216Sdesint	 t_flag;	/*!   -t: workaround TCP bug */
7362216Sdesint	 v_level = 1;	/*    -v: verbosity level */
7462216Sdesint	 v_tty;		/*        stdout is a tty */
7562216Sdesu_int	 w_secs;	/*    -w: retry delay */
7662216Sdesint	 family = PF_UNSPEC;	/* -[46]: address family to use */
7762216Sdes
7862216Sdes
7962216Sdesu_int	 ftp_timeout;	/* default timeout for FTP transfers */
8062216Sdesu_int	 http_timeout;	/* default timeout for HTTP transfers */
8162216Sdesu_char	*buf;		/* transfer buffer */
8262216Sdes
8362216Sdes
8462216Sdesvoid
8562216Sdessig_handler(int sig)
8662216Sdes{
8762216Sdes    errx(1, "Transfer timed out");
8862216Sdes}
8962216Sdes
9062216Sdesstruct xferstat {
9162216Sdes    char		 name[40];
9262216Sdes    struct timeval	 start;
9362216Sdes    struct timeval	 end;
9462216Sdes    struct timeval	 last;
9562216Sdes    off_t		 size;
9662216Sdes    off_t		 offset;
9762216Sdes    off_t		 rcvd;
9862216Sdes};
9962216Sdes
10062216Sdesvoid
10162216Sdesstat_start(struct xferstat *xs, char *name, off_t size, off_t offset)
10262216Sdes{
10362216Sdes    snprintf(xs->name, sizeof xs->name, "%s", name);
10462216Sdes    xs->size = size;
10562216Sdes    xs->offset = offset;
10662216Sdes    if (v_level) {
10762216Sdes	fprintf(stderr, "Receiving %s", xs->name);
10862216Sdes	if (xs->size != -1)
10962216Sdes	    fprintf(stderr, " (%lld bytes)", xs->size - xs->offset);
11062216Sdes    }
11162216Sdes    gettimeofday(&xs->start, NULL);
11262216Sdes    xs->last = xs->start;
11362216Sdes}
11462216Sdes
11562216Sdesvoid
11662216Sdesstat_update(struct xferstat *xs, off_t rcvd)
11762216Sdes{
11862216Sdes    struct timeval now;
11962216Sdes
12062216Sdes    xs->rcvd = rcvd;
12162216Sdes
12262216Sdes    if (v_level <= 1 || !v_tty)
12362216Sdes	return;
12462216Sdes
12562216Sdes    gettimeofday(&now, NULL);
12662216Sdes    if (now.tv_sec <= xs->last.tv_sec)
12762216Sdes	return;
12862216Sdes    xs->last = now;
12962216Sdes
13062216Sdes    fprintf(stderr, "\rReceiving %s", xs->name);
13162216Sdes    if (xs->size == -1)
13262216Sdes	fprintf(stderr, ": %lld bytes", xs->rcvd - xs->offset);
13362216Sdes    else
13462216Sdes	fprintf(stderr, " (%lld bytes): %d%%", xs->size - xs->offset,
13562216Sdes		(int)((100.0 * xs->rcvd) / (xs->size - xs->offset)));
13662216Sdes}
13762216Sdes
13862216Sdesvoid
13962216Sdesstat_end(struct xferstat *xs)
14062216Sdes{
14162216Sdes    double delta;
14262216Sdes    double bps;
14362216Sdes
14462216Sdes    gettimeofday(&xs->end, NULL);
14562216Sdes
14662216Sdes    if (!v_level)
14762216Sdes	return;
14862216Sdes
14962216Sdes    fputc('\n', stderr);
15062216Sdes    delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6))
15162216Sdes	- (xs->start.tv_sec + (xs->start.tv_usec / 1.e6));
15262216Sdes    fprintf(stderr, "%lld bytes transferred in %.1f seconds ",
15362216Sdes	    xs->size - xs->offset, delta);
15462216Sdes    bps = (xs->size - xs->offset) / delta;
15562216Sdes    if (bps > 1024*1024)
15662216Sdes	fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024));
15762216Sdes    else if (bps > 1024)
15862216Sdes	fprintf(stderr, "(%.2f kBps)\n", bps / 1024);
15962216Sdes    else
16062216Sdes	fprintf(stderr, "(%.2f Bps)\n", bps);
16162216Sdes}
16262216Sdes
16362216Sdesint
16462216Sdesfetch(char *URL, char *path)
16562216Sdes{
16662216Sdes    struct url *url;
16762216Sdes    struct url_stat us;
16862216Sdes    struct stat sb;
16962216Sdes    struct xferstat xs;
17062216Sdes    FILE *f, *of;
17162216Sdes    size_t size;
17262216Sdes    off_t count;
17362216Sdes    char flags[8];
17462216Sdes    int ch, n, r;
17562216Sdes    u_int timeout;
17662216Sdes
17762216Sdes    f = of = NULL;
17862216Sdes
17962216Sdes    /* parse URL */
18062216Sdes    if ((url = fetchParseURL(URL)) == NULL) {
18162216Sdes	warnx("%s: parse error", URL);
18262216Sdes	goto failure;
18362216Sdes    }
18462216Sdes
18562216Sdes    timeout = 0;
18662216Sdes    *flags = 0;
18762216Sdes
18862216Sdes    /* common flags */
18962216Sdes    if (v_level > 2)
19062216Sdes	strcat(flags, "v");
19162216Sdes    switch (family) {
19262216Sdes    case PF_INET:
19362216Sdes	strcat(flags, "4");
19462216Sdes	break;
19562216Sdes    case PF_INET6:
19662216Sdes	strcat(flags, "6");
19762216Sdes	break;
19862216Sdes    }
19962216Sdes
20062216Sdes    /* FTP specific flags */
20162216Sdes    if (strcmp(url->scheme, "ftp") == 0) {
20262216Sdes	if (p_flag)
20362216Sdes	    strcat(flags, "p");
20462216Sdes	if (d_flag)
20562216Sdes	    strcat(flags, "d");
20662216Sdes	if (H_flag)
20762216Sdes	    strcat(flags, "h");
20862216Sdes	timeout = T_secs ? T_secs : ftp_timeout;
20962216Sdes    }
21062216Sdes
21162216Sdes    /* HTTP specific flags */
21262216Sdes    if (strcmp(url->scheme, "http") == 0) {
21362216Sdes	if (d_flag)
21462216Sdes	    strcat(flags, "d");
21562216Sdes	if (A_flag)
21662216Sdes	    strcat(flags, "A");
21762216Sdes	timeout = T_secs ? T_secs : http_timeout;
21862216Sdes    }
21962216Sdes
22062216Sdes    /*
22162216Sdes     * Set the protocol timeout.
22262216Sdes     * This currently only works for FTP, so we still use
22362216Sdes     * alarm(timeout) further down.
22462216Sdes     */
22562216Sdes    fetchTimeout = timeout;
22662216Sdes
22762216Sdes    /* stat remote file */
22862216Sdes    alarm(timeout);
22962216Sdes    if (fetchStat(url, &us, flags) == -1)
23062216Sdes	warnx("%s: size not known", path);
23162216Sdes    alarm(timeout);
23262216Sdes
23362216Sdes    /* just print size */
23462216Sdes    if (s_flag) {
23562216Sdes	if (us.size == -1)
23662216Sdes	    printf("Unknown\n");
23762216Sdes	else
23862216Sdes	    printf("%lld\n", us.size);
23962216Sdes	goto success;
24062216Sdes    }
24162216Sdes
24262216Sdes    /* check that size is as expected */
24362216Sdes    if (S_size && us.size != -1 && us.size != S_size) {
24462216Sdes	warnx("%s: size mismatch: expected %lld, actual %lld",
24562216Sdes	      path, S_size, us.size);
24662216Sdes	goto failure;
24762216Sdes    }
24862216Sdes
24962216Sdes    /* symlink instead of copy */
25062216Sdes    if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
25162216Sdes	if (symlink(url->doc, path) == -1) {
25262216Sdes	    warn("%s: symlink()", path);
25362216Sdes	    goto failure;
25462216Sdes	}
25562216Sdes	goto success;
25662216Sdes    }
25762216Sdes
25862216Sdes    if (o_stdout) {
25962216Sdes	/* output to stdout */
26062216Sdes	of = stdout;
26162216Sdes    } else if (r_flag && us.size != -1 && stat(path, &sb) != -1
26262216Sdes	       && (F_flag || (us.mtime && sb.st_mtime == us.mtime))) {
26362216Sdes	/* output to file, restart aborted transfer */
26462216Sdes	if (us.size == sb.st_size)
26562216Sdes	    goto success;
26662216Sdes	else if (sb.st_size > us.size && truncate(path, us.size) == -1) {
26762216Sdes	    warn("%s: truncate()", path);
26862216Sdes	    goto failure;
26962216Sdes	}
27062216Sdes	if ((of = fopen(path, "a")) == NULL) {
27162216Sdes	    warn("%s: open()", path);
27262216Sdes	    goto failure;
27362216Sdes	}
27462216Sdes	url->offset = sb.st_size;
27562216Sdes    } else if (m_flag && us.size != -1 && stat(path, &sb) != -1) {
27662216Sdes	/* output to file, mirror mode */
27762216Sdes	warnx(" local: %lld bytes, mtime %ld", sb.st_size, sb.st_mtime);
27862216Sdes	warnx("remote: %lld bytes, mtime %ld", us.size, us.mtime);
27962216Sdes	if (sb.st_size == us.size && sb.st_mtime == us.mtime)
28062216Sdes	    return 0;
28162216Sdes	if ((of = fopen(path, "w")) == NULL) {
28262216Sdes	    warn("%s: open()", path);
28362216Sdes	    goto failure;
28462216Sdes	}
28562216Sdes    } else {
28662216Sdes	/* output to file, all other cases */
28762216Sdes	if ((of = fopen(path, "w")) == NULL) {
28862216Sdes	    warn("%s: open()", path);
28962216Sdes	    goto failure;
29062216Sdes	}
29162216Sdes    }
29262216Sdes    count = url->offset;
29362216Sdes
29462216Sdes    /* start the transfer */
29562216Sdes    if ((f = fetchGet(url, flags)) == NULL) {
29662216Sdes	warnx("%s", fetchLastErrString);
29762245Sdes	if (!R_flag && !r_flag && !o_stdout)
29862245Sdes	    unlink(path);
29962216Sdes	goto failure;
30062216Sdes    }
30162216Sdes
30262216Sdes    /* start the counter */
30362216Sdes    stat_start(&xs, path, us.size, count);
30462216Sdes
30562216Sdes    n = 0;
30662216Sdes
30762216Sdes    if (us.size == -1) {
30862216Sdes	/*
30962216Sdes	 * We have no idea how much data to expect, so do it byte by
31062216Sdes         * byte. This is incredibly inefficient, but there's not much
31162216Sdes         * we can do about it... :(
31262216Sdes	 */
31362216Sdes	while (1) {
31462216Sdes	    if (timeout)
31562216Sdes		alarm(timeout);
31662216Sdes#ifdef STDIO_HACK
31762216Sdes	    /*
31862216Sdes	     * This is a non-portable hack, but it makes things go
31962216Sdes	     * faster. Basically, if there is data in the input file's
32062216Sdes	     * buffer, write it out; then fall through to the fgetc()
32162216Sdes	     * which forces a refill. It saves a memcpy() and reduces
32262216Sdes	     * the number of iterations, i.e the number of calls to
32362216Sdes	     * alarm(). Empirical evidence shows this can cut user
32462216Sdes	     * time by up to 90%. There may be better (even portable)
32562216Sdes	     * ways to do this.
32662216Sdes	     */
32762216Sdes	    if (f->_r && (f->_ub._base == NULL)) {
32862216Sdes		if (fwrite(f->_p, f->_r, 1, of) < 1)
32962216Sdes		    break;
33062216Sdes		count += f->_r;
33162216Sdes		f->_p += f->_r;
33262216Sdes		f->_r = 0;
33362216Sdes	    }
33462216Sdes#endif
33562216Sdes	    if ((ch = fgetc(f)) == EOF || fputc(ch, of) == EOF)
33662216Sdes		break;
33762216Sdes	    stat_update(&xs, count++);
33862216Sdes	    n++;
33962216Sdes	}
34062216Sdes    } else {
34162216Sdes	/* we know exactly how much to transfer, so do it efficiently */
34262216Sdes	for (size = B_size; count != us.size; n++) {
34362216Sdes	    if (us.size - count < B_size)
34462216Sdes		size = us.size - count;
34562216Sdes	    if (timeout)
34662216Sdes		alarm(timeout);
34762216Sdes	    if (fread(buf, size, 1, f) != 1 || fwrite(buf, size, 1, of) != 1)
34862216Sdes		break;
34962216Sdes	    stat_update(&xs, count += size);
35062216Sdes	}
35162216Sdes    }
35262216Sdes
35362216Sdes    if (timeout)
35462216Sdes	alarm(0);
35562216Sdes
35662216Sdes    stat_end(&xs);
35762216Sdes
35862216Sdes    /* check the status of our files */
35962216Sdes    if (ferror(f))
36062216Sdes	warn("%s", URL);
36162216Sdes    if (ferror(of))
36262216Sdes	warn("%s", path);
36362216Sdes    if (ferror(f) || ferror(of)) {
36462245Sdes	if (!R_flag && !r_flag && !o_stdout)
36562216Sdes	    unlink(path);
36662216Sdes	goto failure;
36762216Sdes    }
36862216Sdes
36962216Sdes    /* need to close the file before setting mtime */
37062216Sdes    if (of != stdout) {
37162216Sdes	fclose(of);
37262216Sdes	of = NULL;
37362216Sdes    }
37462216Sdes
37562216Sdes    /* Set mtime of local file */
37662216Sdes    if (m_flag && us.size != -1 && !o_stdout) {
37762216Sdes	struct timeval tv[2];
37862216Sdes
37962216Sdes	tv[0].tv_sec = (long)us.atime;
38062216Sdes	tv[1].tv_sec = (long)us.mtime;
38162216Sdes	tv[0].tv_usec = tv[1].tv_usec = 0;
38262216Sdes	if (utimes(path, tv))
38362216Sdes	    warn("%s: utimes()", path);
38462216Sdes    }
38562216Sdes
38662216Sdes success:
38762216Sdes    r = 0;
38862216Sdes    goto done;
38962216Sdes failure:
39062216Sdes    r = -1;
39162216Sdes    goto done;
39262216Sdes done:
39362216Sdes    if (f)
39462216Sdes	fclose(f);
39562216Sdes    if (of && of != stdout)
39662216Sdes	fclose(of);
39762216Sdes    fetchFreeURL(url);
39862216Sdes    return r;
39962216Sdes}
40062216Sdes
40162216Sdesvoid
40262216Sdesusage(void)
40362216Sdes{
40462216Sdes    /* XXX badly out of synch */
40562216Sdes    fprintf(stderr,
40662216Sdes	    "Usage: fetch [-1AFHMPRabdlmnpqrstv] [-o outputfile] [-S bytes]\n"
40762216Sdes	    "             [-B bytes] [-T seconds] [-w seconds]\n"
40862216Sdes	    "             [-f file -h host [-c dir] | URL ...]\n"
40962216Sdes	);
41062216Sdes}
41162216Sdes
41262216Sdes
41362216Sdes#define PARSENUM(NAME, TYPE)		\
41462216Sdesint					\
41562216SdesNAME(char *s, TYPE *v)			\
41662216Sdes{					\
41762216Sdes    *v = 0;				\
41862216Sdes    for (*v = 0; *s; s++)		\
41962216Sdes	if (isdigit(*s))		\
42062216Sdes	    *v = *v * 10 + *s - '0';	\
42162216Sdes	else				\
42262216Sdes	    return -1;			\
42362216Sdes    return 0;				\
42462216Sdes}
42562216Sdes
42662216SdesPARSENUM(parseint, u_int)
42762216SdesPARSENUM(parsesize, size_t)
42862216SdesPARSENUM(parseoff, off_t)
42962216Sdes
43062216Sdesint
43162216Sdesmain(int argc, char *argv[])
43262216Sdes{
43362216Sdes    struct stat sb;
43462216Sdes    char *p, *q, *s;
43562216Sdes    int c, e, r;
43662216Sdes
43762216Sdes    while ((c = getopt(argc, argv,
43862254Sdes		       "146AaB:bc:dFf:h:lHMmnPpo:qRrS:sT:tvw:")) != EOF)
43962216Sdes	switch (c) {
44062216Sdes	case '1':
44162216Sdes	    once_flag = 1;
44262216Sdes	    break;
44362216Sdes	case '4':
44462216Sdes	    family = PF_INET;
44562216Sdes	    break;
44662216Sdes	case '6':
44762216Sdes	    family = PF_INET6;
44862216Sdes	    break;
44962216Sdes	case 'A':
45062216Sdes	    A_flag = 1;
45162216Sdes	    break;
45262216Sdes	case 'a':
45362216Sdes	    a_flag = 1;
45462216Sdes	    break;
45562216Sdes	case 'B':
45662216Sdes	    if (parsesize(optarg, &B_size) == -1)
45762216Sdes		errx(1, "invalid buffer size");
45862216Sdes	    break;
45962216Sdes	case 'b':
46062216Sdes	    warnx("warning: the -b option is deprecated");
46162216Sdes	    b_flag = 1;
46262216Sdes	    break;
46362254Sdes	case 'c':
46462254Sdes	    c_dirname = optarg;
46562254Sdes	    break;
46662216Sdes	case 'd':
46762216Sdes	    d_flag = 1;
46862216Sdes	    break;
46962216Sdes	case 'F':
47062216Sdes	    F_flag = 1;
47162216Sdes	    break;
47262216Sdes	case 'f':
47362216Sdes	    f_filename = optarg;
47462216Sdes	    break;
47562216Sdes	case 'H':
47662216Sdes	    H_flag = 1;
47762216Sdes	    break;
47862216Sdes	case 'h':
47962216Sdes	    h_hostname = optarg;
48062216Sdes	    break;
48162216Sdes	case 'l':
48262216Sdes	    l_flag = 1;
48362216Sdes	    break;
48462216Sdes	case 'o':
48562216Sdes	    o_flag = 1;
48662216Sdes	    o_filename = optarg;
48762216Sdes	    break;
48862216Sdes	case 'M':
48962216Sdes	case 'm':
49062216Sdes	    m_flag = 1;
49162216Sdes	    break;
49262216Sdes	case 'n':
49362216Sdes	    m_flag = 0;
49462216Sdes	    break;
49562216Sdes	case 'P':
49662216Sdes	case 'p':
49762216Sdes	    p_flag = 1;
49862216Sdes	    break;
49962216Sdes	case 'q':
50062216Sdes	    v_level = 0;
50162216Sdes	    break;
50262216Sdes	case 'R':
50362216Sdes	    R_flag = 1;
50462216Sdes	    break;
50562216Sdes	case 'r':
50662216Sdes	    r_flag = 1;
50762216Sdes	    break;
50862216Sdes	case 'S':
50962216Sdes	    if (parseoff(optarg, &S_size) == -1)
51062216Sdes		errx(1, "invalid size");
51162216Sdes	    break;
51262216Sdes	case 's':
51362216Sdes	    s_flag = 1;
51462216Sdes	    break;
51562216Sdes	case 'T':
51662216Sdes	    if (parseint(optarg, &T_secs) == -1)
51762216Sdes		errx(1, "invalid timeout");
51862216Sdes	    break;
51962216Sdes	case 't':
52062216Sdes	    t_flag = 1;
52162216Sdes	    warnx("warning: the -t option is deprecated");
52262216Sdes	    break;
52362216Sdes	case 'v':
52462216Sdes	    v_level++;
52562216Sdes	    break;
52662216Sdes	case 'w':
52762216Sdes	    a_flag = 1;
52862216Sdes	    if (parseint(optarg, &w_secs) == -1)
52962216Sdes		errx(1, "invalid delay");
53062216Sdes	    break;
53162216Sdes	default:
53262216Sdes	    usage();
53362216Sdes	    exit(EX_USAGE);
53462216Sdes	}
53562216Sdes
53662216Sdes    argc -= optind;
53762216Sdes    argv += optind;
53862216Sdes
53962254Sdes    if (h_hostname || f_filename || c_dirname) {
54062216Sdes	if (!h_hostname || !f_filename || argc) {
54162216Sdes	    usage();
54262216Sdes	    exit(EX_USAGE);
54362216Sdes	}
54462216Sdes	/* XXX this is a hack. */
54562216Sdes	if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
54662216Sdes	    errx(1, "invalid hostname");
54762254Sdes	if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
54862254Sdes		     c_dirname ? c_dirname : "", f_filename) == -1)
54962216Sdes	    errx(1, strerror(ENOMEM));
55062216Sdes	argc++;
55162216Sdes    }
55262216Sdes
55362216Sdes    if (!argc) {
55462216Sdes	usage();
55562216Sdes	exit(EX_USAGE);
55662216Sdes    }
55762216Sdes
55862216Sdes    /* allocate buffer */
55962216Sdes    if (B_size < MINBUFSIZE)
56062216Sdes	B_size = MINBUFSIZE;
56162216Sdes    if ((buf = malloc(B_size)) == NULL)
56262216Sdes	errx(1, strerror(ENOMEM));
56362216Sdes
56462216Sdes    /* timeout handling */
56562216Sdes    signal(SIGALRM, sig_handler);
56662216Sdes    if ((s = getenv("FTP_TIMEOUT")) != NULL) {
56762216Sdes	if (parseint(s, &ftp_timeout) == -1) {
56862216Sdes	    warnx("FTP_TIMEOUT is not a positive integer");
56962216Sdes	    ftp_timeout = 0;
57062216Sdes	}
57162216Sdes    }
57262216Sdes    if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
57362216Sdes	if (parseint(s, &http_timeout) == -1) {
57462216Sdes	    warnx("HTTP_TIMEOUT is not a positive integer");
57562216Sdes	    http_timeout = 0;
57662216Sdes	}
57762216Sdes    }
57862216Sdes
57962216Sdes    /* output file */
58062216Sdes    if (o_flag) {
58162216Sdes	if (strcmp(o_filename, "-") == 0) {
58262216Sdes	    o_stdout = 1;
58362216Sdes	} else if (stat(o_filename, &sb) == -1) {
58462216Sdes	    if (errno == ENOENT) {
58562216Sdes		if (argc > 1)
58662216Sdes		    errx(EX_USAGE, "%s is not a directory", o_filename);
58762216Sdes	    } else {
58862216Sdes		err(EX_IOERR, "%s", o_filename);
58962216Sdes	    }
59062216Sdes	} else {
59162216Sdes	    if (sb.st_mode & S_IFDIR)
59262216Sdes		o_directory = 1;
59362216Sdes	}
59462216Sdes    }
59562216Sdes
59662216Sdes    /* check if output is to a tty (for progress report) */
59762216Sdes    v_tty = isatty(STDOUT_FILENO);
59862216Sdes    r = 0;
59962216Sdes
60062216Sdes    while (argc) {
60162216Sdes	if ((p = strrchr(*argv, '/')) == NULL)
60262216Sdes	    p = *argv;
60362216Sdes	else
60462216Sdes	    p++;
60562216Sdes
60662216Sdes	if (!*p)
60762216Sdes	    p = "fetch.out";
60862216Sdes
60962216Sdes	fetchLastErrCode = 0;
61062216Sdes
61162216Sdes	if (o_flag) {
61262216Sdes	    if (o_stdout) {
61362216Sdes		e = fetch(*argv, "-");
61462216Sdes	    } else if (o_directory) {
61562216Sdes		asprintf(&q, "%s/%s", o_filename, p);
61662216Sdes		e = fetch(*argv, q);
61762216Sdes		free(q);
61862216Sdes	    } else {
61962216Sdes		e = fetch(*argv, o_filename);
62062216Sdes	    }
62162216Sdes	} else {
62262216Sdes	    e = fetch(*argv, p);
62362216Sdes	}
62462216Sdes
62562216Sdes	if (e == 0 && once_flag)
62662216Sdes	    exit(0);
62762216Sdes
62862216Sdes	if (e) {
62962216Sdes	    r = 1;
63062216Sdes	    if ((fetchLastErrCode
63162216Sdes		 && fetchLastErrCode != FETCH_UNAVAIL
63262216Sdes		 && fetchLastErrCode != FETCH_MOVED
63362216Sdes		 && fetchLastErrCode != FETCH_URL
63462216Sdes		 && fetchLastErrCode != FETCH_RESOLV
63562216Sdes		 && fetchLastErrCode != FETCH_UNKNOWN)) {
63662216Sdes		if (w_secs) {
63762216Sdes		    if (v_level)
63862216Sdes			fprintf(stderr, "Waiting %d seconds before retrying\n", w_secs);
63962216Sdes		    sleep(w_secs);
64062216Sdes		}
64162216Sdes		if (a_flag)
64262216Sdes		    continue;
64362216Sdes		fprintf(stderr, "Skipping %s\n", *argv);
64462216Sdes	    }
64562216Sdes	}
64662216Sdes
64762216Sdes	argc--, argv++;
64862216Sdes    }
64962216Sdes
65062216Sdes    exit(r);
65162216Sdes}
652