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