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