- patch from Yann E. Morin: makes modprobe understand shell patterns
[oweals/busybox.git] / networking / wget.c
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 #include <stdio.h>
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <strings.h>
16 #include <unistd.h>
17 #include <signal.h>
18 #include <sys/ioctl.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25 #include <netdb.h>
26
27 #include <getopt.h>
28
29 #include "busybox.h"
30
31 struct host_info {
32         char *host;
33         int port;
34         char *path;
35         int is_ftp;
36         char *user;
37 };
38
39 static void parse_url(char *url, struct host_info *h);
40 static FILE *open_socket(struct sockaddr_in *s_in);
41 static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc);
42 static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf);
43
44 /* Globals (can be accessed from signal handlers */
45 static off_t filesize = 0;              /* content-length of the file */
46 static int chunked = 0;                 /* chunked transfer encoding */
47 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
48 static void progressmeter(int flag);
49 static char *curfile;                   /* Name of current file being transferred. */
50 static struct timeval start;    /* Time a transfer started. */
51 static volatile unsigned long statbytes = 0; /* Number of bytes transferred so far. */
52 /* For progressmeter() -- number of seconds before xfer considered "stalled" */
53 static const int STALLTIME = 5;
54 #endif
55
56 static void close_and_delete_outfile(FILE* output, char *fname_out, int do_continue)
57 {
58         if (output != stdout && do_continue==0) {
59                 fclose(output);
60                 unlink(fname_out);
61         }
62 }
63
64 /* Read NMEMB elements of SIZE bytes into PTR from STREAM.  Returns the
65  * number of elements read, and a short count if an eof or non-interrupt
66  * error is encountered.  */
67 static size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
68 {
69         size_t ret = 0;
70
71         do {
72                 clearerr(stream);
73                 ret += fread((char *)ptr + (ret * size), size, nmemb - ret, stream);
74         } while (ret < nmemb && ferror(stream) && errno == EINTR);
75
76         return ret;
77 }
78
79 /* Write NMEMB elements of SIZE bytes from PTR to STREAM.  Returns the
80  * number of elements written, and a short count if an eof or non-interrupt
81  * error is encountered.  */
82 static size_t safe_fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream)
83 {
84         size_t ret = 0;
85
86         do {
87                 clearerr(stream);
88                 ret += fwrite((char *)ptr + (ret * size), size, nmemb - ret, stream);
89         } while (ret < nmemb && ferror(stream) && errno == EINTR);
90
91         return ret;
92 }
93
94 /* Read a line or SIZE - 1 bytes into S, whichever is less, from STREAM.
95  * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
96 static char *safe_fgets(char *s, int size, FILE *stream)
97 {
98         char *ret;
99
100         do {
101                 clearerr(stream);
102                 ret = fgets(s, size, stream);
103         } while (ret == NULL && ferror(stream) && errno == EINTR);
104
105         return ret;
106 }
107
108 #define close_delete_and_die(s...) { \
109         close_and_delete_outfile(output, fname_out, do_continue); \
110         bb_error_msg_and_die(s); }
111
112
113 #ifdef CONFIG_FEATURE_WGET_AUTHENTICATION
114 /*
115  *  Base64-encode character string
116  *  oops... isn't something similar in uuencode.c?
117  *  XXX: It would be better to use already existing code
118  */
119 static char *base64enc(unsigned char *p, char *buf, int len) {
120
121         char al[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
122                     "0123456789+/";
123                 char *s = buf;
124
125         while(*p) {
126                                 if (s >= buf+len-4)
127                                         bb_error_msg_and_die("buffer overflow");
128                 *(s++) = al[(*p >> 2) & 0x3F];
129                 *(s++) = al[((*p << 4) & 0x30) | ((*(p+1) >> 4) & 0x0F)];
130                 *s = *(s+1) = '=';
131                 *(s+2) = 0;
132                 if (! *(++p)) break;
133                 *(s++) = al[((*p << 2) & 0x3C) | ((*(p+1) >> 6) & 0x03)];
134                 if (! *(++p)) break;
135                 *(s++) = al[*(p++) & 0x3F];
136         }
137
138                 return buf;
139 }
140 #endif
141
142 #define WGET_OPT_CONTINUE       1
143 #define WGET_OPT_QUIET  2
144 #define WGET_OPT_PASSIVE        4
145 #define WGET_OPT_OUTNAME        8
146 #define WGET_OPT_HEADER 16
147 #define WGET_OPT_PREFIX 32
148 #define WGET_OPT_PROXY  64
149
150 #if ENABLE_WGET_LONG_OPTIONS
151 static const struct option wget_long_options[] = {
152         { "continue",        0, NULL, 'c' },
153         { "quiet",           0, NULL, 'q' },
154         { "passive-ftp",     0, NULL, 139 },
155         { "output-document", 1, NULL, 'O' },
156         { "header",              1, NULL, 131 },
157         { "directory-prefix",1, NULL, 'P' },
158         { "proxy",           1, NULL, 'Y' },
159         { 0,                 0, 0, 0 }
160 };
161 #endif
162
163 int wget_main(int argc, char **argv)
164 {
165         int n, try=5, status;
166         unsigned long opt;
167         int port;
168         char *proxy = 0;
169         char *dir_prefix=NULL;
170         char *s, buf[512];
171         struct stat sbuf;
172         char extra_headers[1024];
173         char *extra_headers_ptr = extra_headers;
174         int extra_headers_left = sizeof(extra_headers);
175         struct host_info server, target;
176         struct sockaddr_in s_in;
177         llist_t *headers_llist = NULL;
178
179         FILE *sfp = NULL;               /* socket to web/ftp server         */
180         FILE *dfp = NULL;               /* socket to ftp server (data)      */
181         char *fname_out = NULL;         /* where to direct output (-O)      */
182         int do_continue = 0;            /* continue a prev transfer (-c)    */
183         long beg_range = 0L;            /*   range at which continue begins */
184         int got_clen = 0;               /* got content-length: from server  */
185         FILE *output;                   /* socket to web server             */
186         int quiet_flag = FALSE;         /* Be verry, verry quiet...         */
187         int use_proxy = 1;              /* Use proxies if env vars are set  */
188         char *proxy_flag = "on";        /* Use proxies if env vars are set  */
189
190         /*
191          * Crack command line.
192          */
193         bb_opt_complementally = "-1:\203::";
194 #if ENABLE_WGET_LONG_OPTIONS
195         bb_applet_long_options = wget_long_options;
196 #endif
197         opt = bb_getopt_ulflags(argc, argv, "cq\213O:\203:P:Y:",
198                                         &fname_out, &headers_llist,
199                                         &dir_prefix, &proxy_flag);
200         if (opt & WGET_OPT_CONTINUE) {
201                 ++do_continue;
202         }
203         if (opt & WGET_OPT_QUIET) {
204                 quiet_flag = TRUE;
205         }
206         if (strcmp(proxy_flag, "off") == 0) {
207                 /* Use the proxy if necessary. */
208                 use_proxy = 0;
209         }
210         if (opt & WGET_OPT_HEADER) {
211                 while (headers_llist) {
212                         int arglen = strlen(headers_llist->data);
213                         if (extra_headers_left - arglen - 2 <= 0)
214                                 bb_error_msg_and_die("extra_headers buffer too small(need %i)", extra_headers_left - arglen);
215                         strcpy(extra_headers_ptr, headers_llist->data);
216                         extra_headers_ptr += arglen;
217                         extra_headers_left -= ( arglen + 2 );
218                         *extra_headers_ptr++ = '\r';
219                         *extra_headers_ptr++ = '\n';
220                         *(extra_headers_ptr + 1) = 0;
221                         headers_llist = headers_llist->link;
222                 }
223         }
224
225         parse_url(argv[optind], &target);
226         server.host = target.host;
227         server.port = target.port;
228
229         /*
230          * Use the proxy if necessary.
231          */
232         if (use_proxy) {
233                 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
234                 if (proxy && *proxy) {
235                         parse_url(bb_xstrdup(proxy), &server);
236                 } else {
237                         use_proxy = 0;
238                 }
239         }
240
241         /* Guess an output filename */
242         if (!fname_out) {
243                 // Dirty hack. Needed because bb_get_last_path_component
244                 // will destroy trailing / by storing '\0' in last byte!
245                 if(*target.path && target.path[strlen(target.path)-1]!='/') {
246                         fname_out =
247 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
248                                 curfile =
249 #endif
250                                 bb_get_last_path_component(target.path);
251                 }
252                 if (fname_out==NULL || strlen(fname_out)<1) {
253                         fname_out =
254 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
255                                 curfile =
256 #endif
257                                 "index.html";
258                 }
259                 if (dir_prefix != NULL)
260                         fname_out = concat_path_file(dir_prefix, fname_out);
261 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
262         } else {
263                 curfile = bb_get_last_path_component(fname_out);
264 #endif
265         }
266         if (do_continue && !fname_out)
267                 bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)");
268
269
270         /*
271          * Open the output file stream.
272          */
273         if (strcmp(fname_out, "-") == 0) {
274                 output = stdout;
275                 quiet_flag = TRUE;
276         } else {
277                 output = bb_xfopen(fname_out, (do_continue ? "a" : "w"));
278         }
279
280         /*
281          * Determine where to start transfer.
282          */
283         if (do_continue) {
284                 if (fstat(fileno(output), &sbuf) < 0)
285                         bb_perror_msg_and_die("fstat()");
286                 if (sbuf.st_size > 0)
287                         beg_range = sbuf.st_size;
288                 else
289                         do_continue = 0;
290         }
291
292         /* We want to do exactly _one_ DNS lookup, since some
293          * sites (i.e. ftp.us.debian.org) use round-robin DNS
294          * and we want to connect to only one IP... */
295         bb_lookup_host(&s_in, server.host);
296         s_in.sin_port = server.port;
297         if (quiet_flag==FALSE) {
298                 fprintf(stdout, "Connecting to %s[%s]:%d\n",
299                                 server.host, inet_ntoa(s_in.sin_addr), ntohs(server.port));
300         }
301
302         if (use_proxy || !target.is_ftp) {
303                 /*
304                  *  HTTP session
305                  */
306                 do {
307                         got_clen = chunked = 0;
308
309                         if (! --try)
310                                 close_delete_and_die("too many redirections");
311
312                         /*
313                          * Open socket to http server
314                          */
315                         if (sfp) fclose(sfp);
316                         sfp = open_socket(&s_in);
317
318                         /*
319                          * Send HTTP request.
320                          */
321                         if (use_proxy) {
322                                 const char *format = "GET %stp://%s:%d/%s HTTP/1.1\r\n";
323 #ifdef CONFIG_FEATURE_WGET_IP6_LITERAL
324                                 if (strchr (target.host, ':'))
325                                         format = "GET %stp://[%s]:%d/%s HTTP/1.1\r\n";
326 #endif
327                                 fprintf(sfp, format,
328                                         target.is_ftp ? "f" : "ht", target.host,
329                                         ntohs(target.port), target.path);
330                         } else {
331                                 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
332                         }
333
334                         fprintf(sfp, "Host: %s\r\nUser-Agent: Wget\r\n", target.host);
335
336 #ifdef CONFIG_FEATURE_WGET_AUTHENTICATION
337                         if (target.user) {
338                                 fprintf(sfp, "Authorization: Basic %s\r\n",
339                                         base64enc((unsigned char*)target.user, buf, sizeof(buf)));
340                         }
341                         if (use_proxy && server.user) {
342                                 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
343                                         base64enc((unsigned char*)server.user, buf, sizeof(buf)));
344                         }
345 #endif
346
347                         if (do_continue)
348                                 fprintf(sfp, "Range: bytes=%ld-\r\n", beg_range);
349                         if(extra_headers_left < sizeof(extra_headers))
350                                 fputs(extra_headers,sfp);
351                         fprintf(sfp,"Connection: close\r\n\r\n");
352
353                         /*
354                         * Retrieve HTTP response line and check for "200" status code.
355                         */
356 read_response:
357                         if (fgets(buf, sizeof(buf), sfp) == NULL)
358                                 close_delete_and_die("no response from server");
359
360                         for (s = buf ; *s != '\0' && !isspace(*s) ; ++s)
361                         ;
362                         for ( ; isspace(*s) ; ++s)
363                         ;
364                         switch (status = atoi(s)) {
365                                 case 0:
366                                 case 100:
367                                         while (gethdr(buf, sizeof(buf), sfp, &n) != NULL);
368                                         goto read_response;
369                                 case 200:
370                                         if (do_continue && output != stdout)
371                                                 output = freopen(fname_out, "w", output);
372                                         do_continue = 0;
373                                         break;
374                                 case 300:       /* redirection */
375                                 case 301:
376                                 case 302:
377                                 case 303:
378                                         break;
379                                 case 206:
380                                         if (do_continue)
381                                                 break;
382                                         /*FALLTHRU*/
383                                 default:
384                                         chomp(buf);
385                                         close_delete_and_die("server returned error %d: %s", atoi(s), buf);
386                         }
387
388                         /*
389                          * Retrieve HTTP headers.
390                          */
391                         while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) {
392                                 if (strcasecmp(buf, "content-length") == 0) {
393                                         unsigned long value;
394                                         if (safe_strtoul(s, &value)) {
395                                                 close_delete_and_die("content-length %s is garbage", s);
396                                         }
397                                         filesize = value;
398                                         got_clen = 1;
399                                         continue;
400                                 }
401                                 if (strcasecmp(buf, "transfer-encoding") == 0) {
402                                         if (strcasecmp(s, "chunked") == 0) {
403                                                 chunked = got_clen = 1;
404                                         } else {
405                                         close_delete_and_die("server wants to do %s transfer encoding", s);
406                                         }
407                                 }
408                                 if (strcasecmp(buf, "location") == 0) {
409                                         if (s[0] == '/')
410                                                 target.path = bb_xstrdup(s+1);
411                                         else {
412                                                 parse_url(bb_xstrdup(s), &target);
413                                                 if (use_proxy == 0) {
414                                                         server.host = target.host;
415                                                         server.port = target.port;
416                                                 }
417                                                 bb_lookup_host(&s_in, server.host);
418                                                 s_in.sin_port = server.port;
419                                                 break;
420                                         }
421                                 }
422                         }
423                 } while(status >= 300);
424
425                 dfp = sfp;
426         }
427         else
428         {
429                 /*
430                  *  FTP session
431                  */
432                 if (! target.user)
433                         target.user = bb_xstrdup("anonymous:busybox@");
434
435                 sfp = open_socket(&s_in);
436                 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
437                         close_delete_and_die("%s", buf+4);
438
439                 /*
440                  * Splitting username:password pair,
441                  * trying to log in
442                  */
443                 s = strchr(target.user, ':');
444                 if (s)
445                         *(s++) = '\0';
446                 switch(ftpcmd("USER ", target.user, sfp, buf)) {
447                         case 230:
448                                 break;
449                         case 331:
450                                 if (ftpcmd("PASS ", s, sfp, buf) == 230)
451                                         break;
452                                 /* FALLTHRU (failed login) */
453                         default:
454                                 close_delete_and_die("ftp login: %s", buf+4);
455                 }
456
457                 ftpcmd("CDUP", NULL, sfp, buf);
458                 ftpcmd("TYPE I", NULL, sfp, buf);
459
460                 /*
461                  * Querying file size
462                  */
463                 if (ftpcmd("SIZE /", target.path, sfp, buf) == 213) {
464                         unsigned long value;
465                         if (safe_strtoul(buf+4, &value)) {
466                                 close_delete_and_die("SIZE value is garbage");
467                         }
468                         filesize = value;
469                         got_clen = 1;
470                 }
471
472                 /*
473                  * Entering passive mode
474                  */
475                 if (ftpcmd("PASV", NULL, sfp, buf) !=  227)
476                         close_delete_and_die("PASV: %s", buf+4);
477                 s = strrchr(buf, ',');
478                 *s = 0;
479                 port = atoi(s+1);
480                 s = strrchr(buf, ',');
481                 port += atoi(s+1) * 256;
482                 s_in.sin_port = htons(port);
483                 dfp = open_socket(&s_in);
484
485                 if (do_continue) {
486                         sprintf(buf, "REST %ld", beg_range);
487                         if (ftpcmd(buf, NULL, sfp, buf) != 350) {
488                                 if (output != stdout)
489                                         output = freopen(fname_out, "w", output);
490                                 do_continue = 0;
491                         } else
492                                 filesize -= beg_range;
493                 }
494
495                 if (ftpcmd("RETR /", target.path, sfp, buf) > 150)
496                         close_delete_and_die("RETR: %s", buf+4);
497
498         }
499
500
501         /*
502          * Retrieve file
503          */
504         if (chunked) {
505                 fgets(buf, sizeof(buf), dfp);
506                 filesize = strtol(buf, (char **) NULL, 16);
507         }
508 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
509         if (quiet_flag==FALSE)
510                 progressmeter(-1);
511 #endif
512         do {
513                 while ((filesize > 0 || !got_clen) && (n = safe_fread(buf, 1, ((chunked || got_clen) && (filesize < sizeof(buf)) ? filesize : sizeof(buf)), dfp)) > 0) {
514                         if (safe_fwrite(buf, 1, n, output) != n) {
515                                 bb_perror_msg_and_die("write error");
516                         }
517 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
518                         statbytes+=n;
519 #endif
520                         if (got_clen) {
521                                 filesize -= n;
522                         }
523                 }
524
525                 if (chunked) {
526                         safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
527                         safe_fgets(buf, sizeof(buf), dfp);
528                         filesize = strtol(buf, (char **) NULL, 16);
529                         if (filesize==0) {
530                                 chunked = 0; /* all done! */
531                         }
532                 }
533
534                 if (n == 0 && ferror(dfp)) {
535                         bb_perror_msg_and_die("network read error");
536                 }
537         } while (chunked);
538 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
539         if (quiet_flag==FALSE)
540                 progressmeter(1);
541 #endif
542         if ((use_proxy == 0) && target.is_ftp) {
543                 fclose(dfp);
544                 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
545                         bb_error_msg_and_die("ftp error: %s", buf+4);
546                 ftpcmd("QUIT", NULL, sfp, buf);
547         }
548         exit(EXIT_SUCCESS);
549 }
550
551
552 void parse_url(char *url, struct host_info *h)
553 {
554         char *cp, *sp, *up, *pp;
555
556         if (strncmp(url, "http://", 7) == 0) {
557                 h->port = bb_lookup_port("http", "tcp", 80);
558                 h->host = url + 7;
559                 h->is_ftp = 0;
560         } else if (strncmp(url, "ftp://", 6) == 0) {
561                 h->port = bb_lookup_port("ftp", "tfp", 21);
562                 h->host = url + 6;
563                 h->is_ftp = 1;
564         } else
565                 bb_error_msg_and_die("not an http or ftp url: %s", url);
566
567         sp = strchr(h->host, '/');
568         if (sp) {
569                 *sp++ = '\0';
570                 h->path = sp;
571         } else
572                 h->path = bb_xstrdup("");
573
574         up = strrchr(h->host, '@');
575         if (up != NULL) {
576                 h->user = h->host;
577                 *up++ = '\0';
578                 h->host = up;
579         } else
580                 h->user = NULL;
581
582         pp = h->host;
583
584 #ifdef CONFIG_FEATURE_WGET_IP6_LITERAL
585         if (h->host[0] == '[') {
586                 char *ep;
587
588                 ep = h->host + 1;
589                 while (*ep == ':' || isxdigit (*ep))
590                         ep++;
591                 if (*ep == ']') {
592                         h->host++;
593                         *ep = '\0';
594                         pp = ep + 1;
595                 }
596         }
597 #endif
598
599         cp = strchr(pp, ':');
600         if (cp != NULL) {
601                 *cp++ = '\0';
602                 h->port = htons(atoi(cp));
603         }
604 }
605
606
607 FILE *open_socket(struct sockaddr_in *s_in)
608 {
609         FILE *fp;
610
611         fp = fdopen(xconnect(s_in), "r+");
612         if (fp == NULL)
613                 bb_perror_msg_and_die("fdopen()");
614
615         return fp;
616 }
617
618
619 char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc)
620 {
621         char *s, *hdrval;
622         int c;
623
624         *istrunc = 0;
625
626         /* retrieve header line */
627         if (fgets(buf, bufsiz, fp) == NULL)
628                 return NULL;
629
630         /* see if we are at the end of the headers */
631         for (s = buf ; *s == '\r' ; ++s)
632                 ;
633         if (s[0] == '\n')
634                 return NULL;
635
636         /* convert the header name to lower case */
637         for (s = buf ; isalnum(*s) || *s == '-' ; ++s)
638                 *s = tolower(*s);
639
640         /* verify we are at the end of the header name */
641         if (*s != ':')
642                 bb_error_msg_and_die("bad header line: %s", buf);
643
644         /* locate the start of the header value */
645         for (*s++ = '\0' ; *s == ' ' || *s == '\t' ; ++s)
646                 ;
647         hdrval = s;
648
649         /* locate the end of header */
650         while (*s != '\0' && *s != '\r' && *s != '\n')
651                 ++s;
652
653         /* end of header found */
654         if (*s != '\0') {
655                 *s = '\0';
656                 return hdrval;
657         }
658
659         /* Rats!  The buffer isn't big enough to hold the entire header value. */
660         while (c = getc(fp), c != EOF && c != '\n')
661                 ;
662         *istrunc = 1;
663         return hdrval;
664 }
665
666 static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf)
667 {
668         if (s1) {
669                 if (!s2) s2="";
670                 fprintf(fp, "%s%s\r\n", s1, s2);
671                 fflush(fp);
672         }
673
674         do {
675                 char *buf_ptr;
676
677                 if (fgets(buf, 510, fp) == NULL) {
678                         bb_perror_msg_and_die("fgets()");
679                 }
680                 buf_ptr = strstr(buf, "\r\n");
681                 if (buf_ptr) {
682                         *buf_ptr = '\0';
683                 }
684         } while (! isdigit(buf[0]) || buf[3] != ' ');
685
686         return atoi(buf);
687 }
688
689 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
690 /* Stuff below is from BSD rcp util.c, as added to openshh.
691  * Original copyright notice is retained at the end of this file.
692  *
693  */
694
695
696 static int
697 getttywidth(void)
698 {
699         int width=0;
700         get_terminal_width_height(0, &width, NULL);
701         return (width);
702 }
703
704 static void
705 updateprogressmeter(int ignore)
706 {
707         int save_errno = errno;
708
709         progressmeter(0);
710         errno = save_errno;
711 }
712
713 static void
714 alarmtimer(int wait)
715 {
716         struct itimerval itv;
717
718         itv.it_value.tv_sec = wait;
719         itv.it_value.tv_usec = 0;
720         itv.it_interval = itv.it_value;
721         setitimer(ITIMER_REAL, &itv, NULL);
722 }
723
724
725 static void
726 progressmeter(int flag)
727 {
728         static const char prefixes[] = " KMGTP";
729         static struct timeval lastupdate;
730         static off_t lastsize, totalsize;
731         struct timeval now, td, wait;
732         off_t cursize, abbrevsize;
733         double elapsed;
734         int ratio, barlength, i, remaining;
735         char buf[256];
736
737         if (flag == -1) {
738                 (void) gettimeofday(&start, (struct timezone *) 0);
739                 lastupdate = start;
740                 lastsize = 0;
741                 totalsize = filesize; /* as filesize changes.. */
742         }
743
744         (void) gettimeofday(&now, (struct timezone *) 0);
745         cursize = statbytes;
746         if (totalsize != 0 && !chunked) {
747                 ratio = 100.0 * cursize / totalsize;
748                 ratio = MAX(ratio, 0);
749                 ratio = MIN(ratio, 100);
750         } else
751                 ratio = 100;
752
753         snprintf(buf, sizeof(buf), "\r%-20.20s %3d%% ", curfile, ratio);
754
755         barlength = getttywidth() - 51;
756         if (barlength > 0) {
757                 i = barlength * ratio / 100;
758                 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
759                          "|%.*s%*s|", i,
760                          "*****************************************************************************"
761                          "*****************************************************************************",
762                          barlength - i, "");
763         }
764         i = 0;
765         abbrevsize = cursize;
766         while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
767                 i++;
768                 abbrevsize >>= 10;
769         }
770         snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %5d %c%c ",
771              (int) abbrevsize, prefixes[i], prefixes[i] == ' ' ? ' ' :
772                  'B');
773
774         timersub(&now, &lastupdate, &wait);
775         if (cursize > lastsize) {
776                 lastupdate = now;
777                 lastsize = cursize;
778                 if (wait.tv_sec >= STALLTIME) {
779                         start.tv_sec += wait.tv_sec;
780                         start.tv_usec += wait.tv_usec;
781                 }
782                 wait.tv_sec = 0;
783         }
784         timersub(&now, &start, &td);
785         elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
786
787         if (wait.tv_sec >= STALLTIME) {
788                 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
789                          " - stalled -");
790         } else if (statbytes <= 0 || elapsed <= 0.0 || cursize > totalsize || chunked) {
791                 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
792                          "   --:-- ETA");
793         } else {
794                 remaining = (int) (totalsize / (statbytes / elapsed) - elapsed);
795                 i = remaining / 3600;
796                 if (i)
797                         snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
798                                  "%2d:", i);
799                 else
800                         snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
801                                  "   ");
802                 i = remaining % 3600;
803                 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
804                          "%02d:%02d ETA", i / 60, i % 60);
805         }
806         write(STDERR_FILENO, buf, strlen(buf));
807
808         if (flag == -1) {
809                 struct sigaction sa;
810                 sa.sa_handler = updateprogressmeter;
811                 sigemptyset(&sa.sa_mask);
812                 sa.sa_flags = SA_RESTART;
813                 sigaction(SIGALRM, &sa, NULL);
814                 alarmtimer(1);
815         } else if (flag == 1) {
816                 alarmtimer(0);
817                 statbytes = 0;
818                 putc('\n', stderr);
819         }
820 }
821 #endif
822
823 /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
824  * much of which was blatantly stolen from openssh.  */
825
826 /*-
827  * Copyright (c) 1992, 1993
828  *      The Regents of the University of California.  All rights reserved.
829  *
830  * Redistribution and use in source and binary forms, with or without
831  * modification, are permitted provided that the following conditions
832  * are met:
833  * 1. Redistributions of source code must retain the above copyright
834  *    notice, this list of conditions and the following disclaimer.
835  * 2. Redistributions in binary form must reproduce the above copyright
836  *    notice, this list of conditions and the following disclaimer in the
837  *    documentation and/or other materials provided with the distribution.
838  *
839  * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
840  *              ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
841  *
842  * 4. Neither the name of the University nor the names of its contributors
843  *    may be used to endorse or promote products derived from this software
844  *    without specific prior written permission.
845  *
846  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
847  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
848  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
849  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
850  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
851  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
852  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
853  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
854  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
855  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
856  * SUCH DAMAGE.
857  *
858  *      $Id: wget.c,v 1.75 2004/10/08 08:27:40 andersen Exp $
859  */