Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/networking/wget.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 532 - (hide annotations) (download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 22131 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd

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