#include /* errno */ #include /* open(2) */ #include /* fprintf(3), stderr */ #include /* free(3), malloc(3), strtol(3), size_t */ #include /* memcpy(3), memmove(3), memset(3) */ #include /* EX_OK, EX_USAGE */ #include /* close(2), getopt(3), lseek(2), read(2), write(2), optarg, optind, STDIN_FILENO, STDOUT_FILENO */ /* Macro to use the appropriate string for argv[0] in the case that * argc == 0. */ #define ARGV0(argv0) ((argv0) == NULL ? program_name : (argv0)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) /* Macro to check if fd is a std* file, e.g. stdin. */ #define fdisstd(fd) \ ((fd) == STDIN_FILENO \ || (fd) == STDOUT_FILENO \ || (fd) == STDERR_FILENO) /* Macro to call the cleanup functions that operate on struct io on the * particular io[2] used in main. Error conditions are not checked because this * is only used when the program is about to terminate (hence its name). */ #define terminate(io) do{ \ Io_buffree(&(io)[0]); \ Io_buffree(&(io)[1]); \ Io_fdclose(&(io)[0]); \ Io_fdclose(&(io)[1]); }while(0) extern int errno; static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034"; static char *fmt_human = "%d+%d > %d+%d; %d > %d\n"; static char *fmt_output; static char *program_name = ""; static char *stdin_name = ""; static char *stdout_name = ""; static int read_flags = O_RDONLY; static int write_flags = O_WRONLY | O_CREAT; /* information that needs to be kept about each file in the "pipe" */ struct Io{ int bs; /* buffer size */ size_t bufuse; /* buffer usage */ char *buf; /* buffer */ int bytes; /* bytes processed */ int fd; /* file descriptor */ int fl; /* file opening flags */ char *fn; /* file name (may be stdin_name or stdout_name) */ int prec; /* partial records processed */ int rec; /* records processed */ long seek; /* bytes to seek/skip (will be 0 after skippage) */ }; /* Allocates *io's buffer. Returns NULL if successful. */ static void * Io_bufalloc(struct Io *io){ return (io->buf = malloc(io->bs * (sizeof *io->buf))); } /* Frees *io's buffer. Returns io. */ static struct Io * Io_buffree(struct Io *io){ free(io->buf); return io; } /* Fills the unused portion of io's buffer with padding, updating io->bufuse. * Returns io. */ static struct Io * Io_bufrpad(struct Io *io, int padding){ memset(io->buf + io->bufuse, padding, io->bs - io->bufuse); io->bufuse = io->bs; return io; } /* Copies from the buffer in src as much as possible to the free space in the * dest buffer, removing the copied units from src and permuting the remaining * units in the src buffer to the start of the buffer, modifying both the src * and dest bufuse and returning dest. */ static struct Io* Io_bufxapp(struct Io *dest, struct Io *src){ int n; n = MIN(src->bufuse, dest->bs - dest->bufuse); memcpy(dest->buf + dest->bufuse, src->buf, n); dest->bufuse += n; memmove(src->buf, src->buf + n, src->bs - n); src->bufuse -= n; return dest; } /* Copies from the buffer in src to the buffer in dest no more than n units, * removing the copied units from src and permuting the remaining units in the * src buffer to the start of the buffer, modifying both the src and dest * bufuse and returning dest. */ static struct Io* Io_bufxfer(struct Io *dest, struct Io *src, int n){ memcpy(dest->buf, src->buf, (dest->bufuse = n)); memmove(src->buf, src->buf + n, (src->bufuse -= n)); return dest; } /* Closes io->fn and returns -1 on error, otherwise io->fd. */ static int Io_fdclose(struct Io *io){ return fdisstd(io->fd) ? 0 : close(io->fd); } /* Opens io->fn and saves the file descriptor into io->fd. Returns io->fd, * which will be -1 if an error occured. */ static int Io_fdopen(struct Io *io, char *fn){ int fd; if((fd = open(fn, io->fl)) != -1 && Io_fdclose(io) == 0){ io->fd = fd; io->fn = fn; } return fd; } /* Reads io->bs bytes from *io's file descriptor into io->buf, storing the * number of read bytes in io->bufuse and updating io->bytes. If io->bufuse is * 0, errno will probably be set. Returns io. */ static struct Io * Io_read(struct Io *io){ io->bytes += (io->bufuse = read(io->fd, io->buf, io->bs)); return io; } /* Seeks io->seek bytes through *io's file descriptor, (counter-intuitively) * returning -1 if successful and a sysexits.h exit code if an unrecoverable * error occurred. */ static int Io_seek(struct Io *io){ ssize_t (*op)(int, void *, size_t); if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) return -1; else if(io->fl == write_flags){ /* cheat and use bufuse as the retval for write */ memset(io->buf, '\0', io->bs); /* This is a dirty trick; rather than testing conditions and operating * likewise, because the parameters to read or write are going to be * the same either way, just use a function pointer to keep track of * the intended operation. */ op = &write; /* Ignore -Wincompatible-pointer-types, it's fine. */ /* The difference is between const void * and void * which isn't * significant enough to affect program behavior. */ }else op = &read; do{ if( (io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek))) == 0) /* second chance */ io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek)); }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); io->bufuse = 0; return -1; } /* Sets the variables in a struct *io to the defaults. Identifies the read/ * write ends of the "pipe" by checking io->fl. Returns io. */ static struct Io * Io_setdefaults(struct Io *io){ io->bs = 1024; io->buf = NULL; io->bytes = 0; io->fd = (io->fl == read_flags) ? STDIN_FILENO : STDOUT_FILENO; io->fn = (io->fl == read_flags) ? stdin_name : stdout_name; io->prec = 0; io->rec = 0; io->seek = 0; return io; } /* Writes io->bufuse units from io->buf to io->fd, permuting any unwritten * bytes to the start of io->buf and updating io->bufuse. If io->bufuse doesn't * change, errno will probably be set. Returns io. */ static struct Io * Io_write(struct Io *io){ int t; if((t = write(io->fd, io->buf, io->bufuse)) > 0) memmove(io->buf, io->buf + t, (io->bufuse -= t)); io->bytes += t; return io; } /* Parses the string s to an integer, returning either the integer or in the * case of an error a negative integer. This is used for argument parsing * (e.g. -B [int]) in dj and no negative integer would be valid anyway. */ static long parse(char *s){ long r; errno = 0; r = (long)strtol(s, &s, 0); if(*s == '\0' /* no chars left unparsed */ && errno == 0) return r; else return -1; } /* Prints an error message suitable for the event of an operating system error, * with the error itself to be described in the string s. */ static int oserr(char *argv0, char *s){ fprintf(stderr, "%s: %s: %s\n", argv0, s, strerror(errno)); return EX_OSERR; } /* Prints statistics regarding the use of dj, particularly partially and * completely read and written records. */ struct Io * output(struct Io io[2]){ fprintf(stderr, fmt_output, io[0].rec, io[0].prec, io[1].rec, io[1].prec, io[0].bytes, io[1].bytes); return io; } static int usage(char *argv0){ fprintf(stderr, "Usage: %s (-fAH) (-a [byte]) (-c [count])\n" "\t(-i [input file]) (-b [input block size]) (-s [input offset])\n" "\t(-o [output file]) (-B [output block size]) (-S [output offset])\n", ARGV0(argv0)); return EX_USAGE; } int main(int argc, char *argv[]){ char align; char noerror; int c; int count; int p; /* pipe side */ struct Io io[2]; /* { read, write } same as pipe(2) */ /* defaults */ align = -1; count = 0; fmt_output = fmt_asv; noerror = 0; io[0].fl = read_flags; Io_setdefaults(&io[0]); io[1].fl = write_flags; Io_setdefaults(&io[1]); if(argc > 0) while((c = getopt(argc, argv, "a:Ac:i:b:fHs:o:B:S:")) != -1) if(c == 'i' || c == 'o'){ if(Io_fdopen(&io[c == 'o'], optarg) == -1){ terminate(io); return oserr(argv[0], optarg); } /* boolean/flag arguments */ }else if(c == 'f') noerror = 1; else if(c == 'A') align = '\0'; else if(c == 'H') fmt_output = fmt_human; /* optarg */ else if(c == 'a' && optarg[0] != '\0' && optarg[1] == '\0') align = optarg[0]; /* numeric args */ else if(!((c == 'c' && (count = parse(optarg)) >= 0) || (c == 'b' && (io[0].bs = parse(optarg)) >= 0) || (c == 's' && (io[0].seek = parse(optarg)) >= 0) || (c == 'B' && (io[1].bs = parse(optarg)) >= 0) || (c == 'S' && (io[1].seek = parse(optarg)) >= 0))){ terminate(io); return usage(argv[0]); } if(argc > optind){ terminate(io); return usage(argv[0]); } for(p = 0; p <= 1; ++p){ if(Io_bufalloc(&io[p]) == NULL){ fprintf(stderr, "%s: Failed to allocate %d bytes\n", ARGV0(argv[0]), io[p].bs); terminate(io); return EX_OSERR; }else if(io[p].seek > 0) /* seek */ switch(Io_seek(&io[p])){ case EX_OK: output(io); terminate(io); return EX_OK; } } do{ /* read */ Io_read(&io[0]); if(!noerror && io[0].bufuse == 0) Io_read(&io[0]); /* second chance */ if(io[0].bufuse == 0) /* that's all she wrote */ break; else if(io[0].bufuse < io[0].bs){ ++io[0].prec; if(noerror){ }else count = 1; if(align >= 0) Io_bufrpad(&io[0], align); }else ++io[0].rec; /* write */ do{ if(io[1].bs > io[0].bs){ /* io[1].bs > io[0].bs */ Io_bufxapp(&io[1], &io[0]); if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1) continue; /* we could write more */ }else Io_bufxfer(&io[1], &io[0], MIN(io[0].bufuse, io[1].bs)); c = io[1].bufuse; Io_write(&io[1]); if(!noerror && io[1].bufuse == c) Io_write(&io[1]); /* second chance */ if(c == io[1].bufuse) /* no more love */ count = 1; else if(c > io[1].bufuse && io[1].bufuse > 0){ io[1].prec += 1; if(noerror){ }else count = 1; }else if(io[1].bufuse == 0 && c < io[1].bs) ++io[1].prec; else ++io[1].rec; }while(io[0].bufuse > 0); }while(count == 0 || --count > 0); output(io); terminate(io); return EX_OK; }