diff --git a/Makefile b/Makefile index 91d69a1..6e554a4 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,12 @@ DESTDIR ?= dist PREFIX ?= /usr/local -MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \ +# normalized prefix +PREFIX_N != (test -d $(PREFIX) && [ '-' != $(PREFIX) ] \ + && CDPATH= cd -P -- $(PREFIX) && pwd -P) +MANDIR != [ $(PREFIX_N) = / ] && printf '/usr/share/man\n' \ || printf '/share/man\n' -SYSEXITS != printf '\043include \n' | cpp -M - | sed 's/ /\n/g' \ +SYSEXITS != printf '\043include \n' | cpp -M - | tr ' ' '\n' \ | sed -n 's/sysexits\.h//p' || printf 'include\n' CC ?= cc @@ -29,7 +32,7 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ CFLAGS += -I$(SYSEXITS) .PHONY: all -all: docs dj false fop hru intcmp mm npc rpn scrut str strcmp swab true +all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true # keep build/include until bindgen(1) has stdin support # https://github.com/rust-lang/rust-bindgen/issues/2703 @@ -40,8 +43,8 @@ build: clean: rm -rf build dist -dist: all - mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 +dist: all docs + mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 cp build/bin/* $(DESTDIR)/$(PREFIX)/bin cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 diff --git a/docs/dj.1 b/docs/dj.1 index 7031ccf..d358e3f 100644 --- a/docs/dj.1 +++ b/docs/dj.1 @@ -4,32 +4,24 @@ .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . .\" -.TH DJ 1 2024-06-17 "Harakit X.X.X" +.TH DJ 1 2024-07-03 "Harakit X.X.X" .SH NAME dj \(en disk jockey .\" .SH SYNOPSIS dj -.RB ( -AdHnq ) -.RB ( -a -.RB [ byte ]) -.RB ( -c -.RB [ count ]) +.RB [ -Hn ] +.RB [ -a\ byte ] +.RB [ -c\ count ] -.RB ( -i -[\fBinput file\fP]) -.RB ( -b -[\fBinput block size\fP]) -.RB ( -s -[\fBinput offset\fP]) +.RB [ -i\ file ] +.RB [ -b\ block_size ] +.RB [ -s\ offset ] -.RB ( -o -[\fBoutput file\fP]) -.RB ( -B -[\fBoutput block size\fP]) -.RB ( -S -[\fBoutput offset\fP]) +.RB [ -o\ file ] +.RB [ -B\ block_size ] +.RB [ -S\ offset ] .\" .SH DESCRIPTION @@ -42,68 +34,109 @@ respectively. This language is inherited from the .BR dd (1p) utility and used here to decrease ambiguity. -When seeking or skipping to a byte, writing or reading starts at the byte -immediately subsequent to the specified byte. +The offset used when skipping or seeking refers to how many bytes are skipped +or sought. Running +.BR dj (1) +with a skip offset of 1 skips one byte into the input and reads from the second +byte onwards. A programmer may think of a file as a zero-indexed array of +bytes; in this analogy, the offset given is the index of the byte at which to +start reading or writing. .\" .SH OPTIONS -.IP \fB-i\fP +.IP \fB-i\fP\ \fIfile\fP Takes a file path as an argument and opens it for use as an input. -.IP \fB-b\fP +.IP \fB-b\fP\ \fIblock_size\fP Takes a numeric argument as the size in bytes of the input buffer, the default being 1024. .IP \fB-s\fP -Takes a numeric argument as the number of bytes to skip into the input -before starting to read. If the standard input is used, bytes read to this point -are discarded. +Takes a numeric argument as the index of the byte at which reading will +commence; \(lqskips\(rq that number of bytes. If the standard input is used, +bytes read to this point are discarded. .IP \fB-o\fP Takes a file path as an argument and opens it for use as an output. -.IP \fB-B\fP +.IP \fB-B\fP\ \fIblock_size\fP Does the same as .B -b but for the output buffer. .IP \fB-S\fP -Seeks a number of bytes through the output before starting to write from -the input. If the output is a stream, null characters are printed. +Takes a numeric argument as the index of the byte at which writing will +commence; \(lqseeks\(rq that number of bytes. If the standard output is used, +null characters are printed. .IP \fB-a\fP Accepts a single literal byte with which the input buffer is padded in the event -of an incomplete read from the input file. -.IP \fB-A\fP -Specifying this option pads the input buffer with null bytes in the event of an -incomplete read. This is equivalent to specifying -.B -a -with a null byte instead of a character. +of an incomplete read from the input file. If the option argument is empty, the +null byte is used. .IP \fB-c\fP Specifies a number of reads to make. The default is 0, in which case the input is read until a partial or empty read is made. -.IP \fB-d\fP -Prints invocation information before program execution as described in the -DIAGNOSTICS section. Each invocation increments the debug level of the -program. .IP \fB-H\fP -Prints diagnostics messages in a human-readable manner as described in the +Prints diagnostic messages in a human-readable manner as described in the DIAGNOSTICS section. .IP \fB-n\fP Retries failed reads once before exiting. -.IP \fB-q\fP -Suppresses error messages which print when a read or write is partial or -empty. Each invocation decrements the debug level of the program. .\" .SH STANDARD INPUT The standard input shall be used as an input if no inputs are specified or if -one or more of the input files is \(lq-\(rq. +input file is \(lq-\(rq. .\" .SH STANDARD OUTPUT The standard output shall be used as an output if no inputs are specified or if -one or more of the input files is \(lq-\(rq. +the output file is \(lq-\(rq. +.\" +.SH EXAMPLES + +The following +.BR sh (1p) +line: + +.RS +printf 'Hello, world!\(rsn' | dj -c 1 -b 7 -s 7 2>/dev/null +.RE + +Produces the following output: + +.RS +world! +.RE + +The following +.BR sh (1p) +lines run sequentially: + +.RS +tr '\(rs0' 0 ibs=1024 skip=0 align=ff count=0 -out= obs=1024 seek=0 debug= 3 noerror=0 -.RE - In non-recoverable errors that don\(cqt pertain to the read-write cycle, a diagnostic message is printed and the program exits with the appropriate .BR sysexits.h (3) @@ -156,17 +175,26 @@ is specified along with the option and a count, actual byte output is the product of the count and the input block size and therefore may be lower than expected. If the .B -a -or -.B -A -options are specified, this could make written data nonsensical. +option is specified, this could make written data nonsensical. .\" .SH CAVEATS Existing files are not truncated on ouput and are instead overwritten. -Many lowercase options have capitalized variants and vice-versa which can be -confusing. Capitalized options tend to affect output or are more intense -versions of lowercase options. +The options +.B -b +and +.B -B +could be confused for each other, and so could +.B -s +and +.BR -S . +The lowercase option affects input and the capitalized option affects output. + +The skipped or sought bytes while processing irregular files, such as streams, +are reported in the diagnostic output, because they were actually read or +written. This is as opposed to bytes skipped while processing regular files, +which are not reported. .\" .SH RATIONALE @@ -187,3 +215,4 @@ Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later .\" .SH SEE ALSO .BR dd (1p) +.BR lseek (3p) diff --git a/docs/fop.1 b/docs/fop.1 index d777c68..b96033a 100644 --- a/docs/fop.1 +++ b/docs/fop.1 @@ -22,7 +22,7 @@ Performs operations on specified fields in data read from the standard input. .\" .SH OPTIONS -.IP \fB-d\fP +.IP \fB-d\fP\ \fIdelimiter\fP Sets a delimiter by which the input data will be split into fields. The default is an ASCII record separator. .\" diff --git a/docs/intcmp.1 b/docs/intcmp.1 index 034a4fd..b90f23a 100644 --- a/docs/intcmp.1 +++ b/docs/intcmp.1 @@ -11,9 +11,7 @@ intcmp \(en compare integers .SH SYNOPSIS intcmp -.RB ( -egl ) -.RB [ integer ] -.RB [ integer... ] +.RB [ -egl ]\ integer\ integer... .SH DESCRIPTION Compare integers to each other. .\" diff --git a/docs/mm.1 b/docs/mm.1 index 2ff9f44..2916aa7 100644 --- a/docs/mm.1 +++ b/docs/mm.1 @@ -10,11 +10,9 @@ mm \(en middleman .SH SYNOPSIS mm -.RB ( -aenu ) -.RB ( -i -.RB [ input ]) -.RB ( -o -.RB [ output ]) +.RB [ -aenu ] +.RB [ -i\ input ] +.RB [ -o\ output ] .\" .SH DESCRIPTION @@ -26,10 +24,10 @@ Catenate input files and write them to the start of each output file or stream. Opens subsequent outputs for appending rather than updating. .IP \fB-e\fP Use the standard error as an output. -.IP \fB-i\fP +.IP \fB-i\fP\ \fIinput\fP Opens a path as an input. If one or more of the input files is \(lq-\(rq or if no inputs are specified, the standard input shall be used. -.IP \fB-o\fP +.IP \fB-o\fP\ \fIoutput\fP Opens a path as an output. If one or more of the output files is \(lq-\(rq or if no outputs are specified, the standard output shall be used. .IP \fB-u\fP diff --git a/docs/npc.1 b/docs/npc.1 index 3e7af39..5acee9e 100644 --- a/docs/npc.1 +++ b/docs/npc.1 @@ -11,7 +11,7 @@ npc \(en show non-printing characters .SH SYNOPSIS npc -.RB ( -et ) +.RB [ -et ] .\" .SH DESCRIPTION diff --git a/docs/scrut.1 b/docs/scrut.1 index 56383b8..2b95bee 100644 --- a/docs/scrut.1 +++ b/docs/scrut.1 @@ -10,8 +10,8 @@ scrut \(en scrutinize file properties .SH SYNOPSIS scrut -.RB ( -LSbcdefgkprsuwx ) -.RB [ file... ] +.RB [ -LSbcdefgkprsuwx ] +.B file... .\" .SH DESCRIPTION diff --git a/docs/str.1 b/docs/str.1 index 22ffea1..1a4d8e4 100644 --- a/docs/str.1 +++ b/docs/str.1 @@ -11,8 +11,7 @@ str \(en test string arguments .SH SYNOPSIS str -.RB [ type ] -.RB [ string... ] +.B type string... .\" .SH DESCRIPTION diff --git a/docs/strcmp.1 b/docs/strcmp.1 index 0ad21b2..c99c8c8 100644 --- a/docs/strcmp.1 +++ b/docs/strcmp.1 @@ -11,8 +11,7 @@ strcmp \(en compare strings .SH SYNOPSIS strcmp -.RM [ string ] -.RB [ strings... ] +.B string string... .\" .SH DESCRIPTION diff --git a/docs/swab.1 b/docs/swab.1 index 72f0f19..42eef95 100644 --- a/docs/swab.1 +++ b/docs/swab.1 @@ -11,11 +11,8 @@ swab \(en swap bytes .SH SYNOPSIS swab -.RB ( -f ) -.RB ( -w -.R [ -.B word size -.R ]) +.RB [ -f ] +.RB [ -w\ word_size ] .\" .SH DESCRIPTION @@ -25,11 +22,10 @@ Swap the latter and former halves of a block of bytes. .IP \fB-f\fP Ignore SIGINT signal. -.IP \fB-w\fP -Configures the word size; that is, the size in bytes of the block size -on which to operate. The default word size is 2. The word size must be -cleanly divisible by 2, otherwise the block of bytes being processed can\(cqt be -halved. +.IP \fB-w\fP\ \fIword_size\fP +Configures the word size; that is, the size in bytes of the block size on which +to operate. The default word size is 2. The word size must be cleanly divisible +by 2, otherwise the block of bytes being processed can\(cqt be halved. .\" .SH EXAMPLES diff --git a/src/dj.c b/src/dj.c index 8a6732c..8fe1af3 100644 --- a/src/dj.c +++ b/src/dj.c @@ -16,282 +16,113 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -#include /* isupper(3), tolower(3) */ +#include /* assert(3) */ #include /* errno */ #include /* open(2) */ #include /* fprintf(3), stderr */ -#include /* free(3), malloc(3), strtol(3), size_t */ +#include /* malloc(3), strtol(3), size_t */ #include /* memcpy(3), memmove(3), memset(3) */ -#include /* EX_OK, EX_USAGE */ +#if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE +# include +#endif #include /* close(2), getopt(3), lseek(2), read(2), write(2), * optarg, optind, STDIN_FILENO, STDOUT_FILENO */ +#include /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, + S_IWUSR */ extern int errno; +char *program_name = "dj"; + /* dj uses two structures that respectively correspond to the reading and * writing ends of its jockeyed "pipe". User-configurable members are noted * with their relevant options. */ struct Io{ - int bs; /* buffer size (-bB) */ + char *buf; /* buffer */ + char *fn; /* file name (-io) */ + size_t bs; /* buffer size (-bB) */ 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) (-io) */ - int prec; /* partial records processed */ - int rec; /* records processed */ - long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */ -} ep[2]; /* "engineered pipe"; also "extended play", for the deejay */ + size_t bytes; /* bytes processed */ + size_t prec; /* partial records processed */ + size_t rec; /* records processed */ + long seek; /* remaining bytes to seek/skip (-sS) */ + int error; /* errno */ + int fd; /* file descriptor */ + int fl; /* file opening flags */ +}; -/* Additionally, the following global variables are used to store user options. - */ +/* To be assigned to main:fmt and used with printio(). */ +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"; -/* (-a) */ static int align; /* Only the lower 8b are used but align is - * negative if no alignment is being done. */ - -/* (-c) */ static int count; /* 0 if dj(1) runs until no more reads are - * possible. */ - -/* ASCII field separator delimited statistics */ - static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034"; -/* human-readable statistics */ - static char *fmt_human = "%d+%d > %d+%d; %d > %d\n"; -/* pointer to chosen formatting */ -/* (-H) */ static char *fmt_output; /* fmt_asv (default) or fmt_human (-H) */ - -/* (-dq) */ static char debug; /* - * -d increments dj -qq | 0 - no diagnostic output whatsoever - * -q decrements dj -q | 1 - typical output without - * | notifications on partial reads or - * | writes - * dj | 2 - typical output (default) - * dj -d | 3 - verbose status messages */ - -/* (-n) */ static char noerror; /* 0 - exits on partial reads or writes - * (default) - * 1 - retries on partial reads/writes - * (-n) */ - -/* Non-configurable defaults. */ -#define bs_default 1024 /* GNU dd(1) default; twice POSIX but a neat 2^10 */ -static char *program_name = ""; static char *stdin_name = ""; static char *stdout_name = ""; -static int read_flags = O_RDONLY; /* These flags are consistent with Busybox */ -static int write_flags = O_WRONLY | O_CREAT; /* dd(1). */ -/* Macro to set defaults for user-configurable options. */ -#define setdefaults do{ \ - align = -1; \ - count = 0; \ - debug = 2; \ - fmt_output = fmt_asv; \ - noerror = 0; \ - ep[0].fl = read_flags; \ - Io_setdefaults(&ep[0]); \ - ep[1].fl = write_flags; \ - Io_setdefaults(&ep[1]); }while(0) +static int creat_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH + | S_IWOTH; /* Consistent with touch(1p). */ +static int read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */ +static int write_flags = O_WRONLY | O_CREAT; #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 check if fd is stdin or stdout */ +#define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_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) - -/* Allocates *io's buffer. Returns NULL if unsuccessful. */ -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, - /* these are the flags used by touch(1p) */ - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) - != -1 - && Io_fdclose(io) == 0){ - io->fd = fd; - io->fn = fn; - } - - return fd; -} - -/* 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. io->buf will be cleared of useful bytes and io->seek will - * be set to zero to indicate the seek occurred. */ -static int -Io_fdseek(struct Io *io){ - int (*op)(int, void *, size_t); - - if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) - return -1; - - /* repeated code to get the condition out of the loop */ - if(io->fl == write_flags){ - memset(io->buf, '\0', io->bs); - /* We're going to cheat and use bufuse as the retval for write(2), - * which is fine because it'll be zeroed as this function returns - * anyway. */ - do{ - if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek))) - == 0) - /* second chance */ - io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)); - }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); - }else if(io->fl == read_flags){ - do{ - if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek))) - == 0) - /* second chance */ - io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)); - }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); - }else - return EX_SOFTWARE; - - io->bufuse = 0; - - return -1; -} - -/* 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){ + int t; - io->bytes += (io->bufuse = read(io->fd, io->buf, io->bs)); + assert(io->bs > 0); + assert(io->bufuse < io->bs); + + if((t = read(io->fd, &(io->buf)[io->bufuse], io->bs - io->bufuse)) < 0){ + io->error = errno; + t = 0; + } + + io->bufuse += t; + io->bytes += t; + io->prec += (0 < io->bufuse && io->bufuse < io->bs); + io->rec += (io->bufuse == io->bs); + + assert(io->bufuse <= io->bs); return io; } -/* 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 = bs_default; - 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)); + assert(io->bufuse > 0); + assert(io->bufuse <= io->bs); + + if((t = write(io->fd, io->buf, io->bufuse)) < 0){ + io->error = errno; + t = 0; + }else if(t > 0) + memmove(io->buf, &(io->buf)[t], (io->bufuse -= t)); + io->bytes += t; + io->prec += (t > 0 && io->bufuse > 0); + io->rec += (t > 0 && io->bufuse == 0); return io; } -/* 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 *s){ - - fprintf(stderr, "%s: %s: %s\n", program_name, s, strerror(errno)); - +oserr(char *e, int n){ + fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n)); return EX_OSERR; } /* Prints statistics regarding the use of dj, particularly partially and - * completely read and written records, accessing debug, ep, and fmt_output. */ + * completely read and written records. */ static void -output(void){ +fprintio(FILE *stream, char *fmt, struct Io io[2]){ - if(debug >= 1) - fprintf(stderr, fmt_output, - ep[0].rec, ep[0].prec, ep[1].rec, ep[1].prec, - ep[0].bytes, ep[1].bytes); + fprintf(stream, fmt, + io[0].rec, io[0].prec, io[1].rec, io[1].prec, + io[0].bytes, io[1].bytes); return; } @@ -311,142 +142,211 @@ parse(char *s){ } static int -usage(void){ +usage(char *s){ - fprintf(stderr, "Usage: %s (-AdfHqQ) (-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", + fprintf(stderr, "Usage: %s [-Hn] [-a byte] [-c count]\n" + "\t[-i file] [-b block_size] [-s offset]\n" + "\t[-o file] [-B block_size] [-S offset]\n", program_name); return EX_USAGE; } int main(int argc, char *argv[]){ - int c; - int i; + int align; /* low 8b used, negative if no alignment is being done */ + int count; /* 0 if dj(1) runs until no more reads are possible */ + char *fmt; /* == fmt_asv (default) or fmt_human (-H) */ + size_t i; /* side of io being modified */ + char noerror; /* 0=exits (default) 1=retries on partial reads or writes */ + struct Io io[2 /* { in, out } */]; - setdefaults; + /* Set defaults. */ + align = -1; + count = 0; + fmt = fmt_asv; + noerror = 0; + for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ + io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */ + io[i].bufuse = 0; + io[i].bytes = 0; + io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO; + io[i].fn = i == 0 ? stdin_name : stdout_name; + io[i].fl = i == 0 ? read_flags : write_flags; + io[i].error = 0; + io[i].prec = 0; + io[i].rec = 0; + io[i].seek = 0; + } if(argc > 0){ + int c; + program_name = argv[0]; - while((c = getopt(argc, argv, "a:Ab:B:c:di:hHnqs:S:o:")) != -1) + while((c = getopt(argc, argv, ":a:b:B:c:i:hHns:S:o:")) != -1) switch(c){ - case 'i': case 'o': - i = (c == 'o'); + case 'i': case 'o': i = (c == 'o'); if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */ - ep[i].fd = (i == 0) ? STDIN_FILENO : STDOUT_FILENO; - ep[i].fn = (i == 0) ? stdin_name : stdout_name; + io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO; + io[i].fn = i == 0 ? stdin_name : stdout_name; break; - }else if(Io_fdopen(&ep[i], optarg) != -1) - break; - terminate(ep); - return oserr(optarg); - case 'A': align = '\0'; break; - case 'd': ++debug; break; - case 'n': noerror = 1; break; - case 'H': fmt_output = fmt_human; break; - case 'q': --debug; break; + }else{ + int fd; + + if((fd = open(optarg, io[i].fl, creat_mode)) != -1 + && (fdisstd(io[i].fd) || close(io[i].fd) == 0)){ + io[i].fd = fd; + io[i].fn = optarg; + break; + } + } + return oserr(optarg, errno); + case 'n': noerror = 1; break; + case 'H': fmt = fmt_human; break; case 'a': - if(optarg[0] != '\0' && optarg[1] == '\0'){ + if(optarg[0] == '\0' || optarg[1] == '\0'){ align = optarg[0]; break; } /* FALLTHROUGH */ - case 'c': case 'b': case 's': case 'B': case 'S': + case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */ if(c == 'c' && (count = parse(optarg)) >= 0) break; - i = isupper(c); - c = tolower(c); - if((c == 'b' && (ep[i].bs = parse(optarg)) > 0) - || (c == 's' && (ep[i].seek = parse(optarg)) >= 0)) + i = (c >= 'A' && c <= 'Z'); + c |= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */ + if((c == 'b' && (io[i].bs = parse(optarg)) > 0) + || (c == 's' && (io[i].seek = parse(optarg)) >= 0)) break; /* FALLTHROUGH */ default: - terminate(ep); - return usage(); + return usage(program_name); } } - if(debug >= 3) - fprintf(stderr, - "argv0=%s\n" - "in=%s\tibs=%d\tskip=%ld\talign=%hhx\tcount=%d\n" - "out=%s\tobs=%d\tseek=%ld\tdebug=%2d\tnoerror=%d\n", - program_name, - ep[0].fn, ep[0].bs, ep[0].seek, align, count, - ep[1].fn, ep[1].bs, ep[1].seek, debug, noerror); + assert(io->fd != STDIN_FILENO || io->fl == read_flags); + assert(io->fd != STDOUT_FILENO || io->fl == write_flags); - if(argc > optind){ - terminate(ep); - return usage(); - } + if(argc > optind) + return usage(program_name); - for(i = 0; i <= 1; ++i){ - if(Io_bufalloc(&ep[i]) == NULL){ - fprintf(stderr, "%s: Failed to allocate %d bytes\n", - program_name, ep[i].bs); - terminate(ep); + for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ + /* buffer allocation */ + if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){ + fprintf(stderr, "%s: Failed to allocate %zd bytes\n", + program_name, io[i].bs); return EX_OSERR; - }else if(ep[i].seek > 0) - switch(Io_fdseek(&ep[i])){ - case EX_OK: - output(); - terminate(ep); - return EX_OK; - } + } + /* easy seeking */ + if(!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1) + io[i].seek = 0; } - do{ /* read */ - Io_read(&ep[0]); - if(!noerror && ep[0].bufuse == 0) - Io_read(&ep[0]); /* second chance */ - if(ep[0].bufuse == 0) /* that's all she wrote */ - break; - else if(ep[0].bufuse < ep[0].bs){ - ++ep[0].prec; - if(debug >= 2){ - fprintf(stderr, "%s: Partial read:\n\t", program_name); - output(); - } - if(!noerror) - count = 1; - if(align >= 0) - Io_bufrpad(&ep[0], align); - }else - ++ep[0].rec; + /* hard seeking */ + if(io[1].seek > 0){ + size_t t; + do{ + memset(io[1].buf, '\0', + (t = io[1].bufuse = MIN(io[1].bs, io[1].seek))); + if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) + Io_write(&io[1]); /* second chance */ + if(io[1].error != 0) + return oserr(io[1].fn, io[1].error); + }while((io[1].seek -= (t - io[1].bufuse)) > 0 && io[1].bufuse != t); + io[1].bufuse = 0; + } - /* write */ - do{ if(ep[1].bs > ep[0].bs){ /* io[1].bs > io[0].bs */ - Io_bufxapp(&ep[1], &ep[0]); - if(ep[0].bs + ep[1].bufuse <= ep[1].bs && count != 1) - continue; /* we could write more */ - }else - Io_bufxfer(&ep[1], &ep[0], MIN(ep[0].bufuse, ep[1].bs)); + if(io[1].seek > 0){ + fprintio(stderr, fmt, io); + return oserr(io[1].fn, errno); + } - c = ep[1].bufuse; - Io_write(&ep[1]); - if(!noerror && ep[1].bufuse == c) - Io_write(&ep[1]); /* second chance */ - if(c == ep[1].bufuse){ /* no more love */ - count = 1; + do{ + assert(io[0].bufuse == 0); + + { /* read */ + char skipping; + size_t t; + + /* hack to intentionally get a partial read from Io_read */ + if((skipping = (io[0].seek > 0)) && io[0].seek < io[0].bs) + io[0].bufuse = io[0].bs - io[0].seek; + + t = io[0].bufuse; + if(Io_read(&io[0])->bufuse == t && !noerror && io[0].error == 0) + Io_read(&io[0]); /* second chance */ + assert(io[0].bufuse >= t); + if(io[0].bufuse == t) /* that's all she wrote */ break; - }else if(c > ep[1].bufuse && ep[1].bufuse > 0){ - ep[1].prec += 1; - if(debug >= 2){ - fprintf(stderr, "%s: Partial write:\n\t", program_name); - output(); - } + + if(/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs){ + fprintf(stderr, "%s: Partial read:\n\t", program_name); + fprintio(stderr, fmt, io); if(!noerror) count = 1; - }else if(ep[1].bufuse == 0 && c < ep[1].bs) - ++ep[1].prec; - else - ++ep[1].rec; - }while(ep[0].bufuse > 0); + if(align >= 0){ + /* fill the rest of the ibuf with padding */ + memset(&(io[0].buf)[io[0].bufuse], align, + io[0].bs - io[0].bufuse); + io->bufuse = io->bs; + } + } + + if(skipping){ + io[0].bufuse = 0; + count += (count != 0); + continue; + } + } + + /* write */ + do{ + int t; + + if(io[0].bs <= io[1].bs){ + int n; + + /* saturate obuf */ + memcpy(io[1].buf, io[0].buf, + (io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))); + /* permute the copied units out of ibuf */ + memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n)); + }else /* if(io[0].bs < io[1].bs) */ { + int n; + + /* drain what we can from ibuf */ + memcpy(&(io[1].buf)[io[1].bufuse], io[0].buf, + (n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse))); + io[1].bufuse += n; + /* permute out the copied units */ + memmove(io[0].buf, &(io[0].buf)[n], io[0].bs - n); + io[0].bufuse -= n; + + if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1) + continue; /* obuf not saturated - we could write more */ + } + + t = io[1].bufuse; + if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) + Io_write(&io[1]); /* second chance */ + assert(io[1].bufuse <= t); + if(io[1].bufuse == t){ /* no more love */ + count = 1; + break; + } + + if(0 < io[1].bufuse /* && io[1].bufuse < t */){ + fprintf(stderr, "%s: Partial write:\n\t", program_name); + fprintio(stderr, fmt, io); + if(!noerror) + count = 1; + } + }while(io[0].bufuse > 0); }while(count == 0 || --count > 0); - output(); - terminate(ep); + fprintio(stderr, fmt, io); + + for(i = 0; i < (sizeof io) / (sizeof *io); ++i) + if(io[i].error) + return oserr(io[i].fn, io[i].error); return EX_OK; } diff --git a/src/intcmp.c b/src/intcmp.c index 408474b..d6dff0d 100644 --- a/src/intcmp.c +++ b/src/intcmp.c @@ -52,7 +52,7 @@ int main(int argc, char *argv[]){ if(optind + 2 /* ref cmp */ > argc){ usage: fprintf(stderr, - "Usage: %s (-eghl) [integer] [integer...]\n", + "Usage: %s [-egl] integer integer...\n", argv[0] == NULL ? program_name : argv[0]); return EX_USAGE; } diff --git a/src/mm.c b/src/mm.c index dc337b7..e905b35 100644 --- a/src/mm.c +++ b/src/mm.c @@ -110,7 +110,7 @@ oserr(char *s, char *r){ * returns an exit status appropriate for a usage error. */ int usage(char *s){ - fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s); + fprintf(stderr, "Usage: %s [-aenu] [-i input]... [-o output]...\n", s); return EX_USAGE; } diff --git a/src/npc.c b/src/npc.c index 8b97180..1f96668 100644 --- a/src/npc.c +++ b/src/npc.c @@ -39,7 +39,7 @@ int main(int argc, char *argv[]){ } if(argc > optind){ -usage: fprintf(stderr, "Usage: %s (-eht)\n", argv[0]); +usage: fprintf(stderr, "Usage: %s [-et]\n", argv[0]); return EX_USAGE; } diff --git a/src/scrut.c b/src/scrut.c index c5b675f..d85d243 100644 --- a/src/scrut.c +++ b/src/scrut.c @@ -66,7 +66,7 @@ int main(int argc, char *argv[]){ if(ops[i] == 'e') continue; else if(ops[i] == 'h'){ -usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n", +usage: fprintf(stderr, "Usage: %s [-%s] file...\n", argv[0] == NULL ? program_name : argv[0], diff --git a/src/str.c b/src/str.c index ae03b1d..b4725eb 100644 --- a/src/str.c +++ b/src/str.c @@ -56,7 +56,7 @@ int main(int argc, char *argv[]){ goto pass; } - fprintf(stderr, "Usage: %s [type] [string...]\n", + fprintf(stderr, "Usage: %s type string...\n", argv[0] == NULL ? program_name : argv[0]); return EX_USAGE; diff --git a/src/strcmp.c b/src/strcmp.c index acb4946..33b73c2 100644 --- a/src/strcmp.c +++ b/src/strcmp.c @@ -8,7 +8,7 @@ int main(int argc, char *argv[]){ int i; if(argc < 3){ - fprintf(stderr, "Usage: %s [string] [string...]\n", + fprintf(stderr, "Usage: %s string string...\n", argv[0] == NULL ? program_name : argv[0]); return EX_USAGE; } diff --git a/src/swab.rs b/src/swab.rs index d05b651..5942c38 100644 --- a/src/swab.rs +++ b/src/swab.rs @@ -29,13 +29,16 @@ use getopt::GetOpt; extern crate sysexits; use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; +extern crate strerror; +use strerror::StrError; + fn oserr(s: &str, e: Error) -> ExitCode { - eprintln!("{}: {}", s, e); + eprintln!("{}: {}", s, e.strerror()); ExitCode::from(EX_OSERR as u8) } fn usage(s: &str) -> ExitCode { - eprintln!("Usage: {} (-f) (-w [wordsize])", s); + eprintln!("Usage: {} [-f] [-w word_size]", s); ExitCode::from(EX_USAGE as u8) }