xref: /freebsd/usr.bin/fetch/fetch.c (revision ddd5b8e9b4d8957fce018c520657cdfa4ecffad3)
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) / 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 				if (mkstemps(tmppath, strlen(slash) + 1) == -1) {
622 					warn("%s: mkstemps()", path);
623 					goto failure;
624 				}
625 				of = fopen(tmppath, "w");
626 				chown(tmppath, sb.st_uid, sb.st_gid);
627 				chmod(tmppath, sb.st_mode & ALLPERMS);
628 			}
629 		}
630 		if (of == NULL)
631 			of = fopen(path, "w");
632 		if (of == NULL) {
633 			warn("%s: open()", path);
634 			goto failure;
635 		}
636 	}
637 	count = url->offset;
638 
639 	/* start the counter */
640 	stat_start(&xs, path, us.size, count);
641 
642 	sigalrm = siginfo = sigint = 0;
643 
644 	/* suck in the data */
645 	signal(SIGINFO, sig_handler);
646 	while (!sigint) {
647 		if (us.size != -1 && us.size - count < B_size &&
648 		    us.size - count >= 0)
649 			size = us.size - count;
650 		else
651 			size = B_size;
652 		if (siginfo) {
653 			stat_end(&xs);
654 			siginfo = 0;
655 		}
656 
657 		if (size == 0)
658 			break;
659 
660 		if ((readcnt = fread(buf, 1, size, f)) < size) {
661 			if (ferror(f) && errno == EINTR && !sigint)
662 				clearerr(f);
663 			else if (readcnt == 0)
664 				break;
665 		}
666 
667 		stat_update(&xs, count += readcnt);
668 		for (ptr = buf; readcnt > 0; ptr += wr, readcnt -= wr)
669 			if ((wr = fwrite(ptr, 1, readcnt, of)) < readcnt) {
670 				if (ferror(of) && errno == EINTR && !sigint)
671 					clearerr(of);
672 				else
673 					break;
674 			}
675 		if (readcnt != 0)
676 			break;
677 	}
678 	if (!sigalrm)
679 		sigalrm = ferror(f) && errno == ETIMEDOUT;
680 	signal(SIGINFO, SIG_DFL);
681 
682 	stat_end(&xs);
683 
684 	/*
685 	 * If the transfer timed out or was interrupted, we still want to
686 	 * set the mtime in case the file is not removed (-r or -R) and
687 	 * the user later restarts the transfer.
688 	 */
689  signal:
690 	/* set mtime of local file */
691 	if (!n_flag && us.mtime && !o_stdout && of != NULL &&
692 	    (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) {
693 		struct timeval tv[2];
694 
695 		fflush(of);
696 		tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime);
697 		tv[1].tv_sec = (long)us.mtime;
698 		tv[0].tv_usec = tv[1].tv_usec = 0;
699 		if (utimes(tmppath ? tmppath : path, tv))
700 			warn("%s: utimes()", tmppath ? tmppath : path);
701 	}
702 
703 	/* timed out or interrupted? */
704 	if (sigalrm)
705 		warnx("transfer timed out");
706 	if (sigint) {
707 		warnx("transfer interrupted");
708 		goto failure;
709 	}
710 
711 	/* timeout / interrupt before connection completley established? */
712 	if (f == NULL)
713 		goto failure;
714 
715 	if (!sigalrm) {
716 		/* check the status of our files */
717 		if (ferror(f))
718 			warn("%s", URL);
719 		if (ferror(of))
720 			warn("%s", path);
721 		if (ferror(f) || ferror(of))
722 			goto failure;
723 	}
724 
725 	/* did the transfer complete normally? */
726 	if (us.size != -1 && count < us.size) {
727 		warnx("%s appears to be truncated: %jd/%jd bytes",
728 		    path, (intmax_t)count, (intmax_t)us.size);
729 		goto failure_keep;
730 	}
731 
732 	/*
733 	 * If the transfer timed out and we didn't know how much to
734 	 * expect, assume the worst (i.e. we didn't get all of it)
735 	 */
736 	if (sigalrm && us.size == -1) {
737 		warnx("%s may be truncated", path);
738 		goto failure_keep;
739 	}
740 
741  success:
742 	r = 0;
743 	if (tmppath != NULL && rename(tmppath, path) == -1) {
744 		warn("%s: rename()", path);
745 		goto failure_keep;
746 	}
747 	goto done;
748  failure:
749 	if (of && of != stdout && !R_flag && !r_flag)
750 		if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG))
751 			unlink(tmppath ? tmppath : path);
752 	if (R_flag && tmppath != NULL && sb.st_size == -1)
753 		rename(tmppath, path); /* ignore errors here */
754  failure_keep:
755 	r = -1;
756 	goto done;
757  done:
758 	if (f)
759 		fclose(f);
760 	if (of && of != stdout)
761 		fclose(of);
762 	if (url)
763 		fetchFreeURL(url);
764 	if (tmppath != NULL)
765 		free(tmppath);
766 	return (r);
767 }
768 
769 static void
770 usage(void)
771 {
772 	fprintf(stderr, "%s\n%s\n%s\n%s\n",
773 "usage: fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [-N file] [-o file] [-S bytes]",
774 "       [-T seconds] [-w seconds] [-i file] URL ...",
775 "       fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [-N file] [-o file] [-S bytes]",
776 "       [-T seconds] [-w seconds] [-i file] -h host -f file [-c dir]");
777 }
778 
779 
780 /*
781  * Entry point
782  */
783 int
784 main(int argc, char *argv[])
785 {
786 	struct stat sb;
787 	struct sigaction sa;
788 	const char *p, *s;
789 	char *end, *q;
790 	int c, e, r;
791 
792 	while ((c = getopt(argc, argv,
793 	    "146AaB:bc:dFf:Hh:i:lMmN:nPpo:qRrS:sT:tUvw:")) != -1)
794 		switch (c) {
795 		case '1':
796 			once_flag = 1;
797 			break;
798 		case '4':
799 			family = PF_INET;
800 			break;
801 		case '6':
802 			family = PF_INET6;
803 			break;
804 		case 'A':
805 			A_flag = 1;
806 			break;
807 		case 'a':
808 			a_flag = 1;
809 			break;
810 		case 'B':
811 			B_size = (off_t)strtol(optarg, &end, 10);
812 			if (*optarg == '\0' || *end != '\0')
813 				errx(1, "invalid buffer size (%s)", optarg);
814 			break;
815 		case 'b':
816 			warnx("warning: the -b option is deprecated");
817 			b_flag = 1;
818 			break;
819 		case 'c':
820 			c_dirname = optarg;
821 			break;
822 		case 'd':
823 			d_flag = 1;
824 			break;
825 		case 'F':
826 			F_flag = 1;
827 			break;
828 		case 'f':
829 			f_filename = optarg;
830 			break;
831 		case 'H':
832 			warnx("the -H option is now implicit, "
833 			    "use -U to disable");
834 			break;
835 		case 'h':
836 			h_hostname = optarg;
837 			break;
838 		case 'i':
839 			i_flag = 1;
840 			i_filename = optarg;
841 			break;
842 		case 'l':
843 			l_flag = 1;
844 			break;
845 		case 'o':
846 			o_flag = 1;
847 			o_filename = optarg;
848 			break;
849 		case 'M':
850 		case 'm':
851 			if (r_flag)
852 				errx(1, "the -m and -r flags "
853 				    "are mutually exclusive");
854 			m_flag = 1;
855 			break;
856 		case 'N':
857 			N_filename = optarg;
858 			break;
859 		case 'n':
860 			n_flag = 1;
861 			break;
862 		case 'P':
863 		case 'p':
864 			p_flag = 1;
865 			break;
866 		case 'q':
867 			v_level = 0;
868 			break;
869 		case 'R':
870 			R_flag = 1;
871 			break;
872 		case 'r':
873 			if (m_flag)
874 				errx(1, "the -m and -r flags "
875 				    "are mutually exclusive");
876 			r_flag = 1;
877 			break;
878 		case 'S':
879 			S_size = (off_t)strtol(optarg, &end, 10);
880 			if (*optarg == '\0' || *end != '\0')
881 				errx(1, "invalid size (%s)", optarg);
882 			break;
883 		case 's':
884 			s_flag = 1;
885 			break;
886 		case 'T':
887 			T_secs = strtol(optarg, &end, 10);
888 			if (*optarg == '\0' || *end != '\0')
889 				errx(1, "invalid timeout (%s)", optarg);
890 			break;
891 		case 't':
892 			t_flag = 1;
893 			warnx("warning: the -t option is deprecated");
894 			break;
895 		case 'U':
896 			U_flag = 1;
897 			break;
898 		case 'v':
899 			v_level++;
900 			break;
901 		case 'w':
902 			a_flag = 1;
903 			w_secs = strtol(optarg, &end, 10);
904 			if (*optarg == '\0' || *end != '\0')
905 				errx(1, "invalid delay (%s)", optarg);
906 			break;
907 		default:
908 			usage();
909 			exit(1);
910 		}
911 
912 	argc -= optind;
913 	argv += optind;
914 
915 	if (h_hostname || f_filename || c_dirname) {
916 		if (!h_hostname || !f_filename || argc) {
917 			usage();
918 			exit(1);
919 		}
920 		/* XXX this is a hack. */
921 		if (strcspn(h_hostname, "@:/") != strlen(h_hostname))
922 			errx(1, "invalid hostname");
923 		if (asprintf(argv, "ftp://%s/%s/%s", h_hostname,
924 		    c_dirname ? c_dirname : "", f_filename) == -1)
925 			errx(1, "%s", strerror(ENOMEM));
926 		argc++;
927 	}
928 
929 	if (!argc) {
930 		usage();
931 		exit(1);
932 	}
933 
934 	/* allocate buffer */
935 	if (B_size < MINBUFSIZE)
936 		B_size = MINBUFSIZE;
937 	if ((buf = malloc(B_size)) == NULL)
938 		errx(1, "%s", strerror(ENOMEM));
939 
940 	/* timeouts */
941 	if ((s = getenv("FTP_TIMEOUT")) != NULL) {
942 		ftp_timeout = strtol(s, &end, 10);
943 		if (*s == '\0' || *end != '\0' || ftp_timeout < 0) {
944 			warnx("FTP_TIMEOUT (%s) is not a positive integer", s);
945 			ftp_timeout = 0;
946 		}
947 	}
948 	if ((s = getenv("HTTP_TIMEOUT")) != NULL) {
949 		http_timeout = strtol(s, &end, 10);
950 		if (*s == '\0' || *end != '\0' || http_timeout < 0) {
951 			warnx("HTTP_TIMEOUT (%s) is not a positive integer", s);
952 			http_timeout = 0;
953 		}
954 	}
955 
956 	/* signal handling */
957 	sa.sa_flags = 0;
958 	sa.sa_handler = sig_handler;
959 	sigemptyset(&sa.sa_mask);
960 	sigaction(SIGALRM, &sa, NULL);
961 	sa.sa_flags = SA_RESETHAND;
962 	sigaction(SIGINT, &sa, NULL);
963 	fetchRestartCalls = 0;
964 
965 	/* output file */
966 	if (o_flag) {
967 		if (strcmp(o_filename, "-") == 0) {
968 			o_stdout = 1;
969 		} else if (stat(o_filename, &sb) == -1) {
970 			if (errno == ENOENT) {
971 				if (argc > 1)
972 					errx(1, "%s is not a directory",
973 					    o_filename);
974 			} else {
975 				err(1, "%s", o_filename);
976 			}
977 		} else {
978 			if (sb.st_mode & S_IFDIR)
979 				o_directory = 1;
980 		}
981 	}
982 
983 	/* check if output is to a tty (for progress report) */
984 	v_tty = isatty(STDERR_FILENO);
985 	if (v_tty)
986 		pgrp = getpgrp();
987 
988 	r = 0;
989 
990 	/* authentication */
991 	if (v_tty)
992 		fetchAuthMethod = query_auth;
993 	if (N_filename != NULL)
994 		if (setenv("NETRC", N_filename, 1) == -1)
995 			err(1, "setenv: cannot set NETRC=%s", N_filename);
996 
997 	while (argc) {
998 		if ((p = strrchr(*argv, '/')) == NULL)
999 			p = *argv;
1000 		else
1001 			p++;
1002 
1003 		if (!*p)
1004 			p = "fetch.out";
1005 
1006 		fetchLastErrCode = 0;
1007 
1008 		if (o_flag) {
1009 			if (o_stdout) {
1010 				e = fetch(*argv, "-");
1011 			} else if (o_directory) {
1012 				asprintf(&q, "%s/%s", o_filename, p);
1013 				e = fetch(*argv, q);
1014 				free(q);
1015 			} else {
1016 				e = fetch(*argv, o_filename);
1017 			}
1018 		} else {
1019 			e = fetch(*argv, p);
1020 		}
1021 
1022 		if (sigint)
1023 			kill(getpid(), SIGINT);
1024 
1025 		if (e == 0 && once_flag)
1026 			exit(0);
1027 
1028 		if (e) {
1029 			r = 1;
1030 			if ((fetchLastErrCode
1031 			    && fetchLastErrCode != FETCH_UNAVAIL
1032 			    && fetchLastErrCode != FETCH_MOVED
1033 			    && fetchLastErrCode != FETCH_URL
1034 			    && fetchLastErrCode != FETCH_RESOLV
1035 			    && fetchLastErrCode != FETCH_UNKNOWN)) {
1036 				if (w_secs && v_level)
1037 					fprintf(stderr, "Waiting %ld seconds "
1038 					    "before retrying\n", w_secs);
1039 				if (w_secs)
1040 					sleep(w_secs);
1041 				if (a_flag)
1042 					continue;
1043 			}
1044 		}
1045 
1046 		argc--, argv++;
1047 	}
1048 
1049 	exit(r);
1050 }
1051