fetch.c revision 83307
1112158Sdas/*- 2112158Sdas * Copyright (c) 2000 Dag-Erling Co�dan Sm�rgrav 3112158Sdas * All rights reserved. 4112158Sdas * 5112158Sdas * Redistribution and use in source and binary forms, with or without 6112158Sdas * modification, are permitted provided that the following conditions 7112158Sdas * are met: 8112158Sdas * 1. Redistributions of source code must retain the above copyright 9112158Sdas * notice, this list of conditions and the following disclaimer 10112158Sdas * in this position and unchanged. 11112158Sdas * 2. Redistributions in binary form must reproduce the above copyright 12112158Sdas * notice, this list of conditions and the following disclaimer in the 13112158Sdas * documentation and/or other materials provided with the distribution. 14112158Sdas * 3. The name of the author may not be used to endorse or promote products 15112158Sdas * derived from this software without specific prior written permission 16112158Sdas * 17112158Sdas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18112158Sdas * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19112158Sdas * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20112158Sdas * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21112158Sdas * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22112158Sdas * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23112158Sdas * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24112158Sdas * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25112158Sdas * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26112158Sdas * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27112158Sdas * 28187808Sdas * $FreeBSD: head/usr.bin/fetch/fetch.c 83307 2001-09-10 17:23:57Z mike $ 29112158Sdas */ 30112158Sdas 31112158Sdas#include <sys/param.h> 32112158Sdas#include <sys/stat.h> 33219557Sdas#include <sys/socket.h> 34219557Sdas 35165743Sdas#include <ctype.h> 36112158Sdas#include <err.h> 37112158Sdas#include <errno.h> 38112158Sdas#include <signal.h> 39112158Sdas#include <stdio.h> 40112158Sdas#include <stdlib.h> 41112158Sdas#include <string.h> 42165743Sdas#include <sysexits.h> 43165743Sdas#include <termios.h> 44165743Sdas#include <unistd.h> 45165743Sdas 46165743Sdas#include <fetch.h> 47219557Sdas 48219557Sdas#define MINBUFSIZE 4096 49219557Sdas 50219557Sdas/* Option flags */ 51219557Sdasint A_flag; /* -A: do not follow 302 redirects */ 52219557Sdasint a_flag; /* -a: auto retry */ 53219557Sdasoff_t B_size; /* -B: buffer size */ 54112158Sdasint b_flag; /*! -b: workaround TCP bug */ 55112158Sdaschar *c_dirname; /* -c: remote directory */ 56112158Sdasint d_flag; /* -d: direct connection */ 57112158Sdasint F_flag; /* -F: restart without checking mtime */ 58219557Sdaschar *f_filename; /* -f: file to fetch */ 59219557Sdaschar *h_hostname; /* -h: host to fetch from */ 60219557Sdasint l_flag; /* -l: link rather than copy file: URLs */ 61219557Sdasint m_flag; /* -[Mm]: mirror mode */ 62219557Sdasint n_flag; /* -n: do not preserve modification time */ 63219557Sdasint o_flag; /* -o: specify output file */ 64112158Sdasint o_directory; /* output file is a directory */ 65112158Sdaschar *o_filename; /* name of output file */ 66219557Sdasint o_stdout; /* output file is stdout */ 67219557Sdasint once_flag; /* -1: stop at first successful file */ 68219557Sdasint p_flag; /* -[Pp]: use passive FTP */ 69219557Sdasint R_flag; /* -R: don't delete partially transferred files */ 70219557Sdasint r_flag; /* -r: restart previously interrupted transfer */ 71219557Sdasoff_t S_size; /* -S: require size to match */ 72219557Sdasint s_flag; /* -s: show size, don't fetch */ 73219557Sdasu_int T_secs = 120; /* -T: transfer timeout in seconds */ 74219557Sdasint t_flag; /*! -t: workaround TCP bug */ 75112158Sdasint U_flag; /* -U: do not use high ports */ 76219557Sdasint v_level = 1; /* -v: verbosity level */ 77112158Sdasint v_tty; /* stdout is a tty */ 78112158Sdasu_int w_secs; /* -w: retry delay */ 79112158Sdasint family = PF_UNSPEC; /* -[46]: address family to use */ 80112158Sdas 81112158Sdasint sigalrm; /* SIGALRM received */ 82112158Sdasint siginfo; /* SIGINFO received */ 83112158Sdasint sigint; /* SIGINT received */ 84112158Sdas 85219557Sdasu_int ftp_timeout; /* default timeout for FTP transfers */ 86u_int http_timeout; /* default timeout for HTTP transfers */ 87u_char *buf; /* transfer buffer */ 88 89 90/* 91 * Signal handler 92 */ 93static void 94sig_handler(int sig) 95{ 96 switch (sig) { 97 case SIGALRM: 98 sigalrm = 1; 99 break; 100 case SIGINFO: 101 siginfo = 1; 102 break; 103 case SIGINT: 104 sigint = 1; 105 break; 106 } 107} 108 109struct xferstat { 110 char name[40]; 111 struct timeval start; 112 struct timeval end; 113 struct timeval last; 114 off_t size; 115 off_t offset; 116 off_t rcvd; 117}; 118 119/* 120 * Update the stats display 121 */ 122static void 123stat_display(struct xferstat *xs, int force) 124{ 125 struct timeval now; 126 127 if (!v_tty || !v_level) 128 return; 129 130 gettimeofday(&now, NULL); 131 if (!force && now.tv_sec <= xs->last.tv_sec) 132 return; 133 xs->last = now; 134 135 fprintf(stderr, "\rReceiving %s", xs->name); 136 if (xs->size <= 0) 137 fprintf(stderr, ": %lld bytes", (long long)xs->rcvd); 138 else 139 fprintf(stderr, " (%lld bytes): %d%%", (long long)xs->size, 140 (int)((100.0 * xs->rcvd) / xs->size)); 141} 142 143/* 144 * Initialize the transfer statistics 145 */ 146static void 147stat_start(struct xferstat *xs, const char *name, off_t size, off_t offset) 148{ 149 snprintf(xs->name, sizeof xs->name, "%s", name); 150 gettimeofday(&xs->start, NULL); 151 xs->last.tv_sec = xs->last.tv_usec = 0; 152 xs->end = xs->last; 153 xs->size = size; 154 xs->offset = offset; 155 xs->rcvd = offset; 156 stat_display(xs, 1); 157} 158 159/* 160 * Update the transfer statistics 161 */ 162static void 163stat_update(struct xferstat *xs, off_t rcvd) 164{ 165 xs->rcvd = rcvd; 166 stat_display(xs, 0); 167} 168 169/* 170 * Finalize the transfer statistics 171 */ 172static void 173stat_end(struct xferstat *xs) 174{ 175 double delta; 176 double bps; 177 178 if (!v_level) 179 return; 180 181 gettimeofday(&xs->end, NULL); 182 183 stat_display(xs, 1); 184 fputc('\n', stderr); 185 delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6)) 186 - (xs->start.tv_sec + (xs->start.tv_usec / 1.e6)); 187 fprintf(stderr, "%lld bytes transferred in %.1f seconds ", 188 (long long)(xs->rcvd - xs->offset), delta); 189 bps = (xs->rcvd - xs->offset) / delta; 190 if (bps > 1024*1024) 191 fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024)); 192 else if (bps > 1024) 193 fprintf(stderr, "(%.2f kBps)\n", bps / 1024); 194 else 195 fprintf(stderr, "(%.2f Bps)\n", bps); 196} 197 198/* 199 * Ask the user for authentication details 200 */ 201static int 202query_auth(struct url *URL) 203{ 204 struct termios tios; 205 tcflag_t saved_flags; 206 int i, nopwd; 207 208 209 fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n", 210 URL->scheme, URL->host, URL->port, URL->doc); 211 212 fprintf(stderr, "Login: "); 213 if (fgets(URL->user, sizeof URL->user, stdin) == NULL) 214 return -1; 215 for (i = 0; URL->user[i]; ++i) 216 if (isspace(URL->user[i])) 217 URL->user[i] = '\0'; 218 219 fprintf(stderr, "Password: "); 220 if (tcgetattr(STDIN_FILENO, &tios) == 0) { 221 saved_flags = tios.c_lflag; 222 tios.c_lflag &= ~ECHO; 223 tios.c_lflag |= ECHONL|ICANON; 224 tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios); 225 nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL); 226 tios.c_lflag = saved_flags; 227 tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios); 228 } else { 229 nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL); 230 } 231 if (nopwd) 232 return -1; 233 234 for (i = 0; URL->pwd[i]; ++i) 235 if (isspace(URL->pwd[i])) 236 URL->pwd[i] = '\0'; 237 return 0; 238} 239 240/* 241 * Fetch a file 242 */ 243static int 244fetch(char *URL, const char *path) 245{ 246 struct url *url; 247 struct url_stat us; 248 struct stat sb, nsb; 249 struct xferstat xs; 250 FILE *f, *of; 251 size_t size, wr; 252 off_t count; 253 char flags[8]; 254 const char *slash; 255 char *tmppath; 256 int r; 257 u_int timeout; 258 u_char *ptr; 259 260 f = of = NULL; 261 tmppath = NULL; 262 263 /* parse URL */ 264 if ((url = fetchParseURL(URL)) == NULL) { 265 warnx("%s: parse error", URL); 266 goto failure; 267 } 268 269 /* if no scheme was specified, take a guess */ 270 if (!*url->scheme) { 271 if (!*url->host) 272 strcpy(url->scheme, SCHEME_FILE); 273 else if (strncasecmp(url->host, "ftp.", 4) == 0) 274 strcpy(url->scheme, SCHEME_FTP); 275 else if (strncasecmp(url->host, "www.", 4) == 0) 276 strcpy(url->scheme, SCHEME_HTTP); 277 } 278 279 timeout = 0; 280 *flags = 0; 281 count = 0; 282 283 /* common flags */ 284 if (v_level > 1) 285 strcat(flags, "v"); 286 switch (family) { 287 case PF_INET: 288 strcat(flags, "4"); 289 break; 290 case PF_INET6: 291 strcat(flags, "6"); 292 break; 293 } 294 295 /* FTP specific flags */ 296 if (strcmp(url->scheme, "ftp") == 0) { 297 if (p_flag) 298 strcat(flags, "p"); 299 if (d_flag) 300 strcat(flags, "d"); 301 if (U_flag) 302 strcat(flags, "l"); 303 timeout = T_secs ? T_secs : ftp_timeout; 304 } 305 306 /* HTTP specific flags */ 307 if (strcmp(url->scheme, "http") == 0) { 308 if (d_flag) 309 strcat(flags, "d"); 310 if (A_flag) 311 strcat(flags, "A"); 312 timeout = T_secs ? T_secs : http_timeout; 313 } 314 315 /* set the protocol timeout. */ 316 fetchTimeout = timeout; 317 318 /* just print size */ 319 if (s_flag) { 320 if (fetchStat(url, &us, flags) == -1) 321 goto failure; 322 if (us.size == -1) 323 printf("Unknown\n"); 324 else 325 printf("%lld\n", (long long)us.size); 326 goto success; 327 } 328 329 /* 330 * If the -r flag was specified, we have to compare the local 331 * and remote files, so we should really do a fetchStat() 332 * first, but I know of at least one HTTP server that only 333 * sends the content size in response to GET requests, and 334 * leaves it out of replies to HEAD requests. Also, in the 335 * (frequent) case that the local and remote files match but 336 * the local file is truncated, we have sufficient information 337 * before the compare to issue a correct request. Therefore, 338 * we always issue a GET request as if we were sure the local 339 * file was a truncated copy of the remote file; we can drop 340 * the connection later if we change our minds. 341 */ 342 sb.st_size = -1; 343 if (!o_stdout && stat(path, &sb) == -1 && errno != ENOENT) { 344 warnx("%s: stat()", path); 345 goto failure; 346 } 347 if (!o_stdout && r_flag && S_ISREG(sb.st_mode)) 348 url->offset = sb.st_size; 349 350 /* start the transfer */ 351 if ((f = fetchXGet(url, &us, flags)) == NULL) { 352 warnx("%s: %s", path, fetchLastErrString); 353 goto failure; 354 } 355 if (sigint) 356 goto signal; 357 358 /* check that size is as expected */ 359 if (S_size) { 360 if (us.size == -1) { 361 warnx("%s: size unknown", path); 362 goto failure; 363 } else if (us.size != S_size) { 364 warnx("%s: size mismatch: expected %lld, actual %lld", 365 path, (long long)S_size, (long long)us.size); 366 goto failure; 367 } 368 } 369 370 /* symlink instead of copy */ 371 if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) { 372 if (symlink(url->doc, path) == -1) { 373 warn("%s: symlink()", path); 374 goto failure; 375 } 376 goto success; 377 } 378 379 if (us.size == -1 && !o_stdout) 380 warnx("%s: size of remote file is not known", path); 381 if (v_level > 1) { 382 if (sb.st_size != -1) 383 fprintf(stderr, "local size / mtime: %lld / %ld\n", 384 (long long)sb.st_size, (long)sb.st_mtime); 385 if (us.size != -1) 386 fprintf(stderr, "remote size / mtime: %lld / %ld\n", 387 (long long)us.size, (long)us.mtime); 388 } 389 390 /* open output file */ 391 if (o_stdout) { 392 /* output to stdout */ 393 of = stdout; 394 } else if (r_flag && sb.st_size != -1) { 395 /* resume mode, local file exists */ 396 if (!F_flag && us.mtime && sb.st_mtime != us.mtime) { 397 /* no match! have to refetch */ 398 fclose(f); 399 /* if precious, warn the user and give up */ 400 if (R_flag) { 401 warnx("%s: local modification time " 402 "does not match remote", path); 403 goto failure_keep; 404 } 405 } else { 406 if (us.size == sb.st_size) 407 /* nothing to do */ 408 goto success; 409 if (sb.st_size > us.size) { 410 /* local file too long! */ 411 warnx("%s: local file (%lld bytes) is longer " 412 "than remote file (%lld bytes)", path, 413 (long long)sb.st_size, (long long)us.size); 414 goto failure; 415 } 416 /* we got it, open local file */ 417 if ((of = fopen(path, "a")) == NULL) { 418 warn("%s: fopen()", path); 419 goto failure; 420 } 421 /* check that it didn't move under our feet */ 422 if (fstat(fileno(of), &nsb) == -1) { 423 /* can't happen! */ 424 warn("%s: fstat()", path); 425 goto failure; 426 } 427 if (nsb.st_dev != sb.st_dev || 428 nsb.st_ino != nsb.st_ino || 429 nsb.st_size != sb.st_size) { 430 warnx("%s: file has changed", path); 431 fclose(of); 432 of = NULL; 433 sb = nsb; 434 } 435 } 436 } else if (m_flag && sb.st_size != -1) { 437 /* mirror mode, local file exists */ 438 if (sb.st_size == us.size && sb.st_mtime == us.mtime) 439 goto success; 440 } 441 442 if (of == NULL) { 443 /* 444 * We don't yet have an output file; either this is a 445 * vanilla run with no special flags, or the local and 446 * remote files didn't match. 447 */ 448 449 if (url->offset != 0) { 450 /* 451 * We tried to restart a transfer, but for 452 * some reason gave up - so we have to restart 453 * from scratch if we want the whole file 454 */ 455 url->offset = 0; 456 if ((f = fetchXGet(url, &us, flags)) == NULL) { 457 warnx("%s: %s", path, fetchLastErrString); 458 goto failure; 459 } 460 if (sigint) 461 goto signal; 462 } 463 464 /* construct a temp file name */ 465 if (sb.st_size != -1 && S_ISREG(sb.st_mode)) { 466 if ((slash = strrchr(path, '/')) == NULL) 467 slash = path; 468 else 469 ++slash; 470 asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s", 471 (int)(slash - path), path, slash); 472 } 473 474 if (tmppath != NULL) { 475 mkstemps(tmppath, strlen(slash) + 1); 476 warnx("tmppath: %s", tmppath); 477 of = fopen(tmppath, "w"); 478 } else { 479 of = fopen(path, "w"); 480 } 481 482 if (of == NULL) { 483 warn("%s: open()", path); 484 goto failure; 485 } 486 } 487 count = url->offset; 488 489 /* start the counter */ 490 stat_start(&xs, path, us.size, count); 491 492 sigalrm = siginfo = sigint = 0; 493 494 /* suck in the data */ 495 signal(SIGINFO, sig_handler); 496 while (!sigint && !sigalrm) { 497 if (us.size != -1 && us.size - count < B_size) 498 size = us.size - count; 499 else 500 size = B_size; 501 if (timeout) 502 alarm(timeout); 503 if (siginfo) { 504 stat_end(&xs); 505 siginfo = 0; 506 } 507 if ((size = fread(buf, 1, size, f)) == 0) { 508 if (ferror(f) && errno == EINTR && !sigalrm && !sigint) 509 clearerr(f); 510 else 511 break; 512 } 513 if (timeout) 514 alarm(0); 515 stat_update(&xs, count += size); 516 for (ptr = buf; size > 0; ptr += wr, size -= wr) 517 if ((wr = fwrite(ptr, 1, size, of)) < size) { 518 if (ferror(of) && errno == EINTR && 519 !sigalrm && !sigint) 520 clearerr(of); 521 else 522 break; 523 } 524 if (size != 0) 525 break; 526 } 527 signal(SIGINFO, SIG_DFL); 528 529 if (timeout) 530 alarm(0); 531 532 stat_end(&xs); 533 534 /* set mtime of local file */ 535 if (!n_flag && us.mtime && !o_stdout 536 && (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) { 537 struct timeval tv[2]; 538 539 fflush(of); 540 tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime); 541 tv[1].tv_sec = (long)us.mtime; 542 tv[0].tv_usec = tv[1].tv_usec = 0; 543 if (utimes(path, tv)) 544 warn("%s: utimes()", path); 545 } 546 547 /* timed out or interrupted? */ 548 signal: 549 if (sigalrm) 550 warnx("transfer timed out"); 551 if (sigint) { 552 warnx("transfer interrupted"); 553 goto failure; 554 } 555 556 if (!sigalrm) { 557 /* check the status of our files */ 558 if (ferror(f)) 559 warn("%s", URL); 560 if (ferror(of)) 561 warn("%s", path); 562 if (ferror(f) || ferror(of)) 563 goto failure; 564 } 565 566 /* did the transfer complete normally? */ 567 if (us.size != -1 && count < us.size) { 568 warnx("%s appears to be truncated: %lld/%lld bytes", 569 path, (long long)count, (long long)us.size); 570 goto failure_keep; 571 } 572 573 /* 574 * If the transfer timed out and we didn't know how much to 575 * expect, assume the worst (i.e. we didn't get all of it) 576 */ 577 if (sigalrm && us.size == -1) { 578 warnx("%s may be truncated", path); 579 goto failure_keep; 580 } 581 582 success: 583 r = 0; 584 if (tmppath != NULL && rename(tmppath, path) == -1) { 585 warn("%s: rename()", path); 586 goto failure_keep; 587 } 588 goto done; 589 failure: 590 if (of && of != stdout && !R_flag && !r_flag) 591 if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG)) 592 unlink(tmppath ? tmppath : path); 593 if (R_flag && tmppath != NULL && sb.st_size == -1) 594 rename(tmppath, path); /* ignore errors here */ 595 failure_keep: 596 r = -1; 597 goto done; 598 done: 599 if (f) 600 fclose(f); 601 if (of && of != stdout) 602 fclose(of); 603 if (url) 604 fetchFreeURL(url); 605 if (tmppath != NULL) 606 free(tmppath); 607 return r; 608} 609 610static void 611usage(void) 612{ 613 fprintf(stderr, "%s\n%s\n%s\n", 614 "Usage: fetch [-146AFMPRUadlmnpqrsv] [-o outputfile] [-S bytes]", 615 " [-B bytes] [-T seconds] [-w seconds]", 616 " [-h host -f file [-c dir] | URL ...]"); 617} 618 619 620#define PARSENUM(NAME, TYPE) \ 621static int \ 622NAME(const char *s, TYPE *v) \ 623{ \ 624 *v = 0; \ 625 for (*v = 0; *s; s++) \ 626 if (isdigit(*s)) \ 627 *v = *v * 10 + *s - '0'; \ 628 else \ 629 return -1; \ 630 return 0; \ 631} 632 633PARSENUM(parseint, u_int); 634PARSENUM(parseoff, off_t); 635 636/* 637 * Entry point 638 */ 639int 640main(int argc, char *argv[]) 641{ 642 struct stat sb; 643 struct sigaction sa; 644 const char *p, *s; 645 char *q; 646 int c, e, r; 647 648 while ((c = getopt(argc, argv, 649 "146AaB:bc:dFf:Hh:lMmnPpo:qRrS:sT:tUvw:")) != EOF) 650 switch (c) { 651 case '1': 652 once_flag = 1; 653 break; 654 case '4': 655 family = PF_INET; 656 break; 657 case '6': 658 family = PF_INET6; 659 break; 660 case 'A': 661 A_flag = 1; 662 break; 663 case 'a': 664 a_flag = 1; 665 break; 666 case 'B': 667 if (parseoff(optarg, &B_size) == -1) 668 errx(1, "invalid buffer size (%s)", optarg); 669 break; 670 case 'b': 671 warnx("warning: the -b option is deprecated"); 672 b_flag = 1; 673 break; 674 case 'c': 675 c_dirname = optarg; 676 break; 677 case 'd': 678 d_flag = 1; 679 break; 680 case 'F': 681 F_flag = 1; 682 break; 683 case 'f': 684 f_filename = optarg; 685 break; 686 case 'H': 687 warnx("The -H option is now implicit, " 688 "use -U to disable"); 689 break; 690 case 'h': 691 h_hostname = optarg; 692 break; 693 case 'l': 694 l_flag = 1; 695 break; 696 case 'o': 697 o_flag = 1; 698 o_filename = optarg; 699 break; 700 case 'M': 701 case 'm': 702 if (r_flag) 703 errx(1, "the -m and -r flags " 704 "are mutually exclusive"); 705 m_flag = 1; 706 break; 707 case 'n': 708 n_flag = 1; 709 break; 710 case 'P': 711 case 'p': 712 p_flag = 1; 713 break; 714 case 'q': 715 v_level = 0; 716 break; 717 case 'R': 718 R_flag = 1; 719 break; 720 case 'r': 721 if (m_flag) 722 errx(1, "the -m and -r flags " 723 "are mutually exclusive"); 724 r_flag = 1; 725 break; 726 case 'S': 727 if (parseoff(optarg, &S_size) == -1) 728 errx(1, "invalid size (%s)", optarg); 729 break; 730 case 's': 731 s_flag = 1; 732 break; 733 case 'T': 734 if (parseint(optarg, &T_secs) == -1) 735 errx(1, "invalid timeout (%s)", optarg); 736 break; 737 case 't': 738 t_flag = 1; 739 warnx("warning: the -t option is deprecated"); 740 break; 741 case 'U': 742 U_flag = 1; 743 break; 744 case 'v': 745 v_level++; 746 break; 747 case 'w': 748 a_flag = 1; 749 if (parseint(optarg, &w_secs) == -1) 750 errx(1, "invalid delay (%s)", optarg); 751 break; 752 default: 753 usage(); 754 exit(EX_USAGE); 755 } 756 757 argc -= optind; 758 argv += optind; 759 760 if (h_hostname || f_filename || c_dirname) { 761 if (!h_hostname || !f_filename || argc) { 762 usage(); 763 exit(EX_USAGE); 764 } 765 /* XXX this is a hack. */ 766 if (strcspn(h_hostname, "@:/") != strlen(h_hostname)) 767 errx(1, "invalid hostname"); 768 if (asprintf(argv, "ftp://%s/%s/%s", h_hostname, 769 c_dirname ? c_dirname : "", f_filename) == -1) 770 errx(1, "%s", strerror(ENOMEM)); 771 argc++; 772 } 773 774 if (!argc) { 775 usage(); 776 exit(EX_USAGE); 777 } 778 779 /* allocate buffer */ 780 if (B_size < MINBUFSIZE) 781 B_size = MINBUFSIZE; 782 if ((buf = malloc(B_size)) == NULL) 783 errx(1, "%s", strerror(ENOMEM)); 784 785 /* timeouts */ 786 if ((s = getenv("FTP_TIMEOUT")) != NULL) { 787 if (parseint(s, &ftp_timeout) == -1) { 788 warnx("FTP_TIMEOUT (%s) is not a positive integer", 789 optarg); 790 ftp_timeout = 0; 791 } 792 } 793 if ((s = getenv("HTTP_TIMEOUT")) != NULL) { 794 if (parseint(s, &http_timeout) == -1) { 795 warnx("HTTP_TIMEOUT (%s) is not a positive integer", 796 optarg); 797 http_timeout = 0; 798 } 799 } 800 801 /* signal handling */ 802 sa.sa_flags = 0; 803 sa.sa_handler = sig_handler; 804 sigemptyset(&sa.sa_mask); 805 sigaction(SIGALRM, &sa, NULL); 806 sa.sa_flags = SA_RESETHAND; 807 sigaction(SIGINT, &sa, NULL); 808 fetchRestartCalls = 0; 809 810 /* output file */ 811 if (o_flag) { 812 if (strcmp(o_filename, "-") == 0) { 813 o_stdout = 1; 814 } else if (stat(o_filename, &sb) == -1) { 815 if (errno == ENOENT) { 816 if (argc > 1) 817 errx(EX_USAGE, "%s is not a directory", 818 o_filename); 819 } else { 820 err(EX_IOERR, "%s", o_filename); 821 } 822 } else { 823 if (sb.st_mode & S_IFDIR) 824 o_directory = 1; 825 } 826 } 827 828 /* check if output is to a tty (for progress report) */ 829 v_tty = isatty(STDERR_FILENO); 830 r = 0; 831 832 /* authentication */ 833 if (v_tty) 834 fetchAuthMethod = query_auth; 835 836 while (argc) { 837 if ((p = strrchr(*argv, '/')) == NULL) 838 p = *argv; 839 else 840 p++; 841 842 if (!*p) 843 p = "fetch.out"; 844 845 fetchLastErrCode = 0; 846 847 if (o_flag) { 848 if (o_stdout) { 849 e = fetch(*argv, "-"); 850 } else if (o_directory) { 851 asprintf(&q, "%s/%s", o_filename, p); 852 e = fetch(*argv, q); 853 free(q); 854 } else { 855 e = fetch(*argv, o_filename); 856 } 857 } else { 858 e = fetch(*argv, p); 859 } 860 861 if (sigint) 862 kill(getpid(), SIGINT); 863 864 if (e == 0 && once_flag) 865 exit(0); 866 867 if (e) { 868 r = 1; 869 if ((fetchLastErrCode 870 && fetchLastErrCode != FETCH_UNAVAIL 871 && fetchLastErrCode != FETCH_MOVED 872 && fetchLastErrCode != FETCH_URL 873 && fetchLastErrCode != FETCH_RESOLV 874 && fetchLastErrCode != FETCH_UNKNOWN)) { 875 if (w_secs && v_level) 876 fprintf(stderr, "Waiting %d seconds " 877 "before retrying\n", w_secs); 878 if (w_secs) 879 sleep(w_secs); 880 if (a_flag) 881 continue; 882 } 883 } 884 885 argc--, argv++; 886 } 887 888 exit(r); 889} 890