fetch.c revision 100834
1139825Simp/*- 21541Srgrimes * Copyright (c) 2000 Dag-Erling Co�dan Sm�rgrav 31541Srgrimes * All rights reserved. 41541Srgrimes * 51541Srgrimes * Redistribution and use in source and binary forms, with or without 61541Srgrimes * modification, are permitted provided that the following conditions 71541Srgrimes * are met: 81541Srgrimes * 1. Redistributions of source code must retain the above copyright 91541Srgrimes * notice, this list of conditions and the following disclaimer 101541Srgrimes * in this position and unchanged. 111541Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 121541Srgrimes * notice, this list of conditions and the following disclaimer in the 131541Srgrimes * documentation and/or other materials provided with the distribution. 141541Srgrimes * 3. The name of the author may not be used to endorse or promote products 151541Srgrimes * derived from this software without specific prior written permission 161541Srgrimes * 171541Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 181541Srgrimes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 191541Srgrimes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 201541Srgrimes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 211541Srgrimes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 221541Srgrimes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 231541Srgrimes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 241541Srgrimes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 251541Srgrimes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 261541Srgrimes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 271541Srgrimes */ 281541Srgrimes 291541Srgrimes#include <sys/cdefs.h> 301541Srgrimes__FBSDID("$FreeBSD: head/usr.bin/fetch/fetch.c 100834 2002-07-28 21:09:25Z des $"); 311541Srgrimes 321541Srgrimes#include <sys/param.h> 331541Srgrimes#include <sys/socket.h> 341541Srgrimes#include <sys/stat.h> 3550477Speter#include <sys/time.h> 361541Srgrimes 371541Srgrimes#include <ctype.h> 381541Srgrimes#include <err.h> 391541Srgrimes#include <errno.h> 401541Srgrimes#include <signal.h> 411541Srgrimes#include <stdio.h> 421541Srgrimes#include <stdlib.h> 431541Srgrimes#include <string.h> 441541Srgrimes#include <sysexits.h> 4533058Sbde#include <termios.h> 4633058Sbde#include <unistd.h> 4760938Sjake 481541Srgrimes#include <fetch.h> 49137457Sphk 50194766Skib#define MINBUFSIZE 4096 51194766Skib 52137457Sphk/* Option flags */ 53137457Sphkint A_flag; /* -A: do not follow 302 redirects */ 54137457Sphkint a_flag; /* -a: auto retry */ 55137457Sphkoff_t B_size; /* -B: buffer size */ 56137457Sphkint b_flag; /*! -b: workaround TCP bug */ 57137457Sphkchar *c_dirname; /* -c: remote directory */ 585455Sdgint d_flag; /* -d: direct connection */ 59137457Sphkint F_flag; /* -F: restart without checking mtime */ 60137457Sphkchar *f_filename; /* -f: file to fetch */ 61137457Sphkchar *h_hostname; /* -h: host to fetch from */ 62137457Sphkint l_flag; /* -l: link rather than copy file: URLs */ 63137457Sphkint m_flag; /* -[Mm]: mirror mode */ 64137457Sphkint n_flag; /* -n: do not preserve modification time */ 65137457Sphkint o_flag; /* -o: specify output file */ 661541Srgrimesint o_directory; /* output file is a directory */ 671541Srgrimeschar *o_filename; /* name of output file */ 68118384Sphkint o_stdout; /* output file is stdout */ 69118384Sphkint once_flag; /* -1: stop at first successful file */ 70118384Sphkint p_flag; /* -[Pp]: use passive FTP */ 71118384Sphkint R_flag; /* -R: don't delete partially transferred files */ 72118384Sphkint r_flag; /* -r: restart previously interrupted transfer */ 73195840Sjhboff_t S_size; /* -S: require size to match */ 74236925Skibint s_flag; /* -s: show size, don't fetch */ 75118384Sphklong T_secs = 120; /* -T: transfer timeout in seconds */ 761541Srgrimesint t_flag; /*! -t: workaround TCP bug */ 771541Srgrimesint U_flag; /* -U: do not use high ports */ 781541Srgrimesint v_level = 1; /* -v: verbosity level */ 791541Srgrimesint v_tty; /* stdout is a tty */ 801541Srgrimespid_t pgrp; /* our process group */ 811541Srgrimeslong w_secs; /* -w: retry delay */ 821541Srgrimesint family = PF_UNSPEC; /* -[46]: address family to use */ 831541Srgrimes 841541Srgrimesint sigalrm; /* SIGALRM received */ 851541Srgrimesint siginfo; /* SIGINFO received */ 861541Srgrimesint sigint; /* SIGINT received */ 871541Srgrimes 881541Srgrimeslong ftp_timeout; /* default timeout for FTP transfers */ 891541Srgrimeslong http_timeout; /* default timeout for HTTP transfers */ 901541Srgrimesu_char *buf; /* transfer buffer */ 911541Srgrimes 92108358Sdillon 93108358Sdillon/* 94108358Sdillon * Signal handler 9534206Sdyson */ 9655206Speterstatic void 9730354Sphksig_handler(int sig) 989759Sbde{ 9942957Sdillon switch (sig) { 10075474Salfred case SIGALRM: 1019759Sbde sigalrm = 1; 102194766Skib break; 103194766Skib case SIGINFO: 10492727Salfred siginfo = 1; 10592727Salfred break; 10692727Salfred case SIGINT: 10792727Salfred sigint = 1; 10892727Salfred break; 10992727Salfred } 11042957Sdillon} 11146349Salc 11246349Salcstruct xferstat { 11346349Salc char name[40]; 11446349Salc struct timeval start; 11546349Salc struct timeval end; 11646349Salc struct timeval last; 11746349Salc off_t size; 11842957Sdillon off_t offset; 11942957Sdillon off_t rcvd; 12042957Sdillon}; 12142957Sdillon 12242957Sdillon/* 12342957Sdillon * Update the stats display 12442957Sdillon */ 12546349Salcstatic void 12646349Salcstat_display(struct xferstat *xs, int force) 127116710Salc{ 12846349Salc struct timeval now; 12946349Salc int ctty_pgrp; 13046349Salc 13146349Salc if (!v_tty || !v_level) 13292029Seivind return; 13342957Sdillon 13442957Sdillon /* check if we're the foreground process */ 13543129Sdillon if (ioctl(STDERR_FILENO, TIOCGPGRP, &ctty_pgrp) == -1 || 13642957Sdillon (pid_t)ctty_pgrp != pgrp) 13742957Sdillon return; 13842957Sdillon 13942957Sdillon gettimeofday(&now, NULL); 14042957Sdillon if (!force && now.tv_sec <= xs->last.tv_sec) 14142957Sdillon return; 14242957Sdillon xs->last = now; 143121455Salc 144121455Salc fprintf(stderr, "\rReceiving %s", xs->name); 14543129Sdillon if (xs->size <= 0) 14643129Sdillon fprintf(stderr, ": %lld bytes", (long long)xs->rcvd); 14742957Sdillon else 14842957Sdillon fprintf(stderr, " (%lld bytes): %d%%", (long long)xs->size, 14958708Sdillon (int)((100.0 * xs->rcvd) / xs->size)); 15058708Sdillon} 15158708Sdillon 15258708Sdillon/* 15358708Sdillon * Initialize the transfer statistics 15458708Sdillon */ 15558708Sdillonstatic void 15658708Sdillonstat_start(struct xferstat *xs, const char *name, off_t size, off_t offset) 157146355Salc{ 15858708Sdillon snprintf(xs->name, sizeof xs->name, "%s", name); 15942957Sdillon gettimeofday(&xs->start, NULL); 16042957Sdillon xs->last.tv_sec = xs->last.tv_usec = 0; 16142957Sdillon xs->end = xs->last; 16242957Sdillon xs->size = size; 16342957Sdillon xs->offset = offset; 16442957Sdillon xs->rcvd = offset; 16542957Sdillon stat_display(xs, 1); 16676827Salfred} 16776827Salfred 168116695Salc/* 16976827Salfred * Update the transfer statistics 17076827Salfred */ 17176827Salfredstatic void 17242957Sdillonstat_update(struct xferstat *xs, off_t rcvd) 17342957Sdillon{ 17442957Sdillon xs->rcvd = rcvd; 17542957Sdillon stat_display(xs, 0); 17642957Sdillon} 177146355Salc 17842957Sdillon/* 179146355Salc * Finalize the transfer statistics 18042957Sdillon */ 181118528Sphkstatic void 182118528Sphkstat_end(struct xferstat *xs) 183118528Sphk{ 184118528Sphk double delta; 185118528Sphk double bps; 18642957Sdillon 18742957Sdillon if (!v_level) 18842957Sdillon return; 18942957Sdillon 190121455Salc gettimeofday(&xs->end, NULL); 191121455Salc 19242957Sdillon stat_display(xs, 1); 19342957Sdillon fputc('\n', stderr); 19442957Sdillon delta = (xs->end.tv_sec + (xs->end.tv_usec / 1.e6)) 19542957Sdillon - (xs->start.tv_sec + (xs->start.tv_usec / 1.e6)); 196229383Skib fprintf(stderr, "%lld bytes transferred in %.1f seconds ", 197229383Skib (long long)(xs->rcvd - xs->offset), delta); 198229383Skib bps = (xs->rcvd - xs->offset) / delta; 199229383Skib if (bps > 1024*1024) 200229383Skib fprintf(stderr, "(%.2f MBps)\n", bps / (1024*1024)); 201229383Skib else if (bps > 1024) 202229383Skib fprintf(stderr, "(%.2f kBps)\n", bps / 1024); 203229383Skib else 204229383Skib fprintf(stderr, "(%.2f Bps)\n", bps); 205229383Skib} 206229383Skib 207229383Skib/* 208229383Skib * Ask the user for authentication details 209229383Skib */ 21092029Seivindstatic int 2115455Sdgquery_auth(struct url *URL) 212{ 213 struct termios tios; 214 tcflag_t saved_flags; 215 int i, nopwd; 216 217 218 fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n", 219 URL->scheme, URL->host, URL->port); 220 221 fprintf(stderr, "Login: "); 222 if (fgets(URL->user, sizeof URL->user, stdin) == NULL) 223 return -1; 224 for (i = 0; URL->user[i]; ++i) 225 if (isspace(URL->user[i])) 226 URL->user[i] = '\0'; 227 228 fprintf(stderr, "Password: "); 229 if (tcgetattr(STDIN_FILENO, &tios) == 0) { 230 saved_flags = tios.c_lflag; 231 tios.c_lflag &= ~ECHO; 232 tios.c_lflag |= ECHONL|ICANON; 233 tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios); 234 nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL); 235 tios.c_lflag = saved_flags; 236 tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios); 237 } else { 238 nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL); 239 } 240 if (nopwd) 241 return -1; 242 243 for (i = 0; URL->pwd[i]; ++i) 244 if (isspace(URL->pwd[i])) 245 URL->pwd[i] = '\0'; 246 return 0; 247} 248 249/* 250 * Fetch a file 251 */ 252static int 253fetch(char *URL, const char *path) 254{ 255 struct url *url; 256 struct url_stat us; 257 struct stat sb, nsb; 258 struct xferstat xs; 259 FILE *f, *of; 260 size_t size, wr; 261 off_t count; 262 char flags[8]; 263 const char *slash; 264 char *tmppath; 265 int r; 266 u_int timeout; 267 u_char *ptr; 268 269 f = of = NULL; 270 tmppath = NULL; 271 272 /* parse URL */ 273 if ((url = fetchParseURL(URL)) == NULL) { 274 warnx("%s: parse error", URL); 275 goto failure; 276 } 277 278 /* if no scheme was specified, take a guess */ 279 if (!*url->scheme) { 280 if (!*url->host) 281 strcpy(url->scheme, SCHEME_FILE); 282 else if (strncasecmp(url->host, "ftp.", 4) == 0) 283 strcpy(url->scheme, SCHEME_FTP); 284 else if (strncasecmp(url->host, "www.", 4) == 0) 285 strcpy(url->scheme, SCHEME_HTTP); 286 } 287 288 timeout = 0; 289 *flags = 0; 290 count = 0; 291 292 /* common flags */ 293 if (v_level > 1) 294 strcat(flags, "v"); 295 if (v_level > 2) 296 fetchDebug = 1; 297 switch (family) { 298 case PF_INET: 299 strcat(flags, "4"); 300 break; 301 case PF_INET6: 302 strcat(flags, "6"); 303 break; 304 } 305 306 /* FTP specific flags */ 307 if (strcmp(url->scheme, "ftp") == 0) { 308 if (p_flag) 309 strcat(flags, "p"); 310 if (d_flag) 311 strcat(flags, "d"); 312 if (U_flag) 313 strcat(flags, "l"); 314 timeout = T_secs ? T_secs : ftp_timeout; 315 } 316 317 /* HTTP specific flags */ 318 if (strcmp(url->scheme, "http") == 0) { 319 if (d_flag) 320 strcat(flags, "d"); 321 if (A_flag) 322 strcat(flags, "A"); 323 timeout = T_secs ? T_secs : http_timeout; 324 } 325 326 /* set the protocol timeout. */ 327 fetchTimeout = timeout; 328 329 /* just print size */ 330 if (s_flag) { 331 if (fetchStat(url, &us, flags) == -1) 332 goto failure; 333 if (us.size == -1) 334 printf("Unknown\n"); 335 else 336 printf("%lld\n", (long long)us.size); 337 goto success; 338 } 339 340 /* 341 * If the -r flag was specified, we have to compare the local 342 * and remote files, so we should really do a fetchStat() 343 * first, but I know of at least one HTTP server that only 344 * sends the content size in response to GET requests, and 345 * leaves it out of replies to HEAD requests. Also, in the 346 * (frequent) case that the local and remote files match but 347 * the local file is truncated, we have sufficient information 348 * before the compare to issue a correct request. Therefore, 349 * we always issue a GET request as if we were sure the local 350 * file was a truncated copy of the remote file; we can drop 351 * the connection later if we change our minds. 352 */ 353 sb.st_size = -1; 354 if (!o_stdout && stat(path, &sb) == -1 && errno != ENOENT) { 355 warnx("%s: stat()", path); 356 goto failure; 357 } 358 if (!o_stdout && r_flag && S_ISREG(sb.st_mode)) 359 url->offset = sb.st_size; 360 361 /* start the transfer */ 362 if ((f = fetchXGet(url, &us, flags)) == NULL) { 363 warnx("%s: %s", path, fetchLastErrString); 364 goto failure; 365 } 366 if (sigint) 367 goto signal; 368 369 /* check that size is as expected */ 370 if (S_size) { 371 if (us.size == -1) { 372 warnx("%s: size unknown", path); 373 goto failure; 374 } else if (us.size != S_size) { 375 warnx("%s: size mismatch: expected %lld, actual %lld", 376 path, (long long)S_size, (long long)us.size); 377 goto failure; 378 } 379 } 380 381 /* symlink instead of copy */ 382 if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) { 383 if (symlink(url->doc, path) == -1) { 384 warn("%s: symlink()", path); 385 goto failure; 386 } 387 goto success; 388 } 389 390 if (us.size == -1 && !o_stdout) 391 warnx("%s: size of remote file is not known", path); 392 if (v_level > 1) { 393 if (sb.st_size != -1) 394 fprintf(stderr, "local size / mtime: %lld / %ld\n", 395 (long long)sb.st_size, (long)sb.st_mtime); 396 if (us.size != -1) 397 fprintf(stderr, "remote size / mtime: %lld / %ld\n", 398 (long long)us.size, (long)us.mtime); 399 } 400 401 /* open output file */ 402 if (o_stdout) { 403 /* output to stdout */ 404 of = stdout; 405 } else if (r_flag && sb.st_size != -1) { 406 /* resume mode, local file exists */ 407 if (!F_flag && us.mtime && sb.st_mtime != us.mtime) { 408 /* no match! have to refetch */ 409 fclose(f); 410 /* if precious, warn the user and give up */ 411 if (R_flag) { 412 warnx("%s: local modification time " 413 "does not match remote", path); 414 goto failure_keep; 415 } 416 } else { 417 if (us.size == sb.st_size) 418 /* nothing to do */ 419 goto success; 420 if (sb.st_size > us.size) { 421 /* local file too long! */ 422 warnx("%s: local file (%lld bytes) is longer " 423 "than remote file (%lld bytes)", path, 424 (long long)sb.st_size, (long long)us.size); 425 goto failure; 426 } 427 /* we got it, open local file */ 428 if ((of = fopen(path, "a")) == NULL) { 429 warn("%s: fopen()", path); 430 goto failure; 431 } 432 /* check that it didn't move under our feet */ 433 if (fstat(fileno(of), &nsb) == -1) { 434 /* can't happen! */ 435 warn("%s: fstat()", path); 436 goto failure; 437 } 438 if (nsb.st_dev != sb.st_dev || 439 nsb.st_ino != nsb.st_ino || 440 nsb.st_size != sb.st_size) { 441 warnx("%s: file has changed", path); 442 fclose(of); 443 of = NULL; 444 sb = nsb; 445 } 446 } 447 } else if (m_flag && sb.st_size != -1) { 448 /* mirror mode, local file exists */ 449 if (sb.st_size == us.size && sb.st_mtime == us.mtime) 450 goto success; 451 } 452 453 if (of == NULL) { 454 /* 455 * We don't yet have an output file; either this is a 456 * vanilla run with no special flags, or the local and 457 * remote files didn't match. 458 */ 459 460 if (url->offset != 0) { 461 /* 462 * We tried to restart a transfer, but for 463 * some reason gave up - so we have to restart 464 * from scratch if we want the whole file 465 */ 466 url->offset = 0; 467 if ((f = fetchXGet(url, &us, flags)) == NULL) { 468 warnx("%s: %s", path, fetchLastErrString); 469 goto failure; 470 } 471 if (sigint) 472 goto signal; 473 } 474 475 /* construct a temp file name */ 476 if (sb.st_size != -1 && S_ISREG(sb.st_mode)) { 477 if ((slash = strrchr(path, '/')) == NULL) 478 slash = path; 479 else 480 ++slash; 481 asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s", 482 (int)(slash - path), path, slash); 483 if (tmppath != NULL) { 484 mkstemps(tmppath, strlen(slash) + 1); 485 of = fopen(tmppath, "w"); 486 } 487 } 488 if (of == NULL) 489 of = fopen(path, "w"); 490 if (of == NULL) { 491 warn("%s: open()", path); 492 goto failure; 493 } 494 } 495 count = url->offset; 496 497 /* start the counter */ 498 stat_start(&xs, path, us.size, count); 499 500 sigalrm = siginfo = sigint = 0; 501 502 /* suck in the data */ 503 signal(SIGINFO, sig_handler); 504 while (!sigint && !sigalrm) { 505 if (us.size != -1 && us.size - count < B_size) 506 size = us.size - count; 507 else 508 size = B_size; 509 if (timeout) 510 alarm(timeout); 511 if (siginfo) { 512 stat_end(&xs); 513 siginfo = 0; 514 } 515 if ((size = fread(buf, 1, size, f)) == 0) { 516 if (ferror(f) && errno == EINTR && !sigalrm && !sigint) 517 clearerr(f); 518 else 519 break; 520 } 521 if (timeout) 522 alarm(0); 523 stat_update(&xs, count += size); 524 for (ptr = buf; size > 0; ptr += wr, size -= wr) 525 if ((wr = fwrite(ptr, 1, size, of)) < size) { 526 if (ferror(of) && errno == EINTR && 527 !sigalrm && !sigint) 528 clearerr(of); 529 else 530 break; 531 } 532 if (size != 0) 533 break; 534 } 535 signal(SIGINFO, SIG_DFL); 536 537 if (timeout) 538 alarm(0); 539 540 stat_end(&xs); 541 542 /* set mtime of local file */ 543 if (!n_flag && us.mtime && !o_stdout 544 && (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) { 545 struct timeval tv[2]; 546 547 fflush(of); 548 tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime); 549 tv[1].tv_sec = (long)us.mtime; 550 tv[0].tv_usec = tv[1].tv_usec = 0; 551 if (utimes(tmppath ? tmppath : path, tv)) 552 warn("%s: utimes()", tmppath ? tmppath : path); 553 } 554 555 /* timed out or interrupted? */ 556 signal: 557 if (sigalrm) 558 warnx("transfer timed out"); 559 if (sigint) { 560 warnx("transfer interrupted"); 561 goto failure; 562 } 563 564 if (!sigalrm) { 565 /* check the status of our files */ 566 if (ferror(f)) 567 warn("%s", URL); 568 if (ferror(of)) 569 warn("%s", path); 570 if (ferror(f) || ferror(of)) 571 goto failure; 572 } 573 574 /* did the transfer complete normally? */ 575 if (us.size != -1 && count < us.size) { 576 warnx("%s appears to be truncated: %lld/%lld bytes", 577 path, (long long)count, (long long)us.size); 578 goto failure_keep; 579 } 580 581 /* 582 * If the transfer timed out and we didn't know how much to 583 * expect, assume the worst (i.e. we didn't get all of it) 584 */ 585 if (sigalrm && us.size == -1) { 586 warnx("%s may be truncated", path); 587 goto failure_keep; 588 } 589 590 success: 591 r = 0; 592 if (tmppath != NULL && rename(tmppath, path) == -1) { 593 warn("%s: rename()", path); 594 goto failure_keep; 595 } 596 goto done; 597 failure: 598 if (of && of != stdout && !R_flag && !r_flag) 599 if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG)) 600 unlink(tmppath ? tmppath : path); 601 if (R_flag && tmppath != NULL && sb.st_size == -1) 602 rename(tmppath, path); /* ignore errors here */ 603 failure_keep: 604 r = -1; 605 goto done; 606 done: 607 if (f) 608 fclose(f); 609 if (of && of != stdout) 610 fclose(of); 611 if (url) 612 fetchFreeURL(url); 613 if (tmppath != NULL) 614 free(tmppath); 615 return r; 616} 617 618static void 619usage(void) 620{ 621 fprintf(stderr, "%s\n%s\n%s\n", 622 "usage: fetch [-146AFMPRUadlmnpqrsv] [-o outputfile] [-S bytes]", 623 " [-B bytes] [-T seconds] [-w seconds]", 624 " [-h host -f file [-c dir] | URL ...]"); 625} 626 627 628/* 629 * Entry point 630 */ 631int 632main(int argc, char *argv[]) 633{ 634 struct stat sb; 635 struct sigaction sa; 636 const char *p, *s; 637 char *end, *q; 638 int c, e, r; 639 640 while ((c = getopt(argc, argv, 641 "146AaB:bc:dFf:Hh:lMmnPpo:qRrS:sT:tUvw:")) != -1) 642 switch (c) { 643 case '1': 644 once_flag = 1; 645 break; 646 case '4': 647 family = PF_INET; 648 break; 649 case '6': 650 family = PF_INET6; 651 break; 652 case 'A': 653 A_flag = 1; 654 break; 655 case 'a': 656 a_flag = 1; 657 break; 658 case 'B': 659 B_size = (off_t)strtol(optarg, &end, 10); 660 if (*optarg == '\0' || *end != '\0') 661 errx(1, "invalid buffer size (%s)", optarg); 662 break; 663 case 'b': 664 warnx("warning: the -b option is deprecated"); 665 b_flag = 1; 666 break; 667 case 'c': 668 c_dirname = optarg; 669 break; 670 case 'd': 671 d_flag = 1; 672 break; 673 case 'F': 674 F_flag = 1; 675 break; 676 case 'f': 677 f_filename = optarg; 678 break; 679 case 'H': 680 warnx("the -H option is now implicit, " 681 "use -U to disable"); 682 break; 683 case 'h': 684 h_hostname = optarg; 685 break; 686 case 'l': 687 l_flag = 1; 688 break; 689 case 'o': 690 o_flag = 1; 691 o_filename = optarg; 692 break; 693 case 'M': 694 case 'm': 695 if (r_flag) 696 errx(1, "the -m and -r flags " 697 "are mutually exclusive"); 698 m_flag = 1; 699 break; 700 case 'n': 701 n_flag = 1; 702 break; 703 case 'P': 704 case 'p': 705 p_flag = 1; 706 break; 707 case 'q': 708 v_level = 0; 709 break; 710 case 'R': 711 R_flag = 1; 712 break; 713 case 'r': 714 if (m_flag) 715 errx(1, "the -m and -r flags " 716 "are mutually exclusive"); 717 r_flag = 1; 718 break; 719 case 'S': 720 S_size = (off_t)strtol(optarg, &end, 10); 721 if (*optarg == '\0' || *end != '\0') 722 errx(1, "invalid size (%s)", optarg); 723 break; 724 case 's': 725 s_flag = 1; 726 break; 727 case 'T': 728 T_secs = strtol(optarg, &end, 10); 729 if (*optarg == '\0' || *end != '\0') 730 errx(1, "invalid timeout (%s)", optarg); 731 break; 732 case 't': 733 t_flag = 1; 734 warnx("warning: the -t option is deprecated"); 735 break; 736 case 'U': 737 U_flag = 1; 738 break; 739 case 'v': 740 v_level++; 741 break; 742 case 'w': 743 a_flag = 1; 744 w_secs = strtol(optarg, &end, 10); 745 if (*optarg == '\0' || *end != '\0') 746 errx(1, "invalid delay (%s)", optarg); 747 break; 748 default: 749 usage(); 750 exit(EX_USAGE); 751 } 752 753 argc -= optind; 754 argv += optind; 755 756 if (h_hostname || f_filename || c_dirname) { 757 if (!h_hostname || !f_filename || argc) { 758 usage(); 759 exit(EX_USAGE); 760 } 761 /* XXX this is a hack. */ 762 if (strcspn(h_hostname, "@:/") != strlen(h_hostname)) 763 errx(1, "invalid hostname"); 764 if (asprintf(argv, "ftp://%s/%s/%s", h_hostname, 765 c_dirname ? c_dirname : "", f_filename) == -1) 766 errx(1, "%s", strerror(ENOMEM)); 767 argc++; 768 } 769 770 if (!argc) { 771 usage(); 772 exit(EX_USAGE); 773 } 774 775 /* allocate buffer */ 776 if (B_size < MINBUFSIZE) 777 B_size = MINBUFSIZE; 778 if ((buf = malloc(B_size)) == NULL) 779 errx(1, "%s", strerror(ENOMEM)); 780 781 /* timeouts */ 782 if ((s = getenv("FTP_TIMEOUT")) != NULL) { 783 ftp_timeout = strtol(s, &end, 10); 784 if (*optarg == '\0' || *end != '\0' || ftp_timeout < 0) { 785 warnx("FTP_TIMEOUT (%s) is not a positive integer", 786 optarg); 787 ftp_timeout = 0; 788 } 789 } 790 if ((s = getenv("HTTP_TIMEOUT")) != NULL) { 791 http_timeout = strtol(s, &end, 10); 792 if (*optarg == '\0' || *end != '\0' || http_timeout < 0) { 793 warnx("HTTP_TIMEOUT (%s) is not a positive integer", 794 optarg); 795 http_timeout = 0; 796 } 797 } 798 799 /* signal handling */ 800 sa.sa_flags = 0; 801 sa.sa_handler = sig_handler; 802 sigemptyset(&sa.sa_mask); 803 sigaction(SIGALRM, &sa, NULL); 804 sa.sa_flags = SA_RESETHAND; 805 sigaction(SIGINT, &sa, NULL); 806 fetchRestartCalls = 0; 807 808 /* output file */ 809 if (o_flag) { 810 if (strcmp(o_filename, "-") == 0) { 811 o_stdout = 1; 812 } else if (stat(o_filename, &sb) == -1) { 813 if (errno == ENOENT) { 814 if (argc > 1) 815 errx(EX_USAGE, "%s is not a directory", 816 o_filename); 817 } else { 818 err(EX_IOERR, "%s", o_filename); 819 } 820 } else { 821 if (sb.st_mode & S_IFDIR) 822 o_directory = 1; 823 } 824 } 825 826 /* check if output is to a tty (for progress report) */ 827 v_tty = isatty(STDERR_FILENO); 828 if (v_tty) 829 pgrp = getpgrp(); 830 831 r = 0; 832 833 /* authentication */ 834 if (v_tty) 835 fetchAuthMethod = query_auth; 836 837 while (argc) { 838 if ((p = strrchr(*argv, '/')) == NULL) 839 p = *argv; 840 else 841 p++; 842 843 if (!*p) 844 p = "fetch.out"; 845 846 fetchLastErrCode = 0; 847 848 if (o_flag) { 849 if (o_stdout) { 850 e = fetch(*argv, "-"); 851 } else if (o_directory) { 852 asprintf(&q, "%s/%s", o_filename, p); 853 e = fetch(*argv, q); 854 free(q); 855 } else { 856 e = fetch(*argv, o_filename); 857 } 858 } else { 859 e = fetch(*argv, p); 860 } 861 862 if (sigint) 863 kill(getpid(), SIGINT); 864 865 if (e == 0 && once_flag) 866 exit(0); 867 868 if (e) { 869 r = 1; 870 if ((fetchLastErrCode 871 && fetchLastErrCode != FETCH_UNAVAIL 872 && fetchLastErrCode != FETCH_MOVED 873 && fetchLastErrCode != FETCH_URL 874 && fetchLastErrCode != FETCH_RESOLV 875 && fetchLastErrCode != FETCH_UNKNOWN)) { 876 if (w_secs && v_level) 877 fprintf(stderr, "Waiting %ld seconds " 878 "before retrying\n", w_secs); 879 if (w_secs) 880 sleep(w_secs); 881 if (a_flag) 882 continue; 883 } 884 } 885 886 argc--, argv++; 887 } 888 889 exit(r); 890} 891