/* vim: ts=2 sw=2 sts=2:et ai:

  pipemeter - A program to show status of a pipe

  Copyright Clint Byrum 2006, All Rights Reserved.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  ---

  $Id: pipemeter.c 102 2007-07-20 07:12:24Z clint $
  
*/


#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE

#include "config.h"
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#define VERSION PACKAGE_VERSION

#define DEFAULT_BLOCK_SIZE 8192
#define DEFAULT_INTERVAL 1
// block sizes over 8M rarely make any sense.
#define DEFAULT_MAX_BLOCK_SIZE (8*1024*1024)
#define DEFCOLS 70
//#define PBAR "[-----------------------------------------------------]"
#define PBCHAR '-'
#define PBLEFT '['
#define PBRIGHT ']'
#define PBFILL '*'
//#define STARS "*****************************************************"
//#define PBAR_LEN 53
// This must be changed if averages and rate formats are changed
// Note: Defines the space everything BUT the pbar takes up
#define OTHER_LEN 34
// Might want to turn down for slower machines.
// This defines the number of samples to average.
// -- 0.8 lowered to 12 to start adaptive block sizing sooner.
#define LAST_MAX 24
/* This defines the adaptive block sizing sampling frequency
 * This means, every X times that avg_bytes is called, we'll check the 
 * rate and change the block size if needed.
 */
#define SAMPLE_FREQ 3
// If rate increases by this much or more, block size gets increased - 
// This is a percentage
#define RATE_INCREASE 0.8
#define RATE_SAME -0.05
#define MIN_BLOCK 64
// Increase by this much
#define INC_PCT 0.10
// Decrease by this much
#define DEC_PCT -0.10
// Paths may not be longer than 64k
#define MAX_LINE 64*1024

// TODO: async/multi threaded to seperate reading and writing.

enum getbytesmode {
  report,
  normal
};

void show_just_rate(int sig);
void show_progress(int sig);
void parseopts(int argc, char *argv[]);
void formatbytes(char *obuffer,double b);
size_t full_write (int desc, const char *ptr, size_t len);
double avg_bytes(off_t abytes);
void setblock(char mode);
void adapt_blocksize(double pctchange);
time_t get_eta(double bps,off_t bytesleft);
time_t get_elapsed(void);
void formattime(char * outbuf,size_t outbufsize,time_t formatme);
double get_bytespersecond(enum getbytesmode mode); // must...get..rid..of..globals... rrggh
off_t parse_size(char *optarg);
void add_input_file(char *path);

// XXX: ok.. probably too many globals .. may have to clean this up

extern char **environ;

/* DEATH TO GLOBALS - maybe in the 2.0 rewrite. */
off_t bytes;
off_t lastbytes = 0;
off_t filesize = 0;
double itimer_seconds; // Hrm.. should this be time_t? :-P
off_t block_size;
off_t max_block_size;
char **filenames=NULL;
int filenames_count=0;
int report_mode=0;
struct timeval start_time;

char *progressbar;
char *progressfill;
unsigned int pbarlen;

// Used for calculating a more pertinent average.
off_t last[LAST_MAX];
unsigned char lcnt; // only used for indexing the above array

/* Adaptive block sizing */
double recordedrate=0;
double highestrate=0;
double lowestrate=0;
long last_bsize[LAST_MAX];
char *buffer;
char lastchange; // Used to tell the adaptive block sizing what we did last cycle
unsigned char needs_resize=0;
long new_block_size;
unsigned char adaptivemode=0; // Gets set to 1 if override stays 1
unsigned char adaptiveoverride=1; // set to 0 if user passes -a 
char *trailer; // no \r's TODO: timestamps?
unsigned char tcount=0;

