1 /* vim: ts=2 sw=2 sts=2:et ai:
2
3 pipemeter - A program to show status of a pipe
4
5 Copyright Clint Byrum 2006, All Rights Reserved.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21 ---
22
23 $Id$
24
25 */
26
27
28 #define _LARGEFILE_SOURCE
29 #define _LARGEFILE64_SOURCE
30
31 #include "config.h"
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36
37 #include <stdio.h>
38 #include <signal.h>
39 #include <sys/time.h>
40 #include <errno.h>
41 #include <unistd.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <limits.h>
45 #include <time.h>
46
47 #ifdef HAVE_GETOPT_H
48 #include <getopt.h>
49 #endif
50
51 #define VERSION PACKAGE_VERSION
52
53 #define DEFAULT_BLOCK_SIZE 8192
54 #define DEFAULT_INTERVAL 1
55 // block sizes over 8M rarely make any sense.
56 #define DEFAULT_MAX_BLOCK_SIZE (8*1024*1024)
57 #define DEFCOLS 70
58 //#define PBAR "[-----------------------------------------------------]"
59 #define PBCHAR '-'
60 #define PBLEFT '['
61 #define PBRIGHT ']'
62 #define PBFILL '*'
63 //#define STARS "*****************************************************"
64 //#define PBAR_LEN 53
65 // This must be changed if averages and rate formats are changed
66 // Note: Defines the space everything BUT the pbar takes up
67 #define OTHER_LEN 34
68 // Might want to turn down for slower machines.
69 // This defines the number of samples to average.
70 // -- 0.8 lowered to 12 to start adaptive block sizing sooner.
71 #define LAST_MAX 24
72 /* This defines the adaptive block sizing sampling frequency
73 * This means, every X times that avg_bytes is called, we'll check the
74 * rate and change the block size if needed.
75 */
76 #define SAMPLE_FREQ 3
77 // If rate increases by this much or more, block size gets increased -
78 // This is a percentage
79 #define RATE_INCREASE 0.8
80 #define RATE_SAME -0.05
81 #define MIN_BLOCK 64
82 // Increase by this much
83 #define INC_PCT 0.10
84 // Decrease by this much
85 #define DEC_PCT -0.10
86 // Paths may not be longer than 64k
87 #define MAX_LINE 64*1024
88
89 // TODO: async/multi threaded to seperate reading and writing.
90
91 enum getbytesmode {
92 report,
93 normal
94 };
95
96 void show_just_rate(int sig);
97 void show_progress(int sig);
98 void parseopts(int argc, char *argv[]);
99 void formatbytes(char *obuffer,double b);
100 size_t full_write (int desc, const char *ptr, size_t len);
101 double avg_bytes(off_t abytes);
102 void setblock(char mode);
103 void adapt_blocksize(double pctchange);
104 time_t get_eta(double bps,off_t bytesleft);
105 time_t get_elapsed(void);
106 void formattime(char * outbuf,size_t outbufsize,time_t formatme);
107 double get_bytespersecond(enum getbytesmode mode); // must...get..rid..of..globals... rrggh
108 off_t parse_size(char *optarg);
109 void add_input_file(char *path);
110
111 // XXX: ok.. probably too many globals .. may have to clean this up
112
113 extern char **environ;
114
115 /* DEATH TO GLOBALS - maybe in the 2.0 rewrite. */
116 off_t bytes;
117 off_t lastbytes = 0;
118 off_t filesize = 0;
119 double itimer_seconds; // Hrm.. should this be time_t? :-P
120 off_t block_size;
121 off_t max_block_size;
122 char **filenames=NULL;
123 int filenames_count=0;
124 int report_mode=0;
125 struct timeval start_time;
126
127 char *progressbar;
128 char *progressfill;
129 unsigned int pbarlen;
130
131 // Used for calculating a more pertinent average.
132 off_t last[LAST_MAX];
133 unsigned char lcnt; // only used for indexing the above array
134
135 /* Adaptive block sizing */
136 double recordedrate=0;
137 double highestrate=0;
138 double lowestrate=0;
139 long last_bsize[LAST_MAX];
140 char *buffer;
141 char lastchange; // Used to tell the adaptive block sizing what we did last cycle
142 unsigned char needs_resize=0;
143 long new_block_size;
144 unsigned char adaptivemode=0; // Gets set to 1 if override stays 1
145 unsigned char adaptiveoverride=1; // set to 0 if user passes -a
146 char *trailer; // no \r's TODO: timestamps?
147 unsigned char tcount=0;
148
main(int argc,char * argv[])149 int main(int argc, char *argv[]) {
150 struct itimerval it;
151
152 //FIXME: these should be initialized! (sig_empty or something?)
153 // (I have a book I don't feel like looking for right now that explains it)
154 struct sigaction sa,sa_orig;
155 struct timeval interval;
156 long bytesin,bytesout;
157 long wholeseconds,microseconds;
158 int in,out;
159 int save_errno;
160 int thisfile=0;
161 int filesizeoverride=0;
162 unsigned int columns,i;
163 char *colstr;
164 char *newbuffer; // Must be same type/makeup as buffer
165 out=fileno(stdout);
166 lastbytes=0;
167 bytes=0;
168
169 parseopts(argc,argv);
170
171 if(filenames != NULL) {
172 if(filesize) {
173 fprintf(stderr, "Warning: -s overrides size of file given by -f!\n");
174 filesizeoverride=1;
175 }
176 for(thisfile=0;thisfile<filenames_count;thisfile++){
177 in = open(filenames[thisfile], O_RDONLY);
178 if(in < 0) {
179 save_errno=errno;
180 fprintf(stderr,"Error opening input file %s: %s",filenames[thisfile],strerror(save_errno));
181 exit(save_errno);
182 }
183 if(!filesizeoverride) {
184 struct stat s;
185 if(fstat(in, &s) != 0) {
186 save_errno=errno;
187 perror("fstat failed");
188 exit(save_errno);
189 } else {
190 filesize += s.st_size;
191 }
192 //fprintf(stderr, "filesize is %lld\n", filesize);
193 /* note: trying to use fstat on a file descriptor opened to /dev/zero
194 results in a st_size of 0, which by coincidence disables the
195 progress bar. This is exactly what I wanted, but I don't know
196 how portable it is.
197
198 Also: giving -f file on the command line shows the progress bar for
199 regular files, with no way to disable it. This is *not* what I
200 wanted, but will be OK for now. -- B.
201 */
202 }
203 close(in);
204 }
205 } else {
206 filenames_count=1; // XXX HACK! This will allow the for loop to continue,
207 // But it also means that it is incorrectly reporting how
208 // man items are in the filenames array
209 }
210 // XXX: hrm.. why not just make columns a long.. ? :P
211 errno=0;
212 colstr=getenv("COLUMNS");
213 if(colstr) {
214 columns=(unsigned int)strtol(getenv("COLUMNS"),NULL,10);
215 } else {
216 columns=DEFCOLS;
217 }
218 // getenv() does not set errno, so no need to check it here
219
220 pbarlen=columns-OTHER_LEN;
221 progressbar=(char *)malloc(pbarlen*sizeof(char)+1);
222 //strcpy(progressbar,PBAR);
223 progressbar[0]=PBLEFT;
224 // yes I'm aware it would be faster to set a limit before the loop. This is
225 // only ever going to factor in if somebody has a 30000 column display
226 for(i=1;i<pbarlen-3;i++) {
227 progressbar[i]=PBCHAR;
228 }
229 progressbar[i]=PBRIGHT;
230 progressbar[i+1]='\0';
231
232 progressfill=(char *)malloc(pbarlen*sizeof(char));
233 for(i=0;i<pbarlen-4;i++) {
234 progressfill[i]=PBFILL;
235 }
236 progressfill[i]=PBRIGHT;
237 progressfill[i+1]='\0';
238
239 memset(&sa, 0, sizeof(struct sigaction));
240 sa.sa_handler= filesize ? show_progress : show_just_rate;
241 sigaction(SIGTERM, &sa, &sa_orig);
242 sigaction(SIGINT, &sa, &sa_orig);
243 if(!report_mode) {
244 // setup timers
245 sigaction(SIGALRM, &sa, &sa_orig);
246
247 /*
248 interval.tv_usec = itimer_seconds * 1000000;
249 interval.tv_sec = 0;
250
251 The code above works on GNU/Linux, but not on most other unices.
252
253 It seems they validate tv_usec, and check it to see if its value is greater
254 than 100000. This sort of makes sense, as 100000 microseconds is one second.
255 However, I think its also very stupid, as the value is a long.. and so
256 should not constrain the user. Plus, I haven't found any place where this
257 limit is documented. How convenient.
258
259 */
260
261 wholeseconds = (long) itimer_seconds;
262 microseconds = (itimer_seconds - (double) wholeseconds) * 1000000;
263
264 #ifdef DEBUG
265 fprintf(stderr,"DEBUG: wholeseconds = %d, microseconds = %d\n"
266 ,wholeseconds
267 ,microseconds
268 );
269 #endif
270
271 interval.tv_usec=microseconds;
272 interval.tv_sec =wholeseconds;
273
274 it.it_value=interval;
275 it.it_interval=interval;
276 if(setitimer(ITIMER_REAL,&it,NULL)) {
277 save_errno=errno;
278 perror("error setting itimer");
279 exit(save_errno);
280 }
281 }
282
283 // Loop until we get an EOF on stdin
284 //start_time=time(NULL);
285 gettimeofday(&start_time,NULL);
286 for(thisfile=0;thisfile<filenames_count;thisfile++) {
287 if(filenames == NULL) {
288 in=fileno(stdin);
289 } else {
290 in = open(filenames[thisfile], O_RDONLY);
291 }
292 while((bytesin=read(in,buffer,block_size))) {
293 if(bytesin > 0) {
294 bytes += bytesin;
295 bytesout=full_write(out,buffer,bytesin);
296 if(bytesout != bytesin) {
297 /* Its possible full_write cleared errno, but we know the write
298 * failed for some reason. */
299 if(!errno) {
300 save_errno=1;
301 } else {
302 save_errno=errno;
303 }
304 perror("write failed, aborting");
305 exit(save_errno);
306 }
307 /* This has to be done here, so that we don't realloc away data in
308 * the middle of a read/write
309 */
310 if(needs_resize) {
311 block_size=new_block_size;
312 while(!(newbuffer=(char *)realloc(buffer,block_size))) {
313 #ifdef DEBUG
314 fprintf(stderr,"\nDEBUG: bs=%ld,nbs=%ld\n",block_size,new_block_size);
315 #endif
316 new_block_size=new_block_size/2;
317 /* even though this may mean we're decreasing it, I want it to
318 * look like we left it alone - since we're only decreasing it
319 * because of memory constraints, not performance
320 */
321 setblock(0);
322 }
323 buffer=newbuffer;
324 needs_resize=0;
325 }
326 } else {
327 /*
328 * If there's no data available, we get an EINTR error. This is not bad.
329 * But some platforms don't define EINTR.
330 */
331 int ignore=0;
332 #ifdef EINTR
333 ignore=(errno==EINTR);
334 #endif
335 if(!ignore) {
336 /* ignore can only be true if errno was == EINTR, so that
337 * assumption is carried to the exit call below. If the logic
338 * changes then we must reexamine this assumption later on.
339 */
340 perror("read failed, aborting");
341 exit(errno);
342 }
343 }
344 }
345 close(in);
346 }
347
348 // Final status report
349 if(filesize) {
350 show_progress(0);
351 } else {
352 show_just_rate(0);
353 }
354 fprintf(stderr, "\n");
355 return 0;
356 }
357
show_just_rate(int sig)358 void show_just_rate(int sig) {
359 /* if no progress bar, we use this function */
360 char numbuf[512]; // XXX: hrm... bad form, but so much simpler
361 char timebuf[10];
362 double bytespersecond;
363 enum getbytesmode gbmode = normal;
364 time_t elapsedtime;
365
366 elapsedtime = get_elapsed();
367 if(sig != SIGALRM) {
368 // If we're reporting
369 gbmode=report;
370 itimer_seconds = elapsedtime;
371 lastbytes=0;
372 #ifdef DEBUG
373 fprintf(stderr,"\nDEBUG: elapsedtime=%lf itimer_seconds=%lf\n",elapsedtime,itimer_seconds);
374 #endif
375 }
376
377 bytespersecond=get_bytespersecond(gbmode);
378 formatbytes(numbuf,bytespersecond);
379 fprintf(stderr,"%s/s",numbuf);
380 /* Show total bytes through */
381 /* Thanks to Sean Reifschneider for this idea */
382 formatbytes(numbuf,bytes);
383 fprintf(stderr," %s",numbuf);
384 formatbytes(numbuf,block_size);
385 fprintf(stderr," %s",numbuf);
386 formattime(timebuf,sizeof(timebuf),elapsedtime);
387 fprintf(stderr," %s",timebuf);
388 lastbytes=bytes;
389 if(sig == 0) {
390 fprintf(stderr,"\n");
391 exit(0);
392 } else if(sig==SIGTERM || sig==SIGINT) {
393 /* This way if the user aborts, something like this won't rm a file early:
394 * pipemeter -f file > /somewhereelse/newfile && rm -f file
395 * DOH!
396 */
397 fprintf(stderr,"\n");
398 exit(1);
399 } else {
400 fputs(trailer,stderr);
401 }
402 }
403
404 /* I'm harping on it, because I have to do it. This provides us with a nice
405 * example of why we need to use fewer globals.
406 * Takes mode argument. Just calculates bytespersecond based on global stuff.
407 * :/ Someone please convince me I'm wrong about this!!! ;)
408 */
get_bytespersecond(enum getbytesmode mode)409 double get_bytespersecond(enum getbytesmode mode) {
410 double bytespersecond;
411 if(mode==report)
412 tcount=0; // Forcing using the actual values, rather than the array
413 if(tcount < LAST_MAX) {
414 #ifdef DEBUG
415 fprintf(stderr,"\nDEBUG: bytes=%lld lastbytes=%lld\n"
416 ,(long long) bytes,(long long) lastbytes);
417 #endif
418 /* Bug reported by Jasper Lieviesse Adriaanse <jasper@nedbsd.nl>
419 Must not divide by zero! ;) */
420 if(itimer_seconds == 0) {
421 /* infinite would be cooler, but really this should only happen
422 briefly, when the clock skews */
423 bytespersecond = -1;
424 } else {
425 bytespersecond=(double)(bytes-lastbytes)/(double)itimer_seconds;
426 }
427 avg_bytes(bytes-lastbytes);
428 tcount++;
429 if(tcount >= LAST_MAX) {
430 // The array is filled, turn it on
431 if(adaptiveoverride) {
432 adaptivemode=1;
433 }
434 }
435 } else {
436 bytespersecond=avg_bytes(bytes-lastbytes);
437 }
438 return bytespersecond;
439 }
440
show_progress(int sig)441 void show_progress(int sig) {
442 //char buf[512]; // XXX: yes, this is bad form.
443 char buf2[512]; // XXX: hopefully we fix them before there are 100 or so
444
445 double percent = (double)bytes / (double)filesize * 100;
446 int progress;
447 double bytespersecond;
448
449 char etabuf[10]; // Seriously, I dare you to overflow it
450
451 enum getbytesmode gbmode=normal;
452
453 time_t elapsedtime=0;
454
455
456 progress = (double)bytes / (double)filesize * pbarlen;
457 #ifdef DEBUG
458 fprintf(stderr,"DEBUG: prog=%d b=%lld pbl=%d",progress,(long long) bytes,pbarlen);
459 #endif
460 if(sig != SIGALRM) {
461 // If we're reporting
462 gbmode=report;
463 elapsedtime=get_elapsed();
464 itimer_seconds = elapsedtime;
465 lastbytes=0;
466 #ifdef DEBUG
467 fprintf(stderr,"\nDEBUG: elapsedtime=%lf itimer_seconds=%lf\n",elapsedtime,itimer_seconds);
468 #endif
469 }
470
471 bytespersecond = get_bytespersecond(gbmode);
472 formatbytes(buf2,bytespersecond);
473 lastbytes=bytes;
474
475 if(sig != SIGALRM) {
476 formattime(etabuf,sizeof(etabuf),elapsedtime);
477 } else {
478 formattime(etabuf,sizeof(etabuf),get_eta(bytespersecond,filesize-bytes));
479 }
480 // This seems kludgy -- do I even care? ARGH GLOBALS!
481 if(gbmode != report) {
482 lastbytes=bytes;
483 }
484
485 if(progress > pbarlen) {
486 progress = pbarlen; // just keep printing full progress bars.
487 }
488
489 strncpy(progressbar+1,progressfill,progress);
490 fputs(progressbar,stderr);
491 fprintf(stderr," %s/s",buf2);
492 formatbytes(buf2,bytes);
493 fprintf(stderr," %s",buf2);
494 fprintf(stderr, " %5.1f%%", percent);
495 fprintf(stderr, " %s", etabuf);
496 if(sig == 0 || sig==SIGTERM || sig==SIGINT) {
497 fprintf(stderr,"\n");
498 exit(0);
499 } else {
500 fputs(trailer,stderr);
501 }
502 }
503
parseopts(int argc,char * argv[])504 void parseopts(int argc, char *argv[]) {
505 int c;
506 FILE *listfile;
507 char listline[MAX_LINE];
508 int listdone;
509 //
510 // Thanks to WaruiInu on #linuxhelp-Undernet for reminding me to include getopt.h
511 //
512 // 22:28 <+WaruiInu> i suspect that another .h must be included
513 // 22:29 <+WaruiInu> i think the .h you have included only has struct options ;
514 // 22:29 <+WaruiInu> for declaring later
515 // 22:29 <+WaruiInu> and the later defining is missing
516 // 22:29 <+WaruiInu> it is my guess :)
517 // 22:34 <+WaruiInu> warui = bad, inu = dog :D
518 //
519 #ifdef HAVEGETOPTLONG
520 static struct option longopts[] = {
521 {"file", 1,NULL,'f'}
522 ,{"size", 1,NULL,'s'}
523 ,{"blocksize",1,NULL,'b'}
524 ,{"interval", 1,NULL,'i'}
525 ,{"maxblock", 1,NULL,'m'}
526 ,{"list", 1,NULL,'F'}
527 ,{"report", 0,NULL,'r'}
528 ,{"version", 0,NULL,'V'}
529 ,{"autooff", 0,NULL,'a'}
530 ,{"log", 0,NULL,'l'}
531 ,{NULL, 0,NULL,0}
532 };
533 #endif
534
535 // Set some defaults
536 itimer_seconds=DEFAULT_INTERVAL;
537 block_size=DEFAULT_BLOCK_SIZE;
538 max_block_size=DEFAULT_MAX_BLOCK_SIZE;
539 filenames = NULL;
540 trailer="\r";
541 do {
542 #ifdef HAVEGETOPTLONG
543 c=getopt_long(argc,argv,"f:s:b:i:m:F:rVal",longopts,NULL);
544 #else
545 c=getopt(argc,argv,"f:s:b:i:m:F:rVal");
546 #endif
547 switch(c) {
548 case -1:
549 // No more options
550 break;
551 case 0:
552 // No options passed, this is fine.
553 break;
554 case 'b':
555 block_size=parse_size(optarg);
556 break;
557 case 'i':
558 itimer_seconds=strtod(optarg,NULL);
559 if(itimer_seconds <= 0.0) {
560 fprintf(stderr,"Bad interval: %s\n",optarg);
561 exit(1);
562 }
563 break;
564 case 's':
565 filesize=parse_size(optarg);
566 break;
567 case 'f':
568 add_input_file(optarg);
569 break;
570 case 'F':
571 /* reads in a file, and then processes each as if it was added via -f */
572 listfile=fopen(optarg,"r");
573 if(listfile==NULL) {
574 perror("Couldn't read list file. Aborting.");
575 exit(1);
576 }
577 while(listdone=(int)fgets(listline,MAX_LINE,listfile)) {
578 /* We are not validating the length of line because we trust that
579 fgets adds a \0 to the end of the string as is documented in
580 at least glibc */
581 if(listline[strlen(listline)-1]=='\n') {
582 listline[strlen(listline)-1]='\0'; // remove newline
583 }
584 add_input_file(listline);
585 }
586 fclose(listfile);
587 break;
588 case 'm':
589 max_block_size = parse_size(optarg);
590 break;
591 case 'r':
592 report_mode=1;
593 break;
594 case 'V':
595 // Exiting here because otherwise we'll start trying to read/write
596 fprintf(stderr,"pipemeter v%s\n",VERSION);
597 exit(0);
598 case 'a':
599 // Turn off adaptive block sizing
600 adaptiveoverride=0;
601 break;
602 case 'l':
603 trailer="\n";
604 break;
605 case '?':
606 case ':':
607 // XXX: better errors
608 default:
609 fprintf(stderr,"usage: pipemeter { -b blocksize } { -i interval } { -ahlr }\n");
610 //fprintf(stderr,"debug: %c %d\n",(char)c,c);
611 exit(1);
612 }
613 } while(c > 0);
614
615 // Everything not an option is now considered a filename
616 if(optind < argc) {
617 while(optind < argc) {
618 add_input_file(argv[optind++]);
619 }
620 }
621 buffer=(char *)malloc(block_size*sizeof(char));
622 }
623
formatbytes(char * obuffer,double b)624 void formatbytes(char *obuffer,double b) {
625 double tmp;
626 if(b>1073741824) {
627 tmp=b/1073741824;
628 // If you can get it to go faster than 999.99G/s ... You win.
629 sprintf(obuffer,"%7.2fG",tmp);
630 } else if(b>1048576) {
631 tmp=b/1048576;
632 sprintf(obuffer,"%7.2fM",tmp);
633 } else if(b>2048) { // under 2k, let it be
634 tmp=b/1024;
635 sprintf(obuffer,"%7.2fk",tmp);
636 } else
637 sprintf(obuffer,"%7.2fB",b);
638 }
639
640 /* This is used to get a smoother average */
641 // TODO: moving average?
avg_bytes(off_t abytes)642 double avg_bytes(off_t abytes) {
643 off_t tmp=0;
644 unsigned char i;
645 double lastrate,ratediff,pctchange;
646 last[lcnt]=abytes;
647 if(lcnt == LAST_MAX-1) {
648 lcnt=0;
649 } else {
650 lcnt++;
651 }
652 /* Adaptive block sizing */
653 if(adaptivemode) {
654 #ifdef DEBUG
655 fprintf(stderr,"DEBUG: lcnt=%d\n",lcnt);
656 #endif
657 if((lcnt % SAMPLE_FREQ)==1) {
658 /* check last SAMPLE_FREQ rates to see if they improved */
659 for(i=0;i<SAMPLE_FREQ;i++) {
660 tmp += last[lcnt-i];
661 }
662 lastrate=(double)((double)tmp/(double)SAMPLE_FREQ*(1.0/itimer_seconds));
663 ratediff=lastrate-recordedrate;
664 if(highestrate==0) highestrate=lastrate; /* should only happen once */
665 if(lowestrate==0) lowestrate=lastrate;
666 if(lastrate > highestrate) {
667 highestrate=lastrate;
668 pctchange=100.0; /* This should encourage more increases */
669 } else if (lastrate < lowestrate) {
670 lowestrate=lastrate;
671 pctchange=-100.0; /* This should force a reversal */
672 } else {
673 pctchange=ratediff/recordedrate;
674 }
675 adapt_blocksize(pctchange);
676 recordedrate=lastrate;
677 }
678 }
679 tmp=0;
680 for(i=0;i<LAST_MAX;i++) {
681 tmp += last[i];
682 }
683 return (((double)tmp/(double)LAST_MAX)*(1.0/itimer_seconds));
684 }
685
686 /* Takes pctchange, calls setblock accordingly
687 * TODO: stop being so lazy and get rid of some of those globals!
688 */
adapt_blocksize(double pctchange)689 void adapt_blocksize(double pctchange) {
690 if(pctchange > RATE_INCREASE) {
691 /* enough to be considered a higher rate */
692 switch(lastchange) {
693 case 1:
694 /* We increased it last time and had success... more! */
695 setblock(1);
696 break;
697 case 0:
698 /* We left it alone last time and had a rate increase. Need more info. */
699 setblock(0);
700 break;
701 case -1:
702 /* We decreased it last time and had an increase. Lets try again. */
703 setblock(-1);
704 }
705 } else if(pctchange > RATE_SAME) {
706 /* enough to be considered as the same rate */
707 switch(lastchange) {
708 case 1:
709 /* We increased it last time and it stayed the same. More! */
710 setblock(1);
711 break;
712 case 0:
713 /* Left it alone and it stayed the same. Duh! Increase it. */
714 setblock(1);
715 break;
716 case -1:
717 /* We decreased it and it stayed the same. Lets stay the same. */
718 /* TODO: investigate whether it wouldn't be better to increase */
719 setblock(0);
720 break;
721 }
722 } else {
723 /* low enough to be considered a loss */
724 switch(lastchange) {
725 case 1:
726 /* We increased it, and had a decrease. Lets bump it back down */
727 setblock(-1);
728 break;
729 case 0:
730 /* Left it alone and it went down. Lets go up. */
731 setblock(1);
732 break;
733 case -1:
734 /* we decreased it and it went down. Back up. */
735 setblock(1);
736 break;
737 }
738 }
739 /* blah */
740 }
741
setblock(char mode)742 void setblock(char mode) {
743 #ifdef DEBUG
744 fprintf(stderr,"DEBUG: setblock(%d) - lastchange=%d\n\n",mode,lastchange);
745 #endif
746 if(mode==1) {
747 //new_block_size=block_size*2;
748 // double/half is too dramatic
749 new_block_size=block_size+(block_size*INC_PCT);
750 // 8 byte alignment
751 while(new_block_size%8 != 0) {
752 // Increase until 8 byte alignment is achieved
753 new_block_size++;
754 }
755 if((new_block_size) < 0) {
756 /* OOPS! we just went over our limit. Throttle back */
757 new_block_size=block_size;
758 mode=0;
759 } else {
760 needs_resize=1;
761 }
762 } else if(mode==-1) {
763 /* This is to prevent going to stupid block sizes like 32 bytes */
764 if(block_size > MIN_BLOCK) {
765 //new_block_size=block_size/2;
766 new_block_size=block_size+(block_size*DEC_PCT);
767 while(new_block_size%8 !=0 && new_block_size > 8) {
768 new_block_size--;
769 }
770 needs_resize=1;
771 }
772 }
773 if(new_block_size > max_block_size) {
774 new_block_size=block_size;
775 needs_resize=0;
776 return;
777 } else {
778 lastchange=mode;
779 }
780 }
781
782 /* returns estimated time until completion in unix time */
783 /* TODO: make this more sophisticated */
get_eta(double bps,off_t bytesleft)784 time_t get_eta(double bps,off_t bytesleft) {
785 return (time_t)(bytesleft/bps);
786 }
787
788 /* returns elapsed time */
get_elapsed(void)789 time_t get_elapsed(void) {
790 struct timeval tnow;
791 time_t tx,ty;
792 gettimeofday(&tnow,NULL);
793 tx=tnow.tv_sec+(tnow.tv_usec * 0.000001);
794 ty=start_time.tv_sec+(start_time.tv_usec * 0.000001);
795 return (tx - ty);
796 }
797
798 /* Formats time for ETA display */
formattime(char * outbuf,size_t outbufsize,time_t formatme)799 void formattime(char *outbuf,size_t outbufsize,time_t formatme) {
800 time_t hours;
801 time_t minutes;
802 time_t seconds;
803
804 hours=(time_t)formatme/3600;
805 seconds=formatme-(hours*3600);
806
807 minutes=(time_t)seconds/60;
808 seconds -= (minutes*60);
809
810 if(outbuf==NULL) {
811 fprintf(stderr,"Null pointer passed to formattime()! Abort Abort Abort!\n");
812 exit(1);
813 }
814 /* Thanks to Petr Adamek for this bug report, and this fix (slightly
815 * modified by me for clarity and sanity. ;) */
816 if (formatme < 0 || hours >= 999) {
817 strncpy(outbuf,"---:--:--",outbufsize-1);
818 } else {
819 snprintf(outbuf,outbufsize,"%3ld:%02ld:%02ld",hours,minutes,seconds);
820 }
821 }
822
823 /* taken from fileutils... yay GPL (and thanks GNU for the code) */
824 size_t
full_write(int desc,const char * ptr,size_t len)825 full_write (int desc, const char *ptr, size_t len)
826 {
827 size_t total_written = 0;
828
829 while (len > 0)
830 {
831 ssize_t written = write (desc, ptr, len);
832 if (written <= 0)
833 {
834 /* Some buggy drivers return 0 when you fall off a device's end. */
835 if (written == 0)
836 errno = ENOSPC;
837 #ifdef EINTR
838 if (errno == EINTR)
839 continue;
840 #endif
841 break;
842 }
843 total_written += written;
844 ptr += written;
845 len -= written;
846 }
847 return total_written;
848 }
849
parse_size(char * optarg)850 off_t parse_size(char *optarg) {
851 int mult=1;
852 off_t temp;
853 // blocksize qualifiers by Ian McMahon, tmbg@hardcoders.org, 9/07/2002
854 // we're gonna examine the last char, and if it matches [kKmMgG] we'll act accordingly
855 switch(optarg[strlen(optarg) - 1]) {
856 case 'g': /* FALLTHRU */
857 case 'G': /* FALLTHRU */
858 mult *= 1024;
859 case 'm': /* FALLTHRU */
860 case 'M': /* FALLTHRU */
861 mult *= 1024;
862 case 'k': /* FALLTHRU */
863 case 'K':
864 mult *= 1024;
865 optarg[strlen(optarg) - 1] = '\0';
866 break;
867 default:
868 break;
869 }
870
871 // Fixes a bug where a specific size in bytes over 2GB could not be Input
872 #if SIZEOF_OFF_T > 4
873 temp=strtoll(optarg,NULL,10);
874 #else
875 temp=strtol(optarg,NULL,10);
876 #endif
877 temp *= mult;
878
879 if(temp==LONG_MIN || temp==LONG_MAX || temp <= 0) {
880 fprintf(stderr,"Bad size: %s\n",optarg);
881 exit(1);
882 }
883 return temp;
884 }
885
886 /* acts on global variables filenames and filenames_count */
add_input_file(char * path)887 void add_input_file(char *path) {
888 filenames=(char **)realloc(filenames,sizeof(char *)*(filenames_count+1));
889 if(filenames == NULL) {
890 fprintf(stderr,"Error allocating memory for filenames list.\n");
891 exit(1);
892 }
893 // +1 for \0
894 filenames[filenames_count]=(char *)malloc(sizeof(char)*(strlen(path)+1));
895 if(filenames[filenames_count] == NULL) {
896 fprintf(stderr,"Error allocating memory for filename. %s\n",optarg);
897 exit(1);
898 }
899 /* don't get on my back for using strcpy. we just allocated exactly
900 enough space for the string as returned by strlen. strncpy is
901 superfluous. */
902 strcpy(filenames[filenames_count],path);
903 filenames_count++;
904 }
905