1/* vi: set sw=4 ts=4: */ 2/* 3 * wget - retrieve a file using HTTP or FTP 4 * 5 * Chip Rosenthal Covad Communications <chip@laserlink.net> 6 * 7 */ 8 9/* We want libc to give us xxx64 functions also */ 10/* http://www.unix.org/version2/whatsnew/lfs20mar.html */ 11//#define _LARGEFILE64_SOURCE 1 12 13#include <getopt.h> /* for struct option */ 14#include "libbb.h" 15 16struct host_info { 17 // May be used if we ever will want to free() all xstrdup()s... 18 /* char *allocated; */ 19 char *host; 20 int port; 21 char *path; 22 int is_ftp; 23 char *user; 24}; 25 26static void parse_url(char *url, struct host_info *h); 27static FILE *open_socket(len_and_sockaddr *lsa); 28static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc); 29static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf); 30 31/* Globals (can be accessed from signal handlers */ 32static off_t content_len; /* Content-length of the file */ 33static off_t beg_range; /* Range at which continue begins */ 34#if ENABLE_FEATURE_WGET_STATUSBAR 35static off_t transferred; /* Number of bytes transferred so far */ 36#endif 37static bool chunked; /* chunked transfer encoding */ 38#if ENABLE_FEATURE_WGET_STATUSBAR 39static void progressmeter(int flag); 40static const char *curfile; /* Name of current file being transferred */ 41enum { 42 STALLTIME = 5 /* Seconds when xfer considered "stalled" */ 43}; 44#else 45static ALWAYS_INLINE void progressmeter(int flag) {} 46#endif 47 48/* Read NMEMB bytes into PTR from STREAM. Returns the number of bytes read, 49 * and a short count if an eof or non-interrupt error is encountered. */ 50static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream) 51{ 52 size_t ret; 53 char *p = (char*)ptr; 54 55 do { 56 clearerr(stream); 57 ret = fread(p, 1, nmemb, stream); 58 p += ret; 59 nmemb -= ret; 60 } while (nmemb && ferror(stream) && errno == EINTR); 61 62 return p - (char*)ptr; 63} 64 65/* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM. 66 * Returns S, or NULL if an eof or non-interrupt error is encountered. */ 67static char *safe_fgets(char *s, int size, FILE *stream) 68{ 69 char *ret; 70 71 do { 72 clearerr(stream); 73 ret = fgets(s, size, stream); 74 } while (ret == NULL && ferror(stream) && errno == EINTR); 75 76 return ret; 77} 78 79#if ENABLE_FEATURE_WGET_AUTHENTICATION 80/* Base64-encode character string. buf is assumed to be char buf[512]. */ 81static char *base64enc_512(char buf[512], const char *str) 82{ 83 unsigned len = strlen(str); 84 if (len > 512/4*3 - 10) /* paranoia */ 85 len = 512/4*3 - 10; 86 bb_uuencode(buf, str, len, bb_uuenc_tbl_base64); 87 return buf; 88} 89#endif 90 91int wget_main(int argc, char **argv); 92int wget_main(int argc, char **argv) 93{ 94 char buf[512]; 95 struct host_info server, target; 96 len_and_sockaddr *lsa; 97 int n, status; 98 int port; 99 int try = 5; 100 unsigned opt; 101 char *str; 102 char *proxy = 0; 103 char *dir_prefix = NULL; 104#if ENABLE_FEATURE_WGET_LONG_OPTIONS 105 char *extra_headers = NULL; 106 llist_t *headers_llist = NULL; 107#endif 108 109 FILE *sfp = NULL; /* socket to web/ftp server */ 110 FILE *dfp = NULL; /* socket to ftp server (data) */ 111 char *fname_out = NULL; /* where to direct output (-O) */ 112 bool got_clen = 0; /* got content-length: from server */ 113 int output_fd = -1; 114 bool use_proxy = 1; /* Use proxies if env vars are set */ 115 const char *proxy_flag = "on"; /* Use proxies if env vars are set */ 116 const char *user_agent = "Wget";/* "User-Agent" header field */ 117 static const char keywords[] ALIGN1 = 118 "content-length\0""transfer-encoding\0""chunked\0""location\0"; 119 enum { 120 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location 121 }; 122 enum { 123 WGET_OPT_CONTINUE = 0x1, 124 WGET_OPT_SPIDER = 0x2, 125 WGET_OPT_QUIET = 0x4, 126 WGET_OPT_OUTNAME = 0x8, 127 WGET_OPT_PREFIX = 0x10, 128 WGET_OPT_PROXY = 0x20, 129 WGET_OPT_USER_AGENT = 0x40, 130 WGET_OPT_PASSIVE = 0x80, 131 WGET_OPT_HEADER = 0x100, 132 }; 133#if ENABLE_FEATURE_WGET_LONG_OPTIONS 134 static const char wget_longopts[] ALIGN1 = 135 /* name, has_arg, val */ 136 "continue\0" No_argument "c" 137 "spider\0" No_argument "s" 138 "quiet\0" No_argument "q" 139 "output-document\0" Required_argument "O" 140 "directory-prefix\0" Required_argument "P" 141 "proxy\0" Required_argument "Y" 142 "user-agent\0" Required_argument "U" 143 "passive-ftp\0" No_argument "\xff" 144 "header\0" Required_argument "\xfe" 145 ; 146 applet_long_options = wget_longopts; 147#endif 148 /* server.allocated = target.allocated = NULL; */ 149 opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::"); 150 opt = getopt32(argv, "csqO:P:Y:U:", 151 &fname_out, &dir_prefix, 152 &proxy_flag, &user_agent 153 USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist) 154 ); 155 if (strcmp(proxy_flag, "off") == 0) { 156 /* Use the proxy if necessary */ 157 use_proxy = 0; 158 } 159#if ENABLE_FEATURE_WGET_LONG_OPTIONS 160 if (headers_llist) { 161 int size = 1; 162 char *cp; 163 llist_t *ll = headers_llist; 164 while (ll) { 165 size += strlen(ll->data) + 2; 166 ll = ll->link; 167 } 168 extra_headers = cp = xmalloc(size); 169 while (headers_llist) { 170 cp += sprintf(cp, "%s\r\n", headers_llist->data); 171 headers_llist = headers_llist->link; 172 } 173 } 174#endif 175 176 parse_url(argv[optind], &target); 177 server.host = target.host; 178 server.port = target.port; 179 180 /* Use the proxy if necessary */ 181 if (use_proxy) { 182 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy"); 183 if (proxy && *proxy) { 184 parse_url(proxy, &server); 185 } else { 186 use_proxy = 0; 187 } 188 } 189 190 /* Guess an output filename */ 191 if (!fname_out) { 192 // Dirty hack. Needed because bb_get_last_path_component 193 // will destroy trailing / by storing '\0' in last byte! 194 if (!last_char_is(target.path, '/')) { 195 fname_out = bb_get_last_path_component(target.path); 196#if ENABLE_FEATURE_WGET_STATUSBAR 197 curfile = fname_out; 198#endif 199 } 200 if (!fname_out || !fname_out[0]) { 201 /* bb_get_last_path_component writes 202 * to last '/' only. We don't have one here... */ 203 fname_out = (char*)"index.html"; 204#if ENABLE_FEATURE_WGET_STATUSBAR 205 curfile = fname_out; 206#endif 207 } 208 if (dir_prefix != NULL) 209 fname_out = concat_path_file(dir_prefix, fname_out); 210#if ENABLE_FEATURE_WGET_STATUSBAR 211 } else { 212 curfile = bb_get_last_path_component(fname_out); 213#endif 214 } 215 /* Impossible? 216 if ((opt & WGET_OPT_CONTINUE) && !fname_out) 217 bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */ 218 219 /* Determine where to start transfer */ 220 if (LONE_DASH(fname_out)) { 221 output_fd = 1; 222 opt &= ~WGET_OPT_CONTINUE; 223 } 224 if (opt & WGET_OPT_CONTINUE) { 225 output_fd = open(fname_out, O_WRONLY); 226 if (output_fd >= 0) { 227 beg_range = xlseek(output_fd, 0, SEEK_END); 228 } 229 /* File doesn't exist. We do not create file here yet. 230 We are not sure it exists on remove side */ 231 } 232 233 /* We want to do exactly _one_ DNS lookup, since some 234 * sites (i.e. ftp.us.debian.org) use round-robin DNS 235 * and we want to connect to only one IP... */ 236 lsa = xhost2sockaddr(server.host, server.port); 237 if (!(opt & WGET_OPT_QUIET)) { 238 fprintf(stderr, "Connecting to %s (%s)\n", server.host, 239 xmalloc_sockaddr2dotted(&lsa->sa)); 240 /* We leak result of xmalloc_sockaddr2dotted */ 241 } 242 243 if (use_proxy || !target.is_ftp) { 244 /* 245 * HTTP session 246 */ 247 do { 248 got_clen = chunked = 0; 249 250 if (!--try) 251 bb_error_msg_and_die("too many redirections"); 252 253 /* Open socket to http server */ 254 if (sfp) fclose(sfp); 255 sfp = open_socket(lsa); 256 257 /* Send HTTP request. */ 258 if (use_proxy) { 259 fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n", 260 target.is_ftp ? "f" : "ht", target.host, 261 target.path); 262 } else { 263 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path); 264 } 265 266 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", 267 target.host, user_agent); 268 269#if ENABLE_FEATURE_WGET_AUTHENTICATION 270 if (target.user) { 271 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6, 272 base64enc_512(buf, target.user)); 273 } 274 if (use_proxy && server.user) { 275 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", 276 base64enc_512(buf, server.user)); 277 } 278#endif 279 280 if (beg_range) 281 fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range); 282#if ENABLE_FEATURE_WGET_LONG_OPTIONS 283 if (extra_headers) 284 fputs(extra_headers, sfp); 285#endif 286 fprintf(sfp, "Connection: close\r\n\r\n"); 287 288 /* 289 * Retrieve HTTP response line and check for "200" status code. 290 */ 291 read_response: 292 if (fgets(buf, sizeof(buf), sfp) == NULL) 293 bb_error_msg_and_die("no response from server"); 294 295 str = buf; 296 str = skip_non_whitespace(str); 297 str = skip_whitespace(str); 298 // xatou wouldn't work: "200 OK" 299 status = atoi(str); 300 switch (status) { 301 case 0: 302 case 100: 303 while (gethdr(buf, sizeof(buf), sfp, &n) != NULL) 304 /* eat all remaining headers */; 305 goto read_response; 306 case 200: 307 break; 308 case 300: /* redirection */ 309 case 301: 310 case 302: 311 case 303: 312 break; 313 case 206: 314 if (beg_range) 315 break; 316 /*FALLTHRU*/ 317 default: 318 /* Show first line only and kill any ESC tricks */ 319 buf[strcspn(buf, "\n\r\x1b")] = '\0'; 320 bb_error_msg_and_die("server returned error: %s", buf); 321 } 322 323 /* 324 * Retrieve HTTP headers. 325 */ 326 while ((str = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) { 327 /* gethdr did already convert the "FOO:" string to lowercase */ 328 smalluint key = index_in_strings(keywords, *&buf) + 1; 329 if (key == KEY_content_length) { 330 content_len = BB_STRTOOFF(str, NULL, 10); 331 if (errno || content_len < 0) { 332 bb_error_msg_and_die("content-length %s is garbage", str); 333 } 334 got_clen = 1; 335 continue; 336 } 337 if (key == KEY_transfer_encoding) { 338 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked) 339 bb_error_msg_and_die("server wants to do %s transfer encoding", str); 340 chunked = got_clen = 1; 341 } 342 if (key == KEY_location) { 343 if (str[0] == '/') 344 /* free(target.allocated); */ 345 target.path = /* target.allocated = */ xstrdup(str+1); 346 else { 347 parse_url(str, &target); 348 if (use_proxy == 0) { 349 server.host = target.host; 350 server.port = target.port; 351 } 352 free(lsa); 353 lsa = xhost2sockaddr(server.host, server.port); 354 break; 355 } 356 } 357 } 358 } while (status >= 300); 359 360 dfp = sfp; 361 362 } else { 363 364 /* 365 * FTP session 366 */ 367 if (!target.user) 368 target.user = xstrdup("anonymous:busybox@"); 369 370 sfp = open_socket(lsa); 371 if (ftpcmd(NULL, NULL, sfp, buf) != 220) 372 bb_error_msg_and_die("%s", buf+4); 373 374 /* 375 * Splitting username:password pair, 376 * trying to log in 377 */ 378 str = strchr(target.user, ':'); 379 if (str) 380 *(str++) = '\0'; 381 switch (ftpcmd("USER ", target.user, sfp, buf)) { 382 case 230: 383 break; 384 case 331: 385 if (ftpcmd("PASS ", str, sfp, buf) == 230) 386 break; 387 /* FALLTHRU (failed login) */ 388 default: 389 bb_error_msg_and_die("ftp login: %s", buf+4); 390 } 391 392 ftpcmd("TYPE I", NULL, sfp, buf); 393 394 /* 395 * Querying file size 396 */ 397 if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) { 398 content_len = BB_STRTOOFF(buf+4, NULL, 10); 399 if (errno || content_len < 0) { 400 bb_error_msg_and_die("SIZE value is garbage"); 401 } 402 got_clen = 1; 403 } 404 405 /* 406 * Entering passive mode 407 */ 408 if (ftpcmd("PASV", NULL, sfp, buf) != 227) { 409 pasv_error: 410 bb_error_msg_and_die("bad response to %s: %s", "PASV", buf); 411 } 412 // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage] 413 // Server's IP is N1.N2.N3.N4 (we ignore it) 414 // Server's port for data connection is P1*256+P2 415 str = strrchr(buf, ')'); 416 if (str) str[0] = '\0'; 417 str = strrchr(buf, ','); 418 if (!str) goto pasv_error; 419 port = xatou_range(str+1, 0, 255); 420 *str = '\0'; 421 str = strrchr(buf, ','); 422 if (!str) goto pasv_error; 423 port += xatou_range(str+1, 0, 255) * 256; 424 set_nport(lsa, htons(port)); 425 dfp = open_socket(lsa); 426 427 if (beg_range) { 428 sprintf(buf, "REST %"OFF_FMT"d", beg_range); 429 if (ftpcmd(buf, NULL, sfp, buf) == 350) 430 content_len -= beg_range; 431 } 432 433 if (ftpcmd("RETR ", target.path, sfp, buf) > 150) 434 bb_error_msg_and_die("bad response to RETR: %s", buf); 435 } 436 if (opt & WGET_OPT_SPIDER) { 437 if (ENABLE_FEATURE_CLEAN_UP) 438 fclose(sfp); 439 goto done; 440 } 441 442 /* 443 * Retrieve file 444 */ 445 if (chunked) { 446 fgets(buf, sizeof(buf), dfp); 447 content_len = STRTOOFF(buf, NULL, 16); 448 } 449 450 /* Do it before progressmeter (want to have nice error message) */ 451 if (output_fd < 0) 452 output_fd = xopen(fname_out, 453 O_WRONLY|O_CREAT|O_EXCL|O_TRUNC); 454 455 if (!(opt & WGET_OPT_QUIET)) 456 progressmeter(-1); 457 458 do { 459 while (content_len > 0 || !got_clen) { 460 unsigned rdsz = sizeof(buf); 461 if (content_len < sizeof(buf) && (chunked || got_clen)) 462 rdsz = (unsigned)content_len; 463 n = safe_fread(buf, rdsz, dfp); 464 if (n <= 0) 465 break; 466 if (full_write(output_fd, buf, n) != n) { 467 bb_perror_msg_and_die(bb_msg_write_error); 468 } 469#if ENABLE_FEATURE_WGET_STATUSBAR 470 transferred += n; 471#endif 472 if (got_clen) { 473 content_len -= n; 474 } 475 } 476 477 if (chunked) { 478 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ 479 safe_fgets(buf, sizeof(buf), dfp); 480 content_len = STRTOOFF(buf, NULL, 16); 481 if (content_len == 0) { 482 chunked = 0; /* all done! */ 483 } 484 } 485 486 if (n == 0 && ferror(dfp)) { 487 bb_perror_msg_and_die(bb_msg_read_error); 488 } 489 } while (chunked); 490 491 if (!(opt & WGET_OPT_QUIET)) 492 progressmeter(1); 493 494 if ((use_proxy == 0) && target.is_ftp) { 495 fclose(dfp); 496 if (ftpcmd(NULL, NULL, sfp, buf) != 226) 497 bb_error_msg_and_die("ftp error: %s", buf+4); 498 ftpcmd("QUIT", NULL, sfp, buf); 499 } 500done: 501 exit(EXIT_SUCCESS); 502} 503 504 505static void parse_url(char *src_url, struct host_info *h) 506{ 507 char *url, *p, *sp; 508 509 /* h->allocated = */ url = xstrdup(src_url); 510 511 if (strncmp(url, "http://", 7) == 0) { 512 h->port = bb_lookup_port("http", "tcp", 80); 513 h->host = url + 7; 514 h->is_ftp = 0; 515 } else if (strncmp(url, "ftp://", 6) == 0) { 516 h->port = bb_lookup_port("ftp", "tcp", 21); 517 h->host = url + 6; 518 h->is_ftp = 1; 519 } else 520 bb_error_msg_and_die("not an http or ftp url: %s", url); 521 522 // FYI: 523 // "Real" wget 'http://busybox.net?var=a/b' sends this request: 524 // 'GET /?var=a/b HTTP 1.0' 525 // and saves 'index.html?var=a%2Fb' (we save 'b') 526 // wget 'http://busybox.net?login=john@doe': 527 // request: 'GET /?login=john@doe HTTP/1.0' 528 // saves: 'index.html?login=john@doe' (we save '?login=john@doe') 529 // wget 'http://busybox.net#test/test': 530 // request: 'GET / HTTP/1.0' 531 // saves: 'index.html' (we save 'test') 532 // 533 // We also don't add unique .N suffix if file exists... 534 sp = strchr(h->host, '/'); 535 p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p; 536 p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p; 537 if (!sp) { 538 /* must be writable because of bb_get_last_path_component() */ 539 static char nullstr[] ALIGN1 = ""; 540 h->path = nullstr; 541 } else if (*sp == '/') { 542 *sp = '\0'; 543 h->path = sp + 1; 544 } else { // '#' or '?' 545 // http://busybox.net?login=john@doe is a valid URL 546 // memmove converts to: 547 // http:/busybox.nett?login=john@doe... 548 memmove(h->host-1, h->host, sp - h->host); 549 h->host--; 550 sp[-1] = '\0'; 551 h->path = sp; 552 } 553 554 sp = strrchr(h->host, '@'); 555 h->user = NULL; 556 if (sp != NULL) { 557 h->user = h->host; 558 *sp = '\0'; 559 h->host = sp + 1; 560 } 561 562 sp = h->host; 563} 564 565 566static FILE *open_socket(len_and_sockaddr *lsa) 567{ 568 FILE *fp; 569 570 /* glibc 2.4 seems to try seeking on it - ??! */ 571 /* hopefully it understands what ESPIPE means... */ 572 fp = fdopen(xconnect_stream(lsa), "r+"); 573 if (fp == NULL) 574 bb_perror_msg_and_die("fdopen"); 575 576 return fp; 577} 578 579 580static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc) 581{ 582 char *s, *hdrval; 583 int c; 584 585 *istrunc = 0; 586 587 /* retrieve header line */ 588 if (fgets(buf, bufsiz, fp) == NULL) 589 return NULL; 590 591 /* see if we are at the end of the headers */ 592 for (s = buf; *s == '\r'; ++s) 593 ; 594 if (s[0] == '\n') 595 return NULL; 596 597 /* convert the header name to lower case */ 598 for (s = buf; isalnum(*s) || *s == '-'; ++s) 599 *s = tolower(*s); 600 601 /* verify we are at the end of the header name */ 602 if (*s != ':') 603 bb_error_msg_and_die("bad header line: %s", buf); 604 605 /* locate the start of the header value */ 606 for (*s++ = '\0'; *s == ' ' || *s == '\t'; ++s) 607 ; 608 hdrval = s; 609 610 /* locate the end of header */ 611 while (*s != '\0' && *s != '\r' && *s != '\n') 612 ++s; 613 614 /* end of header found */ 615 if (*s != '\0') { 616 *s = '\0'; 617 return hdrval; 618 } 619 620 /* Rats! The buffer isn't big enough to hold the entire header value. */ 621 while (c = getc(fp), c != EOF && c != '\n') 622 ; 623 *istrunc = 1; 624 return hdrval; 625} 626 627static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf) 628{ 629 int result; 630 if (s1) { 631 if (!s2) s2 = ""; 632 fprintf(fp, "%s%s\r\n", s1, s2); 633 fflush(fp); 634 } 635 636 do { 637 char *buf_ptr; 638 639 if (fgets(buf, 510, fp) == NULL) { 640 bb_perror_msg_and_die("error getting response"); 641 } 642 buf_ptr = strstr(buf, "\r\n"); 643 if (buf_ptr) { 644 *buf_ptr = '\0'; 645 } 646 } while (!isdigit(buf[0]) || buf[3] != ' '); 647 648 buf[3] = '\0'; 649 result = xatoi_u(buf); 650 buf[3] = ' '; 651 return result; 652} 653 654#if ENABLE_FEATURE_WGET_STATUSBAR 655/* Stuff below is from BSD rcp util.c, as added to openshh. 656 * Original copyright notice is retained at the end of this file. 657 */ 658static int 659getttywidth(void) 660{ 661 int width; 662 get_terminal_width_height(0, &width, NULL); 663 return width; 664} 665 666static void 667updateprogressmeter(int ignore) 668{ 669 int save_errno = errno; 670 671 progressmeter(0); 672 errno = save_errno; 673} 674 675static void alarmtimer(int iwait) 676{ 677 struct itimerval itv; 678 679 itv.it_value.tv_sec = iwait; 680 itv.it_value.tv_usec = 0; 681 itv.it_interval = itv.it_value; 682 setitimer(ITIMER_REAL, &itv, NULL); 683} 684 685static void 686progressmeter(int flag) 687{ 688 static unsigned lastupdate_sec; 689 static unsigned start_sec; 690 static off_t lastsize, totalsize; 691 692 off_t abbrevsize; 693 unsigned since_last_update, elapsed; 694 unsigned ratio; 695 int barlength, i; 696 697 if (flag == -1) { /* first call to progressmeter */ 698 start_sec = monotonic_sec(); 699 lastupdate_sec = start_sec; 700 lastsize = 0; 701 totalsize = content_len + beg_range; /* as content_len changes.. */ 702 } 703 704 ratio = 100; 705 if (totalsize != 0 && !chunked) { 706 /* long long helps to have it working even if !LFS */ 707 ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize); 708 if (ratio > 100) ratio = 100; 709 } 710 711 fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); 712 713 barlength = getttywidth() - 49; 714 if (barlength > 0) { 715 /* god bless gcc for variable arrays :) */ 716 i = barlength * ratio / 100; 717 { 718 char buf[i+1]; 719 memset(buf, '*', i); 720 buf[i] = '\0'; 721 fprintf(stderr, "|%s%*s|", buf, barlength - i, ""); 722 } 723 } 724 i = 0; 725 abbrevsize = transferred + beg_range; 726 while (abbrevsize >= 100000) { 727 i++; 728 abbrevsize >>= 10; 729 } 730 /* see http://en.wikipedia.org/wiki/Tera */ 731 fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]); 732 733// Nuts! Ain't it easier to update progress meter ONLY when we transferred++? 734 735 elapsed = monotonic_sec(); 736 since_last_update = elapsed - lastupdate_sec; 737 if (transferred > lastsize) { 738 lastupdate_sec = elapsed; 739 lastsize = transferred; 740 if (since_last_update >= STALLTIME) { 741 /* We "cut off" these seconds from elapsed time 742 * by adjusting start time */ 743 start_sec += since_last_update; 744 } 745 since_last_update = 0; /* we are un-stalled now */ 746 } 747 elapsed -= start_sec; /* now it's "elapsed since start" */ 748 749 if (since_last_update >= STALLTIME) { 750 fprintf(stderr, " - stalled -"); 751 } else { 752 off_t to_download = totalsize - beg_range; 753 if (transferred <= 0 || (int)elapsed <= 0 || transferred > to_download || chunked) { 754 fprintf(stderr, "--:--:-- ETA"); 755 } else { 756 /* to_download / (transferred/elapsed) - elapsed: */ 757 int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed); 758 /* (long long helps to have working ETA even if !LFS) */ 759 i = eta % 3600; 760 fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60); 761 } 762 } 763 764 if (flag == -1) { /* first call to progressmeter */ 765 struct sigaction sa; 766 sa.sa_handler = updateprogressmeter; 767 sigemptyset(&sa.sa_mask); 768 sa.sa_flags = SA_RESTART; 769 sigaction(SIGALRM, &sa, NULL); 770 alarmtimer(1); 771 } else if (flag == 1) { /* last call to progressmeter */ 772 alarmtimer(0); 773 transferred = 0; 774 putc('\n', stderr); 775 } 776} 777#endif /* FEATURE_WGET_STATUSBAR */ 778 779/* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, 780 * much of which was blatantly stolen from openssh. */ 781 782/*- 783 * Copyright (c) 1992, 1993 784 * The Regents of the University of California. All rights reserved. 785 * 786 * Redistribution and use in source and binary forms, with or without 787 * modification, are permitted provided that the following conditions 788 * are met: 789 * 1. Redistributions of source code must retain the above copyright 790 * notice, this list of conditions and the following disclaimer. 791 * 2. Redistributions in binary form must reproduce the above copyright 792 * notice, this list of conditions and the following disclaimer in the 793 * documentation and/or other materials provided with the distribution. 794 * 795 * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change 796 * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> 797 * 798 * 4. Neither the name of the University nor the names of its contributors 799 * may be used to endorse or promote products derived from this software 800 * without specific prior written permission. 801 * 802 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 803 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 804 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 805 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 806 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 807 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 808 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 809 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 810 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 811 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 812 * SUCH DAMAGE. 813 * 814 */ 815