int main(int argc, char *argv[]) {
  struct itimerval it;

  //FIXME: these should be initialized! (sig_empty or something?)
  // (I have a book I don't feel like looking for right now that explains it)
  struct sigaction sa,sa_orig;
  struct timeval interval;
  long bytesin,bytesout;
  long wholeseconds,microseconds;
  int in,out;
  int save_errno;
  int thisfile=0;
  int filesizeoverride=0;
  unsigned int columns,i;
  char *colstr;
  char *newbuffer; // Must be same type/makeup as buffer
  out=fileno(stdout);
  lastbytes=0;
  bytes=0;

  parseopts(argc,argv);

  if(filenames != NULL) {
    if(filesize) {
      fprintf(stderr, "Warning: -s overrides size of file given by -f!\n");
      filesizeoverride=1;
    }
    for(thisfile=0;thisfile<filenames_count;thisfile++){ 
      in = open(filenames[thisfile], O_RDONLY);
      if(in < 0) {
        save_errno=errno;
        fprintf(stderr,"Error opening input file %s: %s",filenames[thisfile],strerror(save_errno));
        exit(save_errno);
      }
      if(!filesizeoverride) {
        struct stat s;
        if(fstat(in, &s) != 0) {
          save_errno=errno;
          perror("fstat failed");
          exit(save_errno);
        } else {
          filesize += s.st_size;
        }
        //fprintf(stderr, "filesize is %lld\n", filesize);
        /* note: trying to use fstat on a file descriptor opened to /dev/zero
           results in a st_size of 0, which by coincidence disables the
           progress bar. This is exactly what I wanted, but I don't know
           how portable it is.

           Also: giving -f file on the command line shows the progress bar for
           regular files, with no way to disable it. This is *not* what I
           wanted, but will be OK for now.  -- B.
        */
      }
      close(in);
    }
  } else {
    filenames_count=1; // XXX HACK! This will allow the for loop to continue,
                       // But it also means that it is incorrectly reporting how
                       // man items are in the filenames array
  }
  // XXX: hrm.. why not just make columns a long.. ? :P
  errno=0;
  colstr=getenv("COLUMNS");
  if(colstr) {
    columns=(unsigned int)strtol(getenv("COLUMNS"),NULL,10);
  } else {
    columns=DEFCOLS;
  }
  // getenv() does not set errno, so no need to check it here

  pbarlen=columns-OTHER_LEN;
  progressbar=(char *)malloc(pbarlen*sizeof(char)+1);
  //strcpy(progressbar,PBAR);
  progressbar[0]=PBLEFT;
  // yes I'm aware it would be faster to set a limit before the loop. This is
  // only ever going to factor in if somebody has a 30000 column display
  for(i=1;i<pbarlen-3;i++) {
    progressbar[i]=PBCHAR;
  }
  progressbar[i]=PBRIGHT;
  progressbar[i+1]='\0';

  progressfill=(char *)malloc(pbarlen*sizeof(char));
  for(i=0;i<pbarlen-4;i++) {
    progressfill[i]=PBFILL;
  }
  progressfill[i]=PBRIGHT;
  progressfill[i+1]='\0';
 
  memset(&sa, 0, sizeof(struct sigaction));
  sa.sa_handler= filesize ? show_progress : show_just_rate;
  sigaction(SIGTERM, &sa, &sa_orig);
  sigaction(SIGINT,  &sa, &sa_orig);
  if(!report_mode) {
    // setup timers
    sigaction(SIGALRM, &sa, &sa_orig);
  
    /*
    interval.tv_usec = itimer_seconds * 1000000;
    interval.tv_sec  = 0;
    
    The code above works on GNU/Linux, but not on most other unices.

    It seems they validate tv_usec, and check it to see if its value is greater
    than 100000. This sort of makes sense, as 100000 microseconds is one second.
    However, I think its also very stupid, as the value is a long.. and so
    should not constrain the user. Plus, I haven't found any place where this
    limit is documented. How convenient.
    
    */

    wholeseconds = (long) itimer_seconds;
    microseconds = (itimer_seconds - (double) wholeseconds) * 1000000;

#ifdef DEBUG
    fprintf(stderr,"DEBUG: wholeseconds = %d, microseconds = %d\n"
           ,wholeseconds
           ,microseconds
        );
#endif

    interval.tv_usec=microseconds;
    interval.tv_sec =wholeseconds;
    
    it.it_value=interval;
    it.it_interval=interval;
    if(setitimer(ITIMER_REAL,&it,NULL)) {
      save_errno=errno;
      perror("error setting itimer");
      exit(save_errno);
    }
  }

  // Loop until we get an EOF on stdin
  //start_time=time(NULL);
  gettimeofday(&start_time,NULL);
  for(thisfile=0;thisfile<filenames_count;thisfile++) {
    if(filenames == NULL) {
      in=fileno(stdin);
    } else {
      in = open(filenames[thisfile], O_RDONLY);
    }
    while((bytesin=read(in,buffer,block_size))) {
      if(bytesin > 0) {
        bytes += bytesin;
        bytesout=full_write(out,buffer,bytesin);
        if(bytesout != bytesin) {
          /* Its possible full_write cleared errno, but we know the write
           * failed for some reason. */
          if(!errno) {
            save_errno=1;
          } else {
            save_errno=errno;
          }
          perror("write failed, aborting");
          exit(save_errno);
        }
        /* This has to be done here, so that we don't realloc away data in
         * the middle of a read/write
         */
        if(needs_resize) {
          block_size=new_block_size;
          while(!(newbuffer=(char *)realloc(buffer,block_size))) {
 #ifdef DEBUG
            fprintf(stderr,"\nDEBUG: bs=%ld,nbs=%ld\n",block_size,new_block_size);
 #endif
            new_block_size=new_block_size/2;
            /* even though this may mean we're decreasing it, I want it to
             * look like we left it alone - since we're only decreasing it
             * because of memory constraints, not performance
             */
            setblock(0);
          }
          buffer=newbuffer;
          needs_resize=0;
        }
      } else {
        /*
         * If there's no data available, we get an EINTR error. This is not bad.
         * But some platforms don't define EINTR.
        */
        int ignore=0;
 #ifdef EINTR
        ignore=(errno==EINTR);
 #endif
        if(!ignore) {
          /* ignore can only be true if errno was == EINTR, so that
           * assumption is carried to the exit call below. If the logic 
           * changes then we must reexamine this assumption later on.
           */
          perror("read failed, aborting");
          exit(errno);
        }
      }
    }
    close(in);
  }

  // Final status report
  if(filesize) {
    show_progress(0);
  } else {
    show_just_rate(0);
  }
  fprintf(stderr, "\n");
  return 0;
}
  
