fetch.c revision 106041
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 106041 2002-10-27 15:15:13Z 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 (sigalrm || sigint) 391 goto signal; 392 if (f == NULL) { 393 warnx("%s: %s", path, fetchLastErrString); 394 goto failure; 395 } 396 if (sigint) 397 goto signal; 398 399 /* check that size is as expected */ 400 if (S_size) { 401 if (us.size == -1) { 402 warnx("%s: size unknown", path); 403 goto failure; 404 } else if (us.size != S_size) { 405 warnx("%s: size mismatch: expected %lld, actual %lld", 406 path, (long long)S_size, (long long)us.size); 407 goto failure; 408 } 409 } 410 411 /* symlink instead of copy */ 412 if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) { 413 if (symlink(url->doc, path) == -1) { 414 warn("%s: symlink()", path); 415 goto failure; 416 } 417 goto success; 418 } 419 420 if (us.size == -1 && !o_stdout) 421 warnx("%s: size of remote file is not known", path); 422 if (v_level > 1) { 423 if (sb.st_size != -1) 424 fprintf(stderr, "local size / mtime: %lld / %ld\n", 425 (long long)sb.st_size, (long)sb.st_mtime); 426 if (us.size != -1) 427 fprintf(stderr, "remote size / mtime: %lld / %ld\n", 428 (long long)us.size, (long)us.mtime); 429 } 430 431 /* open output file */ 432 if (o_stdout) { 433 /* output to stdout */ 434 of = stdout; 435 } else if (r_flag && sb.st_size != -1) { 436 /* resume mode, local file exists */ 437 if (!F_flag && us.mtime && sb.st_mtime != us.mtime) { 438 /* no match! have to refetch */ 439 fclose(f); 440 /* if precious, warn the user and give up */ 441 if (R_flag) { 442 warnx("%s: local modification time " 443 "does not match remote", path); 444 goto failure_keep; 445 } 446 } else { 447 if (us.size == sb.st_size) 448 /* nothing to do */ 449 goto success; 450 if (sb.st_size > us.size) { 451 /* local file too long! */ 452 warnx("%s: local file (%lld bytes) is longer " 453 "than remote file (%lld bytes)", path, 454 (long long)sb.st_size, (long long)us.size); 455 goto failure; 456 } 457 /* we got it, open local file */ 458 if ((of = fopen(path, "a")) == NULL) { 459 warn("%s: fopen()", path); 460 goto failure; 461 } 462 /* check that it didn't move under our feet */ 463 if (fstat(fileno(of), &nsb) == -1) { 464 /* can't happen! */ 465 warn("%s: fstat()", path); 466 goto failure; 467 } 468 if (nsb.st_dev != sb.st_dev || 469 nsb.st_ino != nsb.st_ino || 470 nsb.st_size != sb.st_size) { 471 warnx("%s: file has changed", path); 472 fclose(of); 473 of = NULL; 474 sb = nsb; 475 } 476 } 477 } else if (m_flag && sb.st_size != -1) { 478 /* mirror mode, local file exists */ 479 if (sb.st_size == us.size && sb.st_mtime == us.mtime) 480 goto success; 481 } 482 483 if (of == NULL) { 484 /* 485 * We don't yet have an output file; either this is a 486 * vanilla run with no special flags, or the local and 487 * remote files didn't match. 488 */ 489 490 if (url->offset != 0) { 491 /* 492 * We tried to restart a transfer, but for 493 * some reason gave up - so we have to restart 494 * from scratch if we want the whole file 495 */ 496 url->offset = 0; 497 if ((f = fetchXGet(url, &us, flags)) == NULL) { 498 warnx("%s: %s", path, fetchLastErrString); 499 goto failure; 500 } 501 if (sigint) 502 goto signal; 503 } 504 505 /* construct a temp file name */ 506 if (sb.st_size != -1 && S_ISREG(sb.st_mode)) { 507 if ((slash = strrchr(path, '/')) == NULL) 508 slash = path; 509 else 510 ++slash; 511 asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s", 512 (int)(slash - path), path, slash); 513 if (tmppath != NULL) { 514 mkstemps(tmppath, strlen(slash) + 1); 515 of = fopen(tmppath, "w"); 516 } 517 } 518 if (of == NULL) 519 of = fopen(path, "w"); 520 if (of == NULL) { 521 warn("%s: open()", path); 522 goto failure; 523 } 524 } 525 count = url->offset; 526 527 /* start the counter */ 528 stat_start(&xs, path, us.size, count); 529 530 sigalrm = siginfo = sigint = 0; 531 532 /* suck in the data */ 533 signal(SIGINFO, sig_handler); 534 while (!sigint) { 535 if (us.size != -1 && us.size - count < B_size) 536 size = us.size - count; 537 else 538 size = B_size; 539 if (siginfo) { 540 stat_end(&xs); 541 siginfo = 0; 542 } 543 if ((size = fread(buf, 1, size, f)) == 0) { 544 if (ferror(f) && errno == EINTR && !sigint) 545 clearerr(f); 546 else 547 break; 548 } 549 stat_update(&xs, count += size); 550 for (ptr = buf; size > 0; ptr += wr, size -= wr) 551 if ((wr = fwrite(ptr, 1, size, of)) < size) { 552 if (ferror(of) && errno == EINTR && !sigint) 553 clearerr(of); 554 else 555 break; 556 } 557 if (size != 0) 558 break; 559 } 560 if (!sigalrm) 561 sigalrm = ferror(f) && errno == ETIMEDOUT; 562 signal(SIGINFO, SIG_DFL); 563 564 stat_end(&xs); 565 566 /* 567 * If the transfer timed out or was interrupted, we still want to 568 * set the mtime in case the file is not removed (-r or -R) and 569 * the user later restarts the transfer. 570 */ 571 signal: 572 /* set mtime of local file */ 573 if (!n_flag && us.mtime && !o_stdout 574 && (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) { 575 struct timeval tv[2]; 576 577 fflush(of); 578 tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime); 579 tv[1].tv_sec = (long)us.mtime; 580 tv[0].tv_usec = tv[1].tv_usec = 0; 581 if (utimes(tmppath ? tmppath : path, tv)) 582 warn("%s: utimes()", tmppath ? tmppath : path); 583 } 584 585 /* timed out or interrupted? */ 586 if (sigalrm) 587 warnx("transfer timed out"); 588 if (sigint) { 589 warnx("transfer interrupted"); 590 goto failure; 591 } 592 593 if (!sigalrm) { 594 /* check the status of our files */ 595 if (ferror(f)) 596 warn("%s", URL); 597 if (ferror(of)) 598 warn("%s", path); 599 if (ferror(f) || ferror(of)) 600 goto failure; 601 } 602 603 /* did the transfer complete normally? */ 604 if (us.size != -1 && count < us.size) { 605 warnx("%s appears to be truncated: %lld/%lld bytes", 606 path, (long long)count, (long long)us.size); 607 goto failure_keep; 608 } 609 610 /* 611 * If the transfer timed out and we didn't know how much to 612 * expect, assume the worst (i.e. we didn't get all of it) 613 */ 614 if (sigalrm && us.size == -1) { 615 warnx("%s may be truncated", path); 616 goto failure_keep; 617 } 618 619 success: 620 r = 0; 621 if (tmppath != NULL && rename(tmppath, path) == -1) { 622 warn("%s: rename()", path); 623 goto failure_keep; 624 } 625 goto done; 626 failure: 627 if (of && of != stdout && !R_flag && !r_flag) 628 if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG)) 629 unlink(tmppath ? tmppath : path); 630 if (R_flag && tmppath != NULL && sb.st_size == -1) 631 rename(tmppath, path); /* ignore errors here */ 632 failure_keep: 633 r = -1; 634 goto done; 635 done: 636 if (f) 637 fclose(f); 638 if (of && of != stdout) 639 fclose(of); 640 if (url) 641 fetchFreeURL(url); 642 if (tmppath != NULL) 643 free(tmppath); 644 return r; 645} 646 647static void 648usage(void) 649{ 650 fprintf(stderr, "%s\n%s\n%s\n", 651 "usage: fetch [-146AFMPRUadlmnpqrsv] [-o outputfile] [-S bytes]", 652 " [-B bytes] [-T seconds] [-w seconds]", 653 " [-h host -f file [-c dir] | URL ...]"); 654} 655 656 657/* 658 * Entry point 659 */ 660int 661main(int argc, char *argv[]) 662{ 663 struct stat sb; 664 struct sigaction sa; 665 const char *p, *s; 666 char *end, *q; 667 int c, e, r; 668 669 while ((c = getopt(argc, argv, 670 "146AaB:bc:dFf:Hh:lMmnPpo:qRrS:sT:tUvw:")) != -1) 671 switch (c) { 672 case '1': 673 once_flag = 1; 674 break; 675 case '4': 676 family = PF_INET; 677 break; 678 case '6': 679 family = PF_INET6; 680 break; 681 case 'A': 682 A_flag = 1; 683 break; 684 case 'a': 685 a_flag = 1; 686 break; 687 case 'B': 688 B_size = (off_t)strtol(optarg, &end, 10); 689 if (*optarg == '\0' || *end != '\0') 690 errx(1, "invalid buffer size (%s)", optarg); 691 break; 692 case 'b': 693 warnx("warning: the -b option is deprecated"); 694 b_flag = 1; 695 break; 696 case 'c': 697 c_dirname = optarg; 698 break; 699 case 'd': 700 d_flag = 1; 701 break; 702 case 'F': 703 F_flag = 1; 704 break; 705 case 'f': 706 f_filename = optarg; 707 break; 708 case 'H': 709 warnx("the -H option is now implicit, " 710 "use -U to disable"); 711 break; 712 case 'h': 713 h_hostname = optarg; 714 break; 715 case 'l': 716 l_flag = 1; 717 break; 718 case 'o': 719 o_flag = 1; 720 o_filename = optarg; 721 break; 722 case 'M': 723 case 'm': 724 if (r_flag) 725 errx(1, "the -m and -r flags " 726 "are mutually exclusive"); 727 m_flag = 1; 728 break; 729 case 'n': 730 n_flag = 1; 731 break; 732 case 'P': 733 case 'p': 734 p_flag = 1; 735 break; 736 case 'q': 737 v_level = 0; 738 break; 739 case 'R': 740 R_flag = 1; 741 break; 742 case 'r': 743 if (m_flag) 744 errx(1, "the -m and -r flags " 745 "are mutually exclusive"); 746 r_flag = 1; 747 break; 748 case 'S': 749 S_size = (off_t)strtol(optarg, &end, 10); 750 if (*optarg == '\0' || *end != '\0') 751 errx(1, "invalid size (%s)", optarg); 752 break; 753 case 's': 754 s_flag = 1; 755 break; 756 case 'T': 757 T_secs = strtol(optarg, &end, 10); 758 if (*optarg == '\0' || *end != '\0') 759 errx(1, "invalid timeout (%s)", optarg); 760 break; 761 case 't': 762 t_flag = 1; 763 warnx("warning: the -t option is deprecated"); 764 break; 765 case 'U': 766 U_flag = 1; 767 break; 768 case 'v': 769 v_level++; 770 break; 771 case 'w': 772 a_flag = 1; 773 w_secs = strtol(optarg, &end, 10); 774 if (*optarg == '\0' || *end != '\0') 775 errx(1, "invalid delay (%s)", optarg); 776 break; 777 default: 778 usage(); 779 exit(EX_USAGE); 780 } 781 782 argc -= optind; 783 argv += optind; 784 785 if (h_hostname || f_filename || c_dirname) { 786 if (!h_hostname || !f_filename || argc) { 787 usage(); 788 exit(EX_USAGE); 789 } 790 /* XXX this is a hack. */ 791 if (strcspn(h_hostname, "@:/") != strlen(h_hostname)) 792 errx(1, "invalid hostname"); 793 if (asprintf(argv, "ftp://%s/%s/%s", h_hostname, 794 c_dirname ? c_dirname : "", f_filename) == -1) 795 errx(1, "%s", strerror(ENOMEM)); 796 argc++; 797 } 798 799 if (!argc) { 800 usage(); 801 exit(EX_USAGE); 802 } 803 804 /* allocate buffer */ 805 if (B_size < MINBUFSIZE) 806 B_size = MINBUFSIZE; 807 if ((buf = malloc(B_size)) == NULL) 808 errx(1, "%s", strerror(ENOMEM)); 809 810 /* timeouts */ 811 if ((s = getenv("FTP_TIMEOUT")) != NULL) { 812 ftp_timeout = strtol(s, &end, 10); 813 if (*s == '\0' || *end != '\0' || ftp_timeout < 0) { 814 warnx("FTP_TIMEOUT (%s) is not a positive integer", s); 815 ftp_timeout = 0; 816 } 817 } 818 if ((s = getenv("HTTP_TIMEOUT")) != NULL) { 819 http_timeout = strtol(s, &end, 10); 820 if (*s == '\0' || *end != '\0' || http_timeout < 0) { 821 warnx("HTTP_TIMEOUT (%s) is not a positive integer", s); 822 http_timeout = 0; 823 } 824 } 825 826 /* signal handling */ 827 sa.sa_flags = 0; 828 sa.sa_handler = sig_handler; 829 sigemptyset(&sa.sa_mask); 830 sigaction(SIGALRM, &sa, NULL); 831 sa.sa_flags = SA_RESETHAND; 832 sigaction(SIGINT, &sa, NULL); 833 fetchRestartCalls = 0; 834 835 /* output file */ 836 if (o_flag) { 837 if (strcmp(o_filename, "-") == 0) { 838 o_stdout = 1; 839 } else if (stat(o_filename, &sb) == -1) { 840 if (errno == ENOENT) { 841 if (argc > 1) 842 errx(EX_USAGE, "%s is not a directory", 843 o_filename); 844 } else { 845 err(EX_IOERR, "%s", o_filename); 846 } 847 } else { 848 if (sb.st_mode & S_IFDIR) 849 o_directory = 1; 850 } 851 } 852 853 /* check if output is to a tty (for progress report) */ 854 v_tty = isatty(STDERR_FILENO); 855 if (v_tty) 856 pgrp = getpgrp(); 857 858 r = 0; 859 860 /* authentication */ 861 if (v_tty) 862 fetchAuthMethod = query_auth; 863 864 while (argc) { 865 if ((p = strrchr(*argv, '/')) == NULL) 866 p = *argv; 867 else 868 p++; 869 870 if (!*p) 871 p = "fetch.out"; 872 873 fetchLastErrCode = 0; 874 875 if (o_flag) { 876 if (o_stdout) { 877 e = fetch(*argv, "-"); 878 } else if (o_directory) { 879 asprintf(&q, "%s/%s", o_filename, p); 880 e = fetch(*argv, q); 881 free(q); 882 } else { 883 e = fetch(*argv, o_filename); 884 } 885 } else { 886 e = fetch(*argv, p); 887 } 888 889 if (sigint) 890 kill(getpid(), SIGINT); 891 892 if (e == 0 && once_flag) 893 exit(0); 894 895 if (e) { 896 r = 1; 897 if ((fetchLastErrCode 898 && fetchLastErrCode != FETCH_UNAVAIL 899 && fetchLastErrCode != FETCH_MOVED 900 && fetchLastErrCode != FETCH_URL 901 && fetchLastErrCode != FETCH_RESOLV 902 && fetchLastErrCode != FETCH_UNKNOWN)) { 903 if (w_secs && v_level) 904 fprintf(stderr, "Waiting %ld seconds " 905 "before retrying\n", w_secs); 906 if (w_secs) 907 sleep(w_secs); 908 if (a_flag) 909 continue; 910 } 911 } 912 913 argc--, argv++; 914 } 915 916 exit(r); 917} 918