xref: /freebsd/usr.bin/fetch/fetch.c (revision aa77200569e397d6ff1fdb4d255d0fa254d0a128)
1 /*-
2  * Copyright (c) 2000-2011 Dag-Erling 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 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/socket.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 
37 #include <ctype.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <signal.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <termios.h>
46 #include <unistd.h>
47 
48 #include <fetch.h>
49 
50 #define MINBUFSIZE	4096
51 #define TIMEOUT		120
52 
53 /* Option flags */
54 static int	 A_flag;	/*    -A: do not follow 302 redirects */
55 static int	 a_flag;	/*    -a: auto retry */
56 static off_t	 B_size;	/*    -B: buffer size */
57 static int	 b_flag;	/*!   -b: workaround TCP bug */
58 static char    *c_dirname;	/*    -c: remote directory */
59 static int	 d_flag;	/*    -d: direct connection */
60 static int	 F_flag;	/*    -F: restart without checking mtime  */
61 static char	*f_filename;	/*    -f: file to fetch */
62 static char	*h_hostname;	/*    -h: host to fetch from */
63 static int	 i_flag;	/*    -i: specify file for mtime comparison */
64 static char	*i_filename;	/*        name of input file */
65 static int	 l_flag;	/*    -l: link rather than copy file: URLs */
66 static int	 m_flag;	/* -[Mm]: mirror mode */
67 static char	*N_filename;	/*    -N: netrc file name */
68 static int	 n_flag;	/*    -n: do not preserve modification time */
69 static int	 o_flag;	/*    -o: specify output file */
70 static int	 o_directory;	/*        output file is a directory */
71 static char	*o_filename;	/*        name of output file */
72 static int	 o_stdout;	/*        output file is stdout */
73 static int	 once_flag;	/*    -1: stop at first successful file */
74 static int	 p_flag;	/* -[Pp]: use passive FTP */
75 static int	 R_flag;	/*    -R: don't delete partial files */
76 static int	 r_flag;	/*    -r: restart previous transfer */
77 static off_t	 S_size;        /*    -S: require size to match */
78 static int	 s_flag;        /*    -s: show size, don't fetch */
79 static long	 T_secs;	/*    -T: transfer timeout in seconds */
80 static int	 t_flag;	/*!   -t: workaround TCP bug */
81 static int	 U_flag;	/*    -U: do not use high ports */
82 static int	 v_level = 1;	/*    -v: verbosity level */
83 static int	 v_tty;		/*        stdout is a tty */
84 static pid_t	 pgrp;		/*        our process group */
85 static long	 w_secs;	/*    -w: retry delay */
86 static int	 family = PF_UNSPEC;	/* -[46]: address family to use */
87 
88 static int	 sigalrm;	/* SIGALRM received */
89 static int	 siginfo;	/* SIGINFO received */
90 static int	 sigint;	/* SIGINT received */
91 
92 static long	 ftp_timeout = TIMEOUT;	/* default timeout for FTP transfers */
93 static long	 http_timeout = TIMEOUT;/* default timeout for HTTP transfers */
94 static char	*buf;		/* transfer buffer */
95 
96 
97 /*
98  * Signal handler
99  */
100 static void
101 sig_handler(int sig)
102 {
103 	switch (sig) {
104 	case SIGALRM:
105 		sigalrm = 1;
106 		break;
107 	case SIGINFO:
108 		siginfo = 1;
109 		break;
110 	case SIGINT:
111 		sigint = 1;
112 		break;
113 	}
114 }
115 
116 struct xferstat {
117 	char		 name[64];
118 	struct timeval	 start;		/* start of transfer */
119 	struct timeval	 last;		/* time of last update */
120 	struct timeval	 last2;		/* time of previous last update */
121 	off_t		 size;		/* size of file per HTTP hdr */
122 	off_t		 offset;	/* starting offset in file */
123 	off_t		 rcvd;		/* bytes already received */
124 	off_t		 lastrcvd;	/* bytes received since last update */
125 };
126 
127 /*
128  * Compute and display ETA
129  */
130 static const char *
131 stat_eta(struct xferstat *xs)
132 {
133 	static char str[16];
134 	long elapsed, eta;
135 	off_t received, expected;
136 
137 	elapsed = xs->last.tv_sec - xs->start.tv_sec;
138 	received = xs->rcvd - xs->offset;
139 	expected = xs->size - xs->rcvd;
140 	eta = (long)((double)elapsed * expected / received);
141 	if (eta > 3600)
142 		snprintf(str, sizeof str, "%02ldh%02ldm",
143 		    eta / 3600, (eta % 3600) / 60);
144 	else if (eta > 0)
145 		snprintf(str, sizeof str, "%02ldm%02lds",
146 		    eta / 60, eta % 60);
147 	else
148 		snprintf(str, sizeof str, "%02ldm%02lds",
149 		    elapsed / 60, elapsed % 60);
150 	return (str);
151 }
152 
153 /*
154  * Format a number as "xxxx YB" where Y is ' ', 'k', 'M'...
155  */
156 static const char *prefixes = " kMGTP";
157 static const char *
158 stat_bytes(off_t bytes)
159 {
160 	static char str[16];
161 	const char *prefix = prefixes;
162 
163 	while (bytes > 9999 && prefix[1] != '\0') {
164 		bytes /= 1024;
165 		prefix++;
166 	}
167 	snprintf(str, sizeof str, "%4jd %cB", (intmax_t)bytes, *prefix);
168 	return (str);
169 }
170 
171 /*
172  * Compute and display transfer rate
173  */
174 static const char *
175 stat_bps(struct xferstat *xs)
176 {
177 	static char str[16];
178 	double delta, bps;
179 
180 	delta = (xs->last.tv_sec + (xs->last.tv_usec / 1.e6))
181 	    - (xs->last2.tv_sec + (xs->last2.tv_usec / 1.e6));
182 
183 	if (delta == 0.0) {
184 		snprintf(str, sizeof str, "?? Bps");
185 	} else {
186 		bps = (xs->rcvd - xs->lastrcvd - xs->offset) / delta;
187 		snprintf(str, sizeof str, "%sps", stat_bytes((off_t)bps));
188 	}
189 	return (str);
190 }
191 
192 /*
193  * Update the stats display
194  */
195 static void
196 stat_display(struct xferstat *xs, int force)
197 {
198 	struct timeval now;
199 	int ctty_pgrp;
200 
201 	/* check if we're the foreground process */
202 	if (ioctl(STDERR_FILENO, TIOCGPGRP, &ctty_pgrp) == -1 ||
203 	    (pid_t)ctty_pgrp != pgrp)
204 		return;
205 
206 	gettimeofday(&now, NULL);
207 	if (!force && now.tv_sec <= xs->last.tv_sec)
208 		return;
209 	xs->last2 = xs->last;
210 	xs->last = now;
211 
212 	fprintf(stderr, "\r%-46.46s", xs->name);
213 	if (xs->size <= 0) {
214 		setproctitle("%s [%s]", xs->name, stat_bytes(xs->rcvd));
215 		fprintf(stderr, "        %s", stat_bytes(xs->rcvd));
216 	} else {
217 		setproctitle("%s [%d%% of %s]", xs->name,
218 		    (int)((100.0 * xs->rcvd) / xs->size),
219 		    stat_bytes(xs->size));
220 		fprintf(stderr, "%3d%% of %s",
221 		    (int)((100.0 * xs->rcvd) / xs->size),
222 		    stat_bytes(xs->size));
223 	}
224 	if (force == 2) {
225 		xs->lastrcvd = xs->offset;
226 		xs->last2 = xs->start;
227 	}
228 	fprintf(stderr, " %s", stat_bps(xs));
229 	if ((xs->size > 0 && xs->rcvd > 0 &&
230 	     xs->last.tv_sec >= xs->start.tv_sec + 3) ||
231 	    force == 2)
232 		fprintf(stderr, " %s", stat_eta(xs));
233 	xs->lastrcvd = xs->rcvd;
234 }
235 
236 /*
237  * Initialize the transfer statistics
238  */
239 static void
240 stat_start(struct xferstat *xs, const char *name, off_t size, off_t offset)
241 {
242 	snprintf(xs->name, sizeof xs->name, "%s", name);
243 	gettimeofday(&xs->start, NULL);
244 	xs->last.tv_sec = xs->last.tv_usec = 0;
245 	xs->size = size;
246 	xs->offset = offset;
247 	xs->rcvd = offset;
248 	xs->lastrcvd = offset;
249 	if (v_tty && v_level > 0)
250 		stat_display(xs, 1);
251 	else if (v_level > 0)
252 		fprintf(stderr, "%-46s", xs->name);
253 }
254 
255 /*
256  * Update the transfer statistics
257  */
258 static void
259 stat_update(struct xferstat *xs, off_t rcvd)
260 {
261 	xs->rcvd = rcvd;
262 	if (v_tty && v_level > 0)
263 		stat_display(xs, 0);
264 }
265 
266 /*
267  * Finalize the transfer statistics
268  */
269 static void
270 stat_end(struct xferstat *xs)
271 {
272 	gettimeofday(&xs->last, NULL);
273 	if (v_tty && v_level > 0) {
274 		stat_display(xs, 2);
275 		putc('\n', stderr);
276 	} else if (v_level > 0) {
277 		fprintf(stderr, "        %s %s\n",
278 		    stat_bytes(xs->size), stat_bps(xs));
279 	}
280 }
281 
282 /*
283  * Ask the user for authentication details
284  */
285 static int
286 query_auth(struct url *URL)
287 {
288 	struct termios tios;
289 	tcflag_t saved_flags;
290 	int i, nopwd;
291 
292 	fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n",
293 	    URL->scheme, URL->host, URL->port);
294 
295 	fprintf(stderr, "Login: ");
296 	if (fgets(URL->user, sizeof URL->user, stdin) == NULL)
297 		return (-1);
298 	for (i = strlen(URL->user); i >= 0; --i)
299 		if (URL->user[i] == '\r' || URL->user[i] == '\n')
300 			URL->user[i] = '\0';
301 
302 	fprintf(stderr, "Password: ");
303 	if (tcgetattr(STDIN_FILENO, &tios) == 0) {
304 		saved_flags = tios.c_lflag;
305 		tios.c_lflag &= ~ECHO;
306 		tios.c_lflag |= ECHONL|ICANON;
307 		tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios);
308 		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
309 		tios.c_lflag = saved_flags;
310 		tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios);
311 	} else {
312 		nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL);
313 	}
314 	if (nopwd)
315 		return (-1);
316 	for (i = strlen(URL->pwd); i >= 0; --i)
317 		if (URL->pwd[i] == '\r' || URL->pwd[i] == '\n')
318 			URL->pwd[i] = '\0';
319 
320 	return (0);
321 }
322 
323 /*
324  * Fetch a file
325  */
326 static int
327 fetch(char *URL, const char *path)
328 {
329 	struct url *url;
330 	struct url_stat us;
331 	struct stat sb, nsb;
332 	struct xferstat xs;
333 	FILE *f, *of;
334 	size_t size, readcnt, wr;
335 	off_t count;
336 	char flags[8];
337 	const char *slash;
338 	char *tmppath;
339 	int r;
340 	unsigned timeout;
341 	char *ptr;
342 
343 	f = of = NULL;
344 	tmppath = NULL;
345 
346 	timeout = 0;
347 	*flags = 0;
348 	count = 0;
349 
350 	/* set verbosity level */
351 	if (v_level > 1)
352 		strcat(flags, "v");
353 	if (v_level > 2)
354 		fetchDebug = 1;
355 
356 	/* parse URL */
357 	url = NULL;
358 	if (*URL == '\0') {
359 		warnx("empty URL");
360 		goto failure;
361 	}
362 	if ((url = fetchParseURL(URL)) == NULL) {
363 		warnx("%s: parse error", URL);
364 		goto failure;
365 	}
366 
367 	/* if no scheme was specified, take a guess */
368 	if (!*url->scheme) {
369 		if (!*url->host)
370 			strcpy(url->scheme, SCHEME_FILE);
371 		else if (strncasecmp(url->host, "ftp.", 4) == 0)
372 			strcpy(url->scheme, SCHEME_FTP);
373 		else if (strncasecmp(url->host, "www.", 4) == 0)
374 			strcpy(url->scheme, SCHEME_HTTP);
375 	}
376 
377 	/* common flags */
378 	switch (family) {
379 	case PF_INET:
380 		strcat(flags, "4");
381 		break;
382 	case PF_INET6:
383 		strcat(flags, "6");
384 		break;
385 	}
386 
387 	/* FTP specific flags */
388 	if (strcmp(url->scheme, SCHEME_FTP) == 0) {
389 		if (p_flag)
390 			strcat(flags, "p");
391 		if (d_flag)
392 			strcat(flags, "d");
393 		if (U_flag)
394 			strcat(flags, "l");
395 		timeout = T_secs ? T_secs : ftp_timeout;
396 	}
397 
398 	/* HTTP specific flags */
399 	if (strcmp(url->scheme, SCHEME_HTTP) == 0 ||
400 	    strcmp(url->scheme, SCHEME_HTTPS) == 0) {
401 		if (d_flag)
402 			strcat(flags, "d");
403 		if (A_flag)
404 			strcat(flags, "A");
405 		timeout = T_secs ? T_secs : http_timeout;
406 		if (i_flag) {
407 			if (stat(i_filename, &sb)) {
408 				warn("%s: stat()", i_filename);
409 				goto failure;
410 			}
411 			url->ims_time = sb.st_mtime;
412 			strcat(flags, "i");
413 		}
414 	}
415 
416 	/* set the protocol timeout. */
417 	fetchTimeout = timeout;
418 
419 	/* just print size */
420 	if (s_flag) {
421 		if (timeout)
422 			alarm(timeout);
423 		r = fetchStat(url, &us, flags);
424 		if (timeout)
425 			alarm(0);
426 		if (sigalrm || sigint)
427 			goto signal;
428 		if (r == -1) {
429 			warnx("%s", fetchLastErrString);
430 			goto failure;
431 		}
432 		if (us.size == -1)
433 			printf("Unknown\n");
434 		else
435 			printf("%jd\n", (intmax_t)us.size);
436 		goto success;
437 	}
438 
439 	/*
440 	 * If the -r flag was specified, we have to compare the local
441 	 * and remote files, so we should really do a fetchStat()
442 	 * first, but I know of at least one HTTP server that only
443 	 * sends the content size in response to GET requests, and
444 	 * leaves it out of replies to HEAD requests.  Also, in the
445 	 * (frequent) case that the local and remote files match but
446 	 * the local file is truncated, we have sufficient information
447 	 * before the compare to issue a correct request.  Therefore,
448 	 * we always issue a GET request as if we were sure the local
449 	 * file was a truncated copy of the remote file; we can drop
450 	 * the connection later if we change our minds.
451 	 */
452 	sb.st_size = -1;
453 	if (!o_stdout) {
454 		r = stat(path, &sb);
455 		if (r == 0 && r_flag && S_ISREG(sb.st_mode)) {
456 			url->offset = sb.st_size;
457 		} else if (r == -1 || !S_ISREG(sb.st_mode)) {
458 			/*
459 			 * Whatever value sb.st_size has now is either
460 			 * wrong (if stat(2) failed) or irrelevant (if the
461 			 * path does not refer to a regular file)
462 			 */
463 			sb.st_size = -1;
464 		}
465 		if (r == -1 && errno != ENOENT) {
466 			warnx("%s: stat()", path);
467 			goto failure;
468 		}
469 	}
470 
471 	/* start the transfer */
472 	if (timeout)
473 		alarm(timeout);
474 	f = fetchXGet(url, &us, flags);
475 	if (timeout)
476 		alarm(0);
477 	if (sigalrm || sigint)
478 		goto signal;
479 	if (f == NULL) {
480 		warnx("%s: %s", URL, fetchLastErrString);
481 		if (i_flag && strcmp(url->scheme, SCHEME_HTTP) == 0
482 		    && fetchLastErrCode == FETCH_OK
483 		    && strcmp(fetchLastErrString, "Not Modified") == 0) {
484 			/* HTTP Not Modified Response, return OK. */
485 			r = 0;
486 			goto done;
487 		} else
488 			goto failure;
489 	}
490 	if (sigint)
491 		goto signal;
492 
493 	/* check that size is as expected */
494 	if (S_size) {
495 		if (us.size == -1) {
496 			warnx("%s: size unknown", URL);
497 		} else if (us.size != S_size) {
498 			warnx("%s: size mismatch: expected %jd, actual %jd",
499 			    URL, (intmax_t)S_size, (intmax_t)us.size);
500 			goto failure;
501 		}
502 	}
503 
504 	/* symlink instead of copy */
505 	if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) {
506 		if (symlink(url->doc, path) == -1) {
507 			warn("%s: symlink()", path);
508 			goto failure;
509 		}
510 		goto success;
511 	}
512 
513 	if (us.size == -1 && !o_stdout && v_level > 0)
514 		warnx("%s: size of remote file is not known", URL);
515 	if (v_level > 1) {
516 		if (sb.st_size != -1)
517 			fprintf(stderr, "local size / mtime: %jd / %ld\n",
518 			    (intmax_t)sb.st_size, (long)sb.st_mtime);
519 		if (us.size != -1)
520 			fprintf(stderr, "remote size / mtime: %jd / %ld\n",
521 			    (intmax_t)us.size, (long)us.mtime);
522 	}
523 
524 	/* open output file */
525 	if (o_stdout) {
526 		/* output to stdout */
527 		of = stdout;
528 	} else if (r_flag && sb.st_size != -1) {
529 		/* resume mode, local file exists */
530 		if (!F_flag && us.mtime && sb.st_mtime != us.mtime) {
531 			/* no match! have to refetch */
532 			fclose(f);
533 			/* if precious, warn the user and give up */
534 			if (R_flag) {
535 				warnx("%s: local modification time "
536 				    "does not match remote", path);
537 				goto failure_keep;
538 			}
539 		} else if (url->offset > sb.st_size) {
540 			/* gap between what we asked for and what we got */
541 			warnx("%s: gap in resume mode", URL);
542 			fclose(of);
543 			of = NULL;
544 			/* picked up again later */
545 		} else if (us.size != -1) {
546 			if (us.size == sb.st_size)
547 				/* nothing to do */
548 				goto success;
549 			if (sb.st_size > us.size) {
550 				/* local file too long! */
551 				warnx("%s: local file (%jd bytes) is longer "
552 				    "than remote file (%jd bytes)", path,
553 				    (intmax_t)sb.st_size, (intmax_t)us.size);
554 				goto failure;
555 			}
556 			/* we got it, open local file */
557 			if ((of = fopen(path, "r+")) == NULL) {
558 				warn("%s: fopen()", path);
559 				goto failure;
560 			}
561 			/* check that it didn't move under our feet */
562 			if (fstat(fileno(of), &nsb) == -1) {
563 				/* can't happen! */
564 				warn("%s: fstat()", path);
565 				goto failure;
566 			}
567 			if (nsb.st_dev != sb.st_dev ||
568 			    nsb.st_ino != nsb.st_ino ||
569 			    nsb.st_size != sb.st_size) {
570 				warnx("%s: file has changed", URL);
571 				fclose(of);
572 				of = NULL;
573 				sb = nsb;
574 				/* picked up again later */
575 			}
576 		}
577 		/* seek to where we left off */
578 		if (of != NULL && fseeko(of, url->offset, SEEK_SET) != 0) {
579 			warn("%s: fseeko()", path);
580 			fclose(of);
581 			of = NULL;
582 			/* picked up again later */
583 		}
584 	} else if (m_flag && sb.st_size != -1) {
585 		/* mirror mode, local file exists */
586 		if (sb.st_size == us.size && sb.st_mtime == us.mtime)
587 			goto success;
588 	}
589 
590 	if (of == NULL) {
591 		/*
592 		 * We don't yet have an output file; either this is a
593 		 * vanilla run with no special flags, or the local and
594 		 * remote files didn't match.
595 		 */
596 
597 		if (url->offset > 0) {
598 			/*
599 			 * We tried to restart a transfer, but for
600 			 * some reason gave up - so we have to restart
601 			 * from scratch if we want the whole file
602 			 */
603 			url->offset = 0;
604 			if ((f = fetchXGet(url, &us, flags)) == NULL) {
605 				warnx("%s: %s", URL, fetchLastErrString);
606 				goto failure;
607 			}
608 			if (sigint)
609 				goto signal;
610 		}
611 
612 		/* construct a temp file name */
613 		if (sb.st_size != -1 && S_ISREG(sb.st_mode)) {
614 			if ((slash = strrchr(path, '/')) == NULL)
615 				slash = path;
616 			else
617 				++slash;
618 			asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s",
619 			    (int)(slash - path), path, slash);
620 			if (tmppath != NULL) {
621 				mkstemps(tmppath, strlen(slash) + 1);
622 				of = fopen(tmppath, "w");
623 				chown(tmppath, sb.st_uid, sb.st_gid);
624 				chmod(tmppath, sb.st_mode & ALLPERMS);
625 			}
626 		}
627 		if (of == NULL)
628 			of = fopen(path, "w");
629 		if (of == NULL) {
630 			warn("%s: open()", path);
631 			goto failure;
632 		}
633 	}
634 	count = url->offset;
635 
636 	/* start the counter */
637 	stat_start(&xs, path, us.size, count);
638 
639 	sigalrm = siginfo = sigint = 0;
640 
641 	/* suck in the data */
642 	signal(SIGINFO, sig_handler);
643 	while (!sigint) {
644 		if (us.size != -1 && us.size - count < B_size &&
645 		    us.size - count >= 0)
646 			size = us.size - count;
647 		else
648 			size = B_size;
649 		if (siginfo) {
650 			stat_end(&xs);
651 			siginfo = 0;
652 		}
653 
654 		if (size == 0)
655 			break;
656 
657 		if ((readcnt = fread(buf, 1, size, f)) < size) {
658 			if (ferror(f) && errno == EINTR && !sigint)
659 				clearerr(f);
660 			else if (readcnt == 0)
661 				break;
662 		}
663 
664 		stat_update(&xs, count += readcnt);
665 		for (ptr = buf; readcnt > 0; ptr += wr, readcnt -= wr)
666 			if ((wr = fwrite(ptr, 1, readcnt, of)) < readcnt) {
667 				if (ferror(of) && errno == EINTR && !sigint)
668 					clearerr(of);
669 				else
670 					break;
671 			}
672 		if (readcnt != 0)
673 			break;
674 	}
675 	if (!sigalrm)
676 		sigalrm = ferror(f) && errno == ETIMEDOUT;
677 	signal(SIGINFO, SIG_DFL);
678 
679 	stat_end(&xs);
680 
681 	/*
682 	 * If the transfer timed out or was interrupted, we still want to
683 	 * set the mtime in case the file is not removed (-r or -R) and
684 	 * the user later restarts the transfer.
685 	 */
686  signal:
687 	/* set mtime of local file */
688 	if (!n_flag && us.mtime && !o_stdout && of != NULL &&
689 	    (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) {
690 		struct timeval tv[2];
691 
692 		fflush(of);
693 		tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
694 		tv[1].tv_sec = (long)us.mtime;
695 		tv[0].tv_usec = tv[1].tv_usec = 0;
696 		if (utimes(tmppath ? tmppath : path, tv))
697 			warn("%s: utimes()", tmppath ? tmppath : path);
698 	}
699 
700 	/* timed out or interrupted? */
701 	if (sigalrm)
702 		warnx("transfer timed out");
703 	if (sigint) {
704 		warnx("transfer interrupted");
705 		goto failure;
706 	}
707 
708 	/* timeout / interrupt before connection completley established? */
709 	if (f == NULL)
710 		goto failure;
711 
712 	if (!sigalrm) {
713 		/* check the status of our files */
714 		if (ferror(f))
715 			warn("%s", URL);
716 		if (ferror(of))
717 			warn("%s", path);
718 		if (ferror(f) || ferror(of))
719 			goto failure;
720 	}
721 
722 	/* did the transfer complete normally? */
723 	if (us.size != -1 && count < us.size) {
724 		warnx("%s appears to be truncated: %jd/%jd bytes",
725 		    path, (intmax_t)count, (intmax_t)us.size);
726 		goto failure_keep;
727 	}
728 
729 	/*
730 	 * If the transfer timed out and we didn't know how much to
731 	 * expect, assume the worst (i.e. we didn't get all of it)
732 	 */
733 	if (sigalrm && us.size == -1) {
734 		warnx("%s may be truncated", path);
735 		goto failure_keep;
736 	}
737 
738  success:
739 	r = 0;
740 	if (tmppath != NULL && rename(tmppath, path) == -1) {
741 		warn("%s: rename()", path);
742 		goto failure_keep;
743 	}
744 	goto done;
745  failure:
746 	if (of && of != stdout && !R_flag && !r_flag)
747 		if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG))
748 			unlink(tmppath ? tmppath : path);
749 	if (R_flag && tmppath != NULL && sb.st_size == -1)
750 		rename(tmppath, path); /* ignore errors here */
751  failure_keep:
752 	r = -1;
753 	goto done;
754  done:
755 	if (f)
756 		fclose(f);
757 	if (of && of != stdout)
758 		fclose(of);
759 	if (url)
760 		fetchFreeURL(url);
761 	if (tmppath != NULL)
762 		free(tmppath);
763 	return (r);
764 }
765 
766 static void
767 usage(void)
768 {
769 	fprintf(stderr, "%s\n%s\n%s\n%s\n",
770 "usage: fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [-N file] [-o file] [-S bytes]",
771 "       [-T seconds] [-w seconds] [-i file] URL ...",
772 "       fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [-N file] [-o file] [-S bytes]",
773 "       [-T seconds] [-w seconds] [-i file] -h host -f file [-c dir]");
774 }
775 
776 
777 /*
778  * Entry point
779  */
780 int
781 main(int argc, char *argv[])
782 {
783 	struct stat sb;
784 	struct sigaction sa;
785 	const char *p, *s;
786 	char *end, *q;
787 	int c, e, r;
788 
789 	while ((c = getopt(argc, argv,
790 	    "146AaB:bc:dFf:Hh:i:lMmN:nPpo:qRrS:sT:tUvw:")) != -1)
791 		switch (c) {
792 		case '1':
793 			once_flag = 1;
794 			break;
795 		case '4':
796 			family = PF_INET;
797 			break;
798 		case '6':
799 			family = PF_INET6;
800 			break;
801 		case 'A':
802 			A_flag = 1;
803 			break;
804 		case 'a':
805 			a_flag = 1;
806 			break;
807 		case 'B':
808 			B_size = (off_t)strtol(optarg, &end, 10);
809 			if (*optarg == '\0' || *end != '\0')
810 				errx(1, "invalid buffer size (%s)", optarg);
811 			break;
812 		case 'b':
813 			warnx("warning: the -b option is deprecated");
814 			b_flag = 1;
815 			break;
816 		case 'c':
817 			c_dirname = optarg;
818 			break;
819 		case 'd':
820 			d_flag = 1;
821 			break;
822 		case 'F':
823 			F_flag = 1;
824 			break;
825 		case 'f':
826 			f_filename = optarg;
827 			break;
828 		case 'H':
829 			warnx("the -H option is now implicit, "
830 			    "use -U to disable");
831 			break;
832 		case 'h':
833 			h_hostname = optarg;
834 			break;
835 		case 'i':
836 			i_flag = 1;
837 			i_filename = optarg;
838 			break;
839 		case 'l':
840 			l_flag = 1;
841 			break;
842 		case 'o':
843 			o_flag = 1;
844 			o_filename = optarg;
845 			break;
846 		case 'M':
847 		case 'm':
848 			if (r_flag)
849 				errx(1, "the -m and -r flags "
850 				    "are mutually exclusive");
851 			m_flag = 1;
852 			break;
853 		case 'N':
854 			N_filename = optarg;
855 			break;
856 		case 'n':
857 			n_flag = 1;
858 			break;
859 		case 'P':
860 		case 'p':
861 			p_flag = 1;
862 			break;
863 		case 'q':
864 			v_level = 0;
865 			break;
866 		case 'R':
867 			R_flag = 1;
868 			break;
869 		case 'r':
870 			if (m_flag)
871 				errx(1, "the -m and -r flags "
872 				    "are mutually exclusive");
873 			r_flag = 1;
874 			break;
875 		case 'S':
876 			S_size = (off_t)strtol(optarg, &end, 10);
877 			if (*optarg == '\0' || *end != '\0')
878 				errx(1, "invalid size (%s)", optarg);
879 			break;
880 		case 's':
881 			s_flag = 1;
882 			break;
883 		case 'T':
884 			T_secs = strtol(optarg, &end, 10);
885 			if (*optarg == '\0' || *end != '\0')
886 				errx(1, "invalid timeout (%s)", optarg);
887 			break;
888 		case 't':
889 			t_flag = 1;
890 			warnx("warning: the -t option is deprecated");
891 			break;
892 		case 'U':
893 			U_flag = 1;
894 			break;
895 		case 'v':
896 			v_level++;
897 			break;
898 		case 'w':
899 			a_flag = 1;
900 			w_secs = strtol(optarg, &end, 10);
901 			if (*optarg == '\0' || *end != '\0')
902 				errx(1, "invalid delay (%s)", optarg);
903 			break;
904 		default:
905 			usage();
906 			exit(1);
907 		}
908 
909 	argc -= optind;
910 	argv += optind;
911 
912 	if (h_hostname || f_filename || c_dirname) {
913 		if (!h_hostname || !f_filename || argc) {
914 			usage();
915 			exit(1);
916 		}
917 		/* XXX this is a hack. */
918 		if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
919 			errx(1, "invalid hostname");
920 		if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
921 		    c_dirname ? c_dirname : "", f_filename) == -1)
922 			errx(1, "%s", strerror(ENOMEM));
923 		argc++;
924 	}
925 
926 	if (!argc) {
927 		usage();
928 		exit(1);
929 	}
930 
931 	/* allocate buffer */
932 	if (B_size < MINBUFSIZE)
933 		B_size = MINBUFSIZE;
934 	if ((buf = malloc(B_size)) == NULL)
935 		errx(1, "%s", strerror(ENOMEM));
936 
937 	/* timeouts */
938 	if ((s = getenv("FTP_TIMEOUT")) != NULL) {
939 		ftp_timeout = strtol(s, &end, 10);
940 		if (*s == '\0' || *end != '\0' || ftp_timeout < 0) {
941 			warnx("FTP_TIMEOUT (%s) is not a positive integer", s);
942 			ftp_timeout = 0;
943 		}
944 	}
945 	if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
946 		http_timeout = strtol(s, &end, 10);
947 		if (*s == '\0' || *end != '\0' || http_timeout < 0) {
948 			warnx("HTTP_TIMEOUT (%s) is not a positive integer", s);
949 			http_timeout = 0;
950 		}
951 	}
952 
953 	/* signal handling */
954 	sa.sa_flags = 0;
955 	sa.sa_handler = sig_handler;
956 	sigemptyset(&sa.sa_mask);
957 	sigaction(SIGALRM, &sa, NULL);
958 	sa.sa_flags = SA_RESETHAND;
959 	sigaction(SIGINT, &sa, NULL);
960 	fetchRestartCalls = 0;
961 
962 	/* output file */
963 	if (o_flag) {
964 		if (strcmp(o_filename, "-") == 0) {
965 			o_stdout = 1;
966 		} else if (stat(o_filename, &sb) == -1) {
967 			if (errno == ENOENT) {
968 				if (argc > 1)
969 					errx(1, "%s is not a directory",
970 					    o_filename);
971 			} else {
972 				err(1, "%s", o_filename);
973 			}
974 		} else {
975 			if (sb.st_mode & S_IFDIR)
976 				o_directory = 1;
977 		}
978 	}
979 
980 	/* check if output is to a tty (for progress report) */
981 	v_tty = isatty(STDERR_FILENO);
982 	if (v_tty)
983 		pgrp = getpgrp();
984 
985 	r = 0;
986 
987 	/* authentication */
988 	if (v_tty)
989 		fetchAuthMethod = query_auth;
990 	if (N_filename != NULL)
991 		setenv("NETRC", N_filename, 1);
992 
993 	while (argc) {
994 		if ((p = strrchr(*argv, '/')) == NULL)
995 			p = *argv;
996 		else
997 			p++;
998 
999 		if (!*p)
1000 			p = "fetch.out";
1001 
1002 		fetchLastErrCode = 0;
1003 
1004 		if (o_flag) {
1005 			if (o_stdout) {
1006 				e = fetch(*argv, "-");
1007 			} else if (o_directory) {
1008 				asprintf(&q, "%s/%s", o_filename, p);
1009 				e = fetch(*argv, q);
1010 				free(q);
1011 			} else {
1012 				e = fetch(*argv, o_filename);
1013 			}
1014 		} else {
1015 			e = fetch(*argv, p);
1016 		}
1017 
1018 		if (sigint)
1019 			kill(getpid(), SIGINT);
1020 
1021 		if (e == 0 && once_flag)
1022 			exit(0);
1023 
1024 		if (e) {
1025 			r = 1;
1026 			if ((fetchLastErrCode
1027 			    && fetchLastErrCode != FETCH_UNAVAIL
1028 			    && fetchLastErrCode != FETCH_MOVED
1029 			    && fetchLastErrCode != FETCH_URL
1030 			    && fetchLastErrCode != FETCH_RESOLV
1031 			    && fetchLastErrCode != FETCH_UNKNOWN)) {
1032 				if (w_secs && v_level)
1033 					fprintf(stderr, "Waiting %ld seconds "
1034 					    "before retrying\n", w_secs);
1035 				if (w_secs)
1036 					sleep(w_secs);
1037 				if (a_flag)
1038 					continue;
1039 			}
1040 		}
1041 
1042 		argc--, argv++;
1043 	}
1044 
1045 	exit(r);
1046 }
1047