void show_just_rate(int sig) {
  /* if no progress bar, we use this function */
  char numbuf[512]; // XXX: hrm... bad form, but so much simpler
  char timebuf[10];
  double bytespersecond;
  enum getbytesmode gbmode = normal;
  time_t elapsedtime;
  
  elapsedtime = get_elapsed();
  if(sig != SIGALRM) {
    // If we're reporting
    gbmode=report;
    itimer_seconds = elapsedtime;
    lastbytes=0;
#ifdef DEBUG
    fprintf(stderr,"\nDEBUG: elapsedtime=%lf itimer_seconds=%lf\n",elapsedtime,itimer_seconds);
#endif
  }
  
  bytespersecond=get_bytespersecond(gbmode);
  formatbytes(numbuf,bytespersecond);
  fprintf(stderr,"%s/s",numbuf);
  /* Show total bytes through */
  /* Thanks to Sean Reifschneider for this idea */
  formatbytes(numbuf,bytes);
  fprintf(stderr," %s",numbuf);
  formatbytes(numbuf,block_size);
  fprintf(stderr," %s",numbuf);
  formattime(timebuf,sizeof(timebuf),elapsedtime);
  fprintf(stderr," %s",timebuf);
  lastbytes=bytes;
  if(sig == 0) {
    fprintf(stderr,"\n");
    exit(0);
  } else if(sig==SIGTERM || sig==SIGINT) {
    /* This way if the user aborts, something like this won't rm a file early:
     * pipemeter -f file > /somewhereelse/newfile && rm -f file
     * DOH!
     */
    fprintf(stderr,"\n");
    exit(1);
  } else {
    fprintf(stderr,trailer);
  }
}
 
/* I'm harping on it, because I have to do it. This provides us with a nice
 * example of why we need to use fewer globals.
 * Takes mode argument. Just calculates bytespersecond based on global stuff.
 * :/   Someone please convince me I'm wrong about this!!! ;)
 */
