/* 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