double get_bytespersecond(enum getbytesmode mode) {
  double bytespersecond;
  if(mode==report)
    tcount=0; // Forcing using the actual values, rather than the array
  if(tcount < LAST_MAX) {
#ifdef DEBUG
    fprintf(stderr,"\nDEBUG: bytes=%lld lastbytes=%lld\n"
           ,(long long) bytes,(long long) lastbytes);
#endif
    /* Bug reported by Jasper Lieviesse Adriaanse <jasper@nedbsd.nl>
      Must not divide by zero! ;) */
    if(itimer_seconds == 0) {
      /* infinite would be cooler, but really this should only happen
         briefly, when the clock skews */
      bytespersecond = -1;
    } else {
      bytespersecond=(double)(bytes-lastbytes)/(double)itimer_seconds;
    }
    avg_bytes(bytes-lastbytes);
    tcount++;
    if(tcount >= LAST_MAX) {
      // The array is filled, turn it on
      if(adaptiveoverride) {
        adaptivemode=1;
      }
    }
  } else {
    bytespersecond=avg_bytes(bytes-lastbytes);
  }
  return bytespersecond;
}

void show_progress(int sig) {
  //char buf[512]; // XXX: yes, this is bad form.
  char buf2[512]; // XXX: hopefully we fix them before there are 100 or so
  
  double percent  = (double)bytes / (double)filesize * 100;
  int    progress;
  double bytespersecond;

  char etabuf[10]; // Seriously, I dare you to overflow it

  enum getbytesmode gbmode=normal;

  time_t elapsedtime=0;

  
  progress = (double)bytes / (double)filesize * pbarlen;
#ifdef DEBUG
  fprintf(stderr,"DEBUG: prog=%d b=%lld pbl=%d",progress,(long long) bytes,pbarlen);
#endif
  if(sig != SIGALRM) {
    // If we're reporting
    gbmode=report;
    elapsedtime=get_elapsed();
    itimer_seconds = elapsedtime;
    lastbytes=0;
#ifdef DEBUG
    fprintf(stderr,"\nDEBUG: elapsedtime=%lf itimer_seconds=%lf\n",elapsedtime,itimer_seconds);
#endif
  }

  bytespersecond = get_bytespersecond(gbmode);
  formatbytes(buf2,bytespersecond);
  lastbytes=bytes;
  
  if(sig != SIGALRM) {
    formattime(etabuf,sizeof(etabuf),elapsedtime);
  } else {
    formattime(etabuf,sizeof(etabuf),get_eta(bytespersecond,filesize-bytes));
  }
  // This seems kludgy -- do I even care? ARGH GLOBALS!
  if(gbmode != report) {
    lastbytes=bytes;
  }

  if(progress > pbarlen) {
    progress = pbarlen; // just keep printing full progress bars. 
  }

  strncpy(progressbar+1,progressfill,progress);
  fprintf(stderr, progressbar);
  fprintf(stderr," %s/s",buf2);
  formatbytes(buf2,bytes);
  fprintf(stderr," %s",buf2);
  fprintf(stderr, " %5.1f%%", percent);
  fprintf(stderr, " %s", etabuf);
  if(sig == 0 || sig==SIGTERM || sig==SIGINT) {
    fprintf(stderr,"\n");
    exit(0);
  } else {
    fprintf(stderr,trailer);
  }
}

void parseopts(int argc, char *argv[]) {
  int c;
  FILE *listfile;
  char listline[MAX_LINE];
  int listdone;
  //
  // Thanks to WaruiInu on #linuxhelp-Undernet for reminding me to include getopt.h
  //
  // 22:28 <+WaruiInu> i suspect that another .h must be included
  // 22:29 <+WaruiInu> i think the .h you have included only has struct options ;
  // 22:29 <+WaruiInu> for declaring later
  // 22:29 <+WaruiInu> and the later defining is missing
  // 22:29 <+WaruiInu> it is my guess :)
  // 22:34 <+WaruiInu> warui = bad, inu = dog :D
  //
#ifdef HAVEGETOPTLONG
  static struct option longopts[] = {
     {"file",     1,NULL,'f'}
    ,{"size",     1,NULL,'s'}
    ,{"blocksize",1,NULL,'b'}
    ,{"interval", 1,NULL,'i'}
    ,{"maxblock", 1,NULL,'m'}
    ,{"list",     1,NULL,'F'}
    ,{"report",   0,NULL,'r'}
    ,{"version",  0,NULL,'V'}
    ,{"autooff",  0,NULL,'a'}
    ,{"log",      0,NULL,'l'}
    ,{NULL,       0,NULL,0}
  };
#endif
  
  // Set some defaults
  itimer_seconds=DEFAULT_INTERVAL;
  block_size=DEFAULT_BLOCK_SIZE;
  max_block_size=DEFAULT_MAX_BLOCK_SIZE;
  filenames = NULL;
  trailer="\r";
  do {
#ifdef HAVEGETOPTLONG
    c=getopt_long(argc,argv,"f:s:b:i:m:F:rVal",longopts,NULL);
#else
    c=getopt(argc,argv,"f:s:b:i:m:F:rVal");
#endif
    switch(c) {
    case -1:
      // No more options
      break;
    case 0:
      // No options passed, this is fine.
      break;
    case 'b':
      block_size=parse_size(optarg);
      break;
    case 'i':
      itimer_seconds=strtod(optarg,NULL);
      if(itimer_seconds <= 0.0) {
        fprintf(stderr,"Bad interval: %s\n",optarg);
        exit(1);
      }
      break;
    case 's':
      filesize=parse_size(optarg);
      break;
    case 'f':
      add_input_file(optarg);
      break;
    case 'F':
      /* reads in a file, and then processes each as if it was added via -f */
      listfile=fopen(optarg,"r");
      if(listfile==NULL) {
        perror("Couldn't read list file. Aborting.");
        exit(1);
      }
      while(listdone=(int)fgets(listline,MAX_LINE,listfile)) {
        /* We are not validating the length of line because we trust that 
           fgets adds a \0 to the end of the string as is documented in 
           at least glibc */
        if(listline[strlen(listline)-1]=='\n') {
          listline[strlen(listline)-1]='\0'; // remove newline
        }
        add_input_file(listline);
      }
      fclose(listfile);
      break;
    case 'm':
      max_block_size = parse_size(optarg);
      break;
    case 'r':
      report_mode=1;
      break;
    case 'V':
      // Exiting here because otherwise we'll start trying to read/write
      fprintf(stderr,"pipemeter v%s\n",VERSION);
      exit(0);
    case 'a':
      // Turn off adaptive block sizing
      adaptiveoverride=0;
      break;
    case 'l':
      trailer="\n";
      break;
    case '?':
    case ':':
      // XXX: better errors
    default:
      fprintf(stderr,"usage: pipemeter { -b blocksize } { -i interval } { -ahlr }\n");
      //fprintf(stderr,"debug: %c %d\n",(char)c,c);
      exit(1);
    }
  } while(c > 0);

  // Everything not an option is now considered a filename
  if(optind < argc) {
    while(optind < argc) {
      add_input_file(argv[optind++]);
    }
  }
  buffer=(char *)malloc(block_size*sizeof(char));
}

void formatbytes(char *obuffer,double b) {
  double tmp;
  if(b>1073741824) {
    tmp=b/1073741824;
    // If you can get it to go faster than 999.99G/s ... You win.
    sprintf(obuffer,"%7.2fG",tmp);
  } else if(b>1048576) {
    tmp=b/1048576;
    sprintf(obuffer,"%7.2fM",tmp);
  } else if(b>2048) { // under 2k, let it be
    tmp=b/1024;
    sprintf(obuffer,"%7.2fk",tmp);
  } else
    sprintf(obuffer,"%7.2fB",b);
}

/* This is used to get a smoother average */
// TODO: moving average?
double avg_bytes(off_t abytes) {
  off_t tmp=0;
  unsigned char i;
  double lastrate,ratediff,pctchange;
  last[lcnt]=abytes;
  if(lcnt == LAST_MAX-1) {
    lcnt=0;
  } else {
    lcnt++;
  }
  /* Adaptive block sizing */
  if(adaptivemode) {
#ifdef DEBUG
    fprintf(stderr,"DEBUG: lcnt=%d\n",lcnt);
#endif
    if((lcnt % SAMPLE_FREQ)==1) {
      /* check last SAMPLE_FREQ rates to see if they improved */
      for(i=0;i<SAMPLE_FREQ;i++) {
        tmp += last[lcnt-i];
      }
      lastrate=(double)((double)tmp/(double)SAMPLE_FREQ*(1.0/itimer_seconds));
      ratediff=lastrate-recordedrate;
      if(highestrate==0) highestrate=lastrate; /* should only happen once */
      if(lowestrate==0) lowestrate=lastrate;
      if(lastrate > highestrate) {
        highestrate=lastrate;
        pctchange=100.0; /* This should encourage more increases */
      } else if (lastrate < lowestrate) {
        lowestrate=lastrate;
        pctchange=-100.0; /* This should force a reversal */
      } else {
        pctchange=ratediff/recordedrate;
      }
      adapt_blocksize(pctchange);
      recordedrate=lastrate;
    }
  }
  tmp=0;
  for(i=0;i<LAST_MAX;i++) {
    tmp += last[i];
  }
  return (((double)tmp/(double)LAST_MAX)*(1.0/itimer_seconds));
}

/* Takes pctchange, calls setblock accordingly
 * TODO: stop being so lazy and get rid of some of those globals!
 */
void adapt_blocksize(double pctchange) {
  if(pctchange > RATE_INCREASE) {
    /* enough to be considered a higher rate */
    switch(lastchange) {
    case 1:
      /* We increased it last time and had success... more! */
      setblock(1);
      break;
    case 0:
      /* We left it alone last time and had a rate increase. Need more info. */
      setblock(0);
      break;
    case -1:
      /* We decreased it last time and had an increase. Lets try again. */
      setblock(-1);
    }
  } else if(pctchange > RATE_SAME) {
    /* enough to be considered as the same rate */
    switch(lastchange) {
    case 1:
      /* We increased it last time and it stayed the same. More! */
      setblock(1);
      break;
    case 0:
      /* Left it alone and it stayed the same. Duh! Increase it. */
      setblock(1);
      break;
    case -1:
      /* We decreased it and it stayed the same. Lets stay the same. */
      /* TODO: investigate whether it wouldn't be better to increase */
      setblock(0);
      break;
    }
  } else {
    /* low enough to be considered a loss */
    switch(lastchange) {
    case 1:
      /* We increased it, and had a decrease. Lets bump it back down */
      setblock(-1);
      break;
    case 0:
      /* Left it alone and it went down. Lets go up. */
      setblock(1);
      break;
    case -1:
      /* we decreased it and it went down. Back up. */
      setblock(1);
      break;
    }
  } 
  /* blah */
}
  
void setblock(char mode) {
#ifdef DEBUG
  fprintf(stderr,"DEBUG: setblock(%d) - lastchange=%d\n\n",mode,lastchange);
#endif
  if(mode==1) {
    //new_block_size=block_size*2;
    // double/half is too dramatic
    new_block_size=block_size+(block_size*INC_PCT);
    // 8 byte alignment
    while(new_block_size%8 != 0) {
      // Increase until 8 byte alignment is achieved
      new_block_size++;
    }
    if((new_block_size) < 0) {
      /* OOPS! we just went over our limit. Throttle back */
      new_block_size=block_size;
      mode=0;
    } else {
      needs_resize=1;
    }
  } else if(mode==-1) {
    /* This is to prevent going to stupid block sizes like 32 bytes */
    if(block_size > MIN_BLOCK) {
      //new_block_size=block_size/2;
      new_block_size=block_size+(block_size*DEC_PCT);
      while(new_block_size%8 !=0 && new_block_size > 8) {
        new_block_size--;
      }
      needs_resize=1;
    }
  }
  if(new_block_size > max_block_size) {
    new_block_size=block_size;
    needs_resize=0;
    return;
  } else {
    lastchange=mode;
  }
}

/* returns estimated time until completion in unix time */
/* TODO: make this more sophisticated */
time_t get_eta(double bps,off_t bytesleft) {
  return (time_t)(bytesleft/bps);
}  

/* returns elapsed time */
time_t get_elapsed(void) {
  struct timeval tnow;
  time_t tx,ty;
  gettimeofday(&tnow,NULL);
  tx=tnow.tv_sec+(tnow.tv_usec * 0.000001);
  ty=start_time.tv_sec+(start_time.tv_usec * 0.000001);
  return (tx - ty);
}

/* Formats time for ETA display */
void formattime(char *outbuf,size_t outbufsize,time_t formatme) {
  time_t hours;
  time_t minutes;
  time_t seconds;
  
  hours=(time_t)formatme/3600;
  seconds=formatme-(hours*3600);

  minutes=(time_t)seconds/60;
  seconds -= (minutes*60);

  if(outbuf==NULL) {
    fprintf(stderr,"Null pointer passed to formattime()! Abort Abort Abort!\n");
    exit(1);
  }
  /* Thanks to Petr Adamek for this bug report, and this fix (slightly
   * modified by me for clarity and sanity. ;) */
  if (formatme < 0 || hours >= 999) {
    strncpy(outbuf,"---:--:--",outbufsize-1);
  } else {
    snprintf(outbuf,outbufsize,"%3ld:%02ld:%02ld",hours,minutes,seconds);
  }
}

/* taken from fileutils... yay GPL (and thanks GNU for the code) */
size_t
full_write (int desc, const char *ptr, size_t len)
{
  size_t total_written = 0;

  while (len > 0)
    {
      ssize_t written = write (desc, ptr, len);
      if (written <= 0)
	{
	  /* Some buggy drivers return 0 when you fall off a device's end.  */
	  if (written == 0)
	    errno = ENOSPC;
#ifdef EINTR
	  if (errno == EINTR)
	    continue;
#endif
	  break;
	}
      total_written += written;
      ptr += written;
      len -= written;
    }
  return total_written;
}

off_t parse_size(char *optarg) {
  int mult=1;
  off_t temp;
  // blocksize qualifiers by Ian McMahon, tmbg@hardcoders.org, 9/07/2002
  // we're gonna examine the last char, and if it matches [kKmMgG] we'll act accordingly
  switch(optarg[strlen(optarg) - 1]) { 
    case 'g': /* FALLTHRU */
    case 'G': /* FALLTHRU */
      mult *= 1024;  
    case 'm': /* FALLTHRU */
    case 'M': /* FALLTHRU */
      mult *= 1024;  
    case 'k': /* FALLTHRU */
    case 'K': 
      mult *= 1024;  
      optarg[strlen(optarg) - 1] = '\0';
      break;
    default:
      break;
  }

// Fixes a bug where a specific size in bytes over 2GB could not be Input
#if SIZEOF_OFF_T > 4
  temp=strtoll(optarg,NULL,10);
#else
  temp=strtol(optarg,NULL,10);
#endif
  temp *= mult;

  if(temp==LONG_MIN || temp==LONG_MAX || temp <= 0) {
    fprintf(stderr,"Bad size: %s\n",optarg);
    exit(1);
  }
  return temp;
}

/* acts on global variables filenames and filenames_count */
void add_input_file(char *path) {
  filenames=(char **)realloc(filenames,sizeof(char *)*(filenames_count+1));
  if(filenames == NULL) {
    fprintf(stderr,"Error allocating memory for filenames list.\n");
    exit(1);
  }
  // +1 for \0
  filenames[filenames_count]=(char *)malloc(sizeof(char)*(strlen(path)+1));
  if(filenames[filenames_count] == NULL) {
    fprintf(stderr,"Error allocating memory for filename. %s\n",optarg);
    exit(1);
  }
  /* don't get on my back for using strcpy. we just allocated exactly
     enough space for the string as returned by strlen. strncpy is
     superfluous. */
  strcpy(filenames[filenames_count],path);
  filenames_count++;
}


syntax highlighted by Code2HTML, v. 0.9.1