diff --git a/Makefile b/Makefile index 76f7be2..024815f 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 @@ -28,6 +31,7 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ --extern strerror=build/o/libstrerror.rlib CFLAGS += -I$(SYSEXITS) +.PHONY: all all: docs dj false fop hru intcmp mm npc peek rpn scrut str strcmp swab true # keep build/include until bindgen(1) has stdin support @@ -39,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 @@ -49,9 +53,12 @@ install: dist cp -r $(DESTDIR)/* / .PHONY: test -test: build +test: build /tmp/getopt + /tmp/getopt tests/posix-compat.sh - $(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt + +/tmp/getopt: src/libgetopt.rs + $(RUSTC) --test -o /tmp/getopt src/libgetopt.rs .PHONY: docs docs: docs/ build @@ -64,21 +71,22 @@ docs: docs/ build rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \ build/o/libstrerror.rlib -build/o/libgetopt.rlib: build src/getopt-rs/lib.rs +build/o/libgetopt.rlib: build src/libgetopt.rs $(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \ - -o $@ src/getopt-rs/lib.rs + -o $@ src/libgetopt.rs -build/o/libstrerror.rlib: build src/strerror.rs +build/o/libstrerror.rlib: build src/libstrerror.rs $(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \ - src/strerror.rs + src/libstrerror.rs -# bandage solution until bindgen(1) gets stdin support -build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h - printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \ - > build/include/sysexits.h +build/o/libsysexits.rlib: build/include/sysexits.h bindgen --default-macro-constant-type signed --use-core --formatter=none \ build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ - +# bandage solution until bindgen(1) gets stdin support +build/include/sysexits.h: build $(SYSEXITS)sysexits.h + printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h > $@ + .PHONY: dj dj: build/bin/dj build/bin/dj: src/dj.c build @@ -101,13 +109,13 @@ build/bin/hru: src/hru.rs build rustlibs .PHONY: intcmp intcmp: build/bin/intcmp -build/bin/intcmp: src/intcmp.c build - $(CC) $(CFLAGS) -o $@ src/intcmp.c +build/bin/intcmp: src/intcmp.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/intcmp.rs .PHONY: mm mm: build/bin/mm -build/bin/mm: src/mm.c build - $(CC) $(CFLAGS) -o $@ src/mm.c +build/bin/mm: src/mm.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/mm.rs .PHONY: npc npc: build/bin/npc @@ -141,10 +149,8 @@ build/bin/strcmp: src/strcmp.c build .PHONY: swab swab: build/bin/swab -build/bin/swab: src/swab.rs build build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ - --extern sysexits=build/o/libsysexits.rlib \ - -o $@ src/swab.rs +build/bin/swab: src/swab.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/swab.rs .PHONY: true true: build/bin/true 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..f641156 100644 --- a/docs/mm.1 +++ b/docs/mm.1 @@ -3,18 +3,16 @@ .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . .\" -.TH MM 1 2024-06-17 "Harakit X.X.X" +.TH MM 1 2024-07-14 "Harakit X.X.X" .SH NAME mm \(en middleman .\" .SH SYNOPSIS mm -.RB ( -aenu ) -.RB ( -i -.RB [ input ]) -.RB ( -o -.RB [ output ]) +.RB [ -aetu ] +.RB [ -i\ input ] +.RB [ -o\ output ] .\" .SH DESCRIPTION @@ -23,19 +21,25 @@ Catenate input files and write them to the start of each output file or stream. .SH OPTIONS .IP \fB-a\fP -Opens subsequent outputs for appending rather than updating. +Opens outputs for appending rather than updating. .IP \fB-e\fP Use the standard error as an output. -.IP \fB-i\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 -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-t\fP +Causes outputs to be overwritten instead of being truncated. .IP \fB-u\fP Ensures neither input or output will be buffered. -.IP \fB-n\fP -Causes SIGINT signals to be ignored. +.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. If specified as the +last option and if there are trailing arguments to the program, they shall be +appended to the list of files to use as inputs. +.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 and the +.B -e +option is not specified, the standard output shall be used. If specified as the +last option and if there are trailing arguments to the program, they shall be +appended to the list of files to use as outputs. .\" .SH DIAGNOSTICS @@ -47,10 +51,6 @@ exits with the appropriate .BR sysexits.h (3) status. .\" -.SH CAVEATS - -Existing files are not truncated on ouput and are instead overwritten. -.\" .SH RATIONALE The 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..db6d3e8 100644 --- a/docs/strcmp.1 +++ b/docs/strcmp.1 @@ -4,15 +4,14 @@ .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . .\" -.TH STRCMP 1 2024-06-17 "Harakit X.X.X" +.TH STRCMP 1 2024-07-15 "Harakit X.X.X" .SH NAME strcmp \(en compare strings .\" .SH SYNOPSIS strcmp -.RM [ string ] -.RB [ strings... ] +.B string string... .\" .SH DESCRIPTION @@ -21,15 +20,15 @@ Check whether string arguments are the same. .SH DIAGNOSTICS The program will exit successfully if the strings are identical. Otherwise, it -will exit with an error code of 1 if a string passed has a lesser byte value -than one of the prior strings: +will exit with an error code less than 128 if a string passed has a lesser byte +value than one of the prior strings: .RS strcmp b a .RE -or with an error code of 255 if it has a greater byte value than one of the -prior strings: +or with an error code greater than 128 if it has a greater byte value than one +of the prior strings: .RS strcmp a b diff --git a/docs/swab.1 b/docs/swab.1 index 72f0f19..7c50b15 100644 --- a/docs/swab.1 +++ b/docs/swab.1 @@ -11,11 +11,7 @@ swab \(en swap bytes .SH SYNOPSIS swab -.RB ( -f ) -.RB ( -w -.R [ -.B word size -.R ]) +.RB [ -w\ word_size ] .\" .SH DESCRIPTION @@ -23,13 +19,10 @@ Swap the latter and former halves of a block of bytes. .\" .SH OPTIONS -.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..3b5bc5f 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,212 @@ 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 */ + long skipping; + size_t t; + + /* hack to intentionally get a partial read from Io_read */ + if((skipping = MIN(io[0].seek, io[0].bs)) > 0) + io[0].bufuse = io[0].bs - (size_t)skipping; + + 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 > 0){ + io[0].seek -= 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/fop.rs b/src/fop.rs index 5244300..91c8a72 100644 --- a/src/fop.rs +++ b/src/fop.rs @@ -26,27 +26,39 @@ extern crate getopt; extern crate strerror; extern crate sysexits; -use getopt::{ Opt, Parser }; +use getopt::GetOpt; use strerror::StrError; use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; fn main() { let argv = args().collect::>(); let mut d = '\u{1E}'.to_string(); - let mut arg_parser = Parser::new(&argv, "d:"); + let mut index_arg = 0; - while let Some(opt) = arg_parser.next() { - match opt { - Ok(Opt('d', Some(arg))) => d = arg, - _ => {}, + let usage = format!( + "Usage: {} [-d delimiter] index command [args...]", + argv[0], + ); + + while let Some(opt) = argv.getopt("d:") { + match opt.opt() { + Ok(_) => { + /* unwrap because Err(OptError::MissingArg) will be returned if + * opt.arg() is None */ + d = opt.arg().unwrap(); + index_arg = opt.ind(); + }, + Err(_) => { + eprintln!("{}", usage); + exit(EX_USAGE); + } }; } - let index_arg = arg_parser.index(); - let command_arg = arg_parser.index() + 1; + let command_arg = index_arg as usize + 1; argv.get(command_arg).unwrap_or_else(|| { - eprintln!("Usage: {} [-d delimiter] index command [args...]", argv[0]); + eprintln!("{}", usage); exit(EX_USAGE); }); diff --git a/src/getopt-rs/error.rs b/src/getopt-rs/error.rs deleted file mode 100644 index 322af02..0000000 --- a/src/getopt-rs/error.rs +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::{ error, fmt }; - -use crate::ErrorKind::{ self, * }; - -/// A basic error type for [`Parser`](struct.Parser.html) -#[derive(Debug, Eq, PartialEq)] -pub struct Error { - culprit: char, - kind: ErrorKind, -} - -impl Error { - /// Creates a new error using a known kind and the character that caused the - /// issue. - pub fn new(kind: ErrorKind, culprit: char) -> Self { - Self { culprit, kind } - } - - /// Returns the [`ErrorKind`](enum.ErrorKind.html) for this error. - pub fn kind(self) -> ErrorKind { - self.kind - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.kind { - MissingArgument => write!( - f, - "option requires an argument -- {:?}", - self.culprit, - ), - UnknownOption => write!(f, "unknown option -- {:?}", self.culprit), - } - } -} - -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - None - } -} diff --git a/src/getopt-rs/errorkind.rs b/src/getopt-rs/errorkind.rs deleted file mode 100644 index 5475d8e..0000000 --- a/src/getopt-rs/errorkind.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/// What kinds of errors [`Parser`](struct.Parser.html) can return. -#[derive(Debug, Eq, PartialEq)] -pub enum ErrorKind { - /// An argument was not found for an option that was expecting one. - MissingArgument, - /// An unknown option character was encountered. - UnknownOption, -} diff --git a/src/getopt-rs/lib.rs b/src/getopt-rs/lib.rs deleted file mode 100644 index 62f0e0d..0000000 --- a/src/getopt-rs/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -//! # getopt -//! -//! `getopt` provides a minimal, (essentially) POSIX-compliant option parser. - -pub use crate::{ - error::Error, - errorkind::ErrorKind, - opt::Opt, - parser::Parser, - result::Result -}; - -mod error; -mod errorkind; -mod opt; -mod parser; -mod result; -#[cfg(test)] -mod tests; diff --git a/src/getopt-rs/opt.rs b/src/getopt-rs/opt.rs deleted file mode 100644 index 05b51e6..0000000 --- a/src/getopt-rs/opt.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::fmt; - -/// A single option. -/// -/// For `Opt(x, y)`: -/// - `x` is the character representing the option. -/// - `y` is `Some` string, or `None` if no argument was expected. -/// -/// # Example -/// -/// ``` -/// # fn main() -> Result<(), Box> { -/// use getopt::Opt; -/// -/// // args = ["program", "-abc", "foo"]; -/// # let args: Vec = vec!["program", "-abc", "foo"] -/// # .into_iter() -/// # .map(String::from) -/// # .collect(); -/// let optstring = "ab:c"; -/// let mut opts = getopt::Parser::new(&args, optstring); -/// -/// assert_eq!(Opt('a', None), opts.next().transpose()?.unwrap()); -/// assert_eq!(Opt('b', Some("c".to_string())), opts.next().transpose()?.unwrap()); -/// assert_eq!(None, opts.next().transpose()?); -/// # Ok(()) -/// # } -/// ``` -#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Opt(pub char, pub Option); - -impl fmt::Display for Opt { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Opt({:?}, {:?})", self.0, self.1) - } -} diff --git a/src/getopt-rs/parser.rs b/src/getopt-rs/parser.rs deleted file mode 100644 index 6f06cc3..0000000 --- a/src/getopt-rs/parser.rs +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::collections::HashMap; - -use crate::{ error::Error, errorkind::ErrorKind, opt::Opt, result::Result }; - -/// The core of the `getopt` crate. -/// -/// `Parser` is implemented as an iterator over the options present in the given -/// argument vector. -/// -/// The method [`next`](#method.next) does the heavy lifting. -/// -/// # Examples -/// -/// ## Simplified usage: -/// ``` -/// # fn main() -> Result<(), Box> { -/// use getopt::Opt; -/// -/// // args = ["program", "-abc", "foo"]; -/// # let args: Vec = vec!["program", "-abc", "foo"] -/// # .into_iter() -/// # .map(String::from) -/// # .collect(); -/// let mut opts = getopt::Parser::new(&args, "ab:c"); -/// -/// assert_eq!(Some(Opt('a', None)), opts.next().transpose()?); -/// assert_eq!(1, opts.index()); -/// assert_eq!(Some(Opt('b', Some("c".to_string()))), opts.next().transpose()?); -/// assert_eq!(2, opts.index()); -/// assert_eq!(None, opts.next()); -/// assert_eq!(2, opts.index()); -/// assert_eq!("foo", args[opts.index()]); -/// # Ok(()) -/// # } -/// ``` -/// -/// ## A more idiomatic example: -/// ``` -/// # fn main() -> Result<(), Box> { -/// use getopt::Opt; -/// -/// // args = ["program", "-abc", "-d", "foo", "-e", "bar"]; -/// # let mut args: Vec = vec!["program", "-abc", "-d", "foo", "-e", "bar"] -/// # .into_iter() -/// # .map(String::from) -/// # .collect(); -/// let mut opts = getopt::Parser::new(&args, "ab:cd:e"); -/// -/// let mut a_flag = false; -/// let mut b_flag = String::new(); -/// let mut c_flag = false; -/// let mut d_flag = String::new(); -/// let mut e_flag = false; -/// -/// loop { -/// match opts.next().transpose()? { -/// None => break, -/// Some(opt) => match opt { -/// Opt('a', None) => a_flag = true, -/// Opt('b', Some(arg)) => b_flag = arg.clone(), -/// Opt('c', None) => c_flag = true, -/// Opt('d', Some(arg)) => d_flag = arg.clone(), -/// Opt('e', None) => e_flag = true, -/// _ => unreachable!(), -/// }, -/// } -/// } -/// -/// let new_args = args.split_off(opts.index()); -/// -/// assert_eq!(true, a_flag); -/// assert_eq!("c", b_flag); -/// assert_eq!(false, c_flag); -/// assert_eq!("foo", d_flag); -/// assert_eq!(true, e_flag); -/// -/// assert_eq!(1, new_args.len()); -/// assert_eq!("bar", new_args.first().unwrap()); -/// # Ok(()) -/// # } -/// ``` -#[derive(Debug, Eq, PartialEq)] -pub struct Parser { - opts: HashMap, - args: Vec>, - index: usize, - point: usize, -} - -impl Parser { - /// Create a new `Parser`, which will process the arguments in `args` - /// according to the options specified in `optstring`. - /// - /// For compatibility with - /// [`std::env::args`](https://doc.rust-lang.org/std/env/fn.args.html), - /// valid options are expected to begin at the second element of `args`, and - /// `index` is - /// initialised to `1`. - /// If `args` is structured differently, be sure to call - /// [`set_index`](#method.set_index) before the first invocation of - /// [`next`](#method.next). - /// - /// `optstring` is a string of recognised option characters; if a character - /// is followed by a colon (`:`), that option takes an argument. - /// - /// # Note: - /// Transforming the OS-specific argument strings into a vector of `String`s - /// is the sole responsibility of the calling program, as it involves some - /// level of potential information loss (which this crate does not presume - /// to handle unilaterally) and error handling (which would complicate the - /// interface). - pub fn new(args: &[String], optstring: &str) -> Self { - let optstring: Vec = optstring.chars().collect(); - let mut opts = HashMap::new(); - let mut i = 0; - let len = optstring.len(); - - while i < len { - let j = i + 1; - - if j < len && optstring[j] == ':' { - opts.insert(optstring[i], true); - i += 1; - } else { - opts.insert(optstring[i], false); - } - i += 1; - } - - Self { - opts, - // "explode" the args into a vector of character vectors, to allow - // indexing - args: args.iter().map(|e| e.chars().collect()).collect(), - index: 1, - point: 0, - } - } - - /// Return the current `index` of the parser. - /// - /// `args[index]` will always point to the the next element of `args`; when - /// the parser is - /// finished with an element, it will increment `index`. - /// - /// After the last option has been parsed (and [`next`](#method.next) is - /// returning `None`), - /// `index` will point to the first non-option argument. - pub fn index(&self) -> usize { - self.index - } - - // `point` must be reset to 0 whenever `index` is changed - - /// Modify the current `index` of the parser. - pub fn set_index(&mut self, value: usize) { - self.index = value; - self.point = 0; - } - - /// Increment the current `index` of the parser. - /// - /// This use case is common enough to warrant its own optimised method. - pub fn incr_index(&mut self) { - self.index += 1; - self.point = 0; - } -} - -impl Iterator for Parser { - type Item = Result; - - /// Returns the next option, if any. - /// - /// Returns an [`Error`](struct.Error.html) if an unexpected option is - /// encountered or if an - /// expected argument is not found. - /// - /// Parsing stops at the first non-hyphenated argument; or at the first - /// argument matching "-"; - /// or after the first argument matching "--". - /// - /// When no more options are available, `next` returns `None`. - /// - /// # Examples - /// - /// ## "-" - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "-", "-a"]; - /// # let args: Vec = vec!["program", "-", "-a"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a"); - /// - /// assert_eq!(None, opts.next()); - /// assert_eq!("-", args[opts.index()]); - /// ``` - /// - /// ## "--" - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "--", "-a"]; - /// # let args: Vec = vec!["program", "--", "-a"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a"); - /// - /// assert_eq!(None, opts.next()); - /// assert_eq!("-a", args[opts.index()]); - /// ``` - /// - /// ## Unexpected option: - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "-b"]; - /// # let args: Vec = vec!["program", "-b"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a"); - /// - /// assert_eq!( - /// "unknown option -- 'b'".to_string(), - /// opts.next().unwrap().unwrap_err().to_string() - /// ); - /// ``` - /// - /// ## Missing argument: - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "-a"]; - /// # let args: Vec = vec!["program", "-a"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a:"); - /// - /// assert_eq!( - /// "option requires an argument -- 'a'".to_string(), - /// opts.next().unwrap().unwrap_err().to_string() - /// ); - /// ``` - fn next(&mut self) -> Option> { - if self.point == 0 { - /* - * Rationale excerpts below taken verbatim from "The Open Group Base - * Specifications Issue 7, 2018 edition", IEEE Std 1003.1-2017 - * (Revision of IEEE Std 1003.1-2008). - * Copyright © 2001-2018 IEEE and The Open Group. - */ - - /* - * If, when getopt() is called: - * argv[optind] is a null pointer - * *argv[optind] is not the character '-' - * argv[optind] points to the string "-" - * getopt() shall return -1 without changing optind. - */ - if self.index >= self.args.len() - || self.args[self.index].is_empty() - || self.args[self.index][0] != '-' - || self.args[self.index].len() == 1 - { - return None; - } - - /* - * If: - * argv[optind] points to the string "--" - * getopt() shall return -1 after incrementing index. - */ - if self.args[self.index][1] == '-' && self.args[self.index].len() == 2 { - self.incr_index(); - return None; - } - - // move past the starting '-' - self.point += 1; - } - - let opt = self.args[self.index][self.point]; - self.point += 1; - - match self.opts.get(&opt) { - None => { - if self.point >= self.args[self.index].len() { - self.incr_index(); - } - Some(Err(Error::new(ErrorKind::UnknownOption, opt))) - } - Some(false) => { - if self.point >= self.args[self.index].len() { - self.incr_index(); - } - - Some(Ok(Opt(opt, None))) - } - Some(true) => { - let arg: String = if self.point >= self.args[self.index].len() { - self.incr_index(); - if self.index >= self.args.len() { - return Some(Err(Error::new( - ErrorKind::MissingArgument, - opt, - ))); - } - self.args[self.index].iter().collect() - } else { - self.args[self.index] - .clone() - .split_off(self.point) - .iter() - .collect() - }; - - self.incr_index(); - - Some(Ok(Opt(opt, Some(arg)))) - } - } - } -} diff --git a/src/getopt-rs/result.rs b/src/getopt-rs/result.rs deleted file mode 100644 index 015a402..0000000 --- a/src/getopt-rs/result.rs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::result; - -use crate::error::Error; - -/// A specialized `Result` type for use with [`Parser`](struct.Parser.html) -pub type Result = result::Result; diff --git a/src/getopt-rs/tests.rs b/src/getopt-rs/tests.rs deleted file mode 100644 index c53d517..0000000 --- a/src/getopt-rs/tests.rs +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use crate::{Opt, Parser}; - -macro_rules! basic_test { - ($name:ident, $expect:expr, $next:expr, [$($arg:expr),+], $optstr:expr) => ( - #[test] - fn $name() -> Result<(), String> { - let expect: Option = $expect; - let args: Vec = vec![$($arg),+] - .into_iter() - .map(String::from) - .collect(); - let next: Option = $next; - let mut opts = Parser::new(&args, $optstr); - - match opts.next().transpose() { - Err(error) => { - return Err(format!("next() returned {:?}", error)) - }, - Ok(actual) => if actual != expect { - return Err( - format!("expected {:?}; got {:?}", expect, actual) - ) - }, - }; - - match next { - None => if opts.index() < args.len() { - return Err(format!( - "expected end of args; got {:?}", args[opts.index()] - )) - }, - Some(n) => if args[opts.index()] != n { - return Err(format!( - "next arg: expected {:?}; got {:?}", - n, - args[opts.index()] - )) - }, - }; - - Ok(()) - } - ) -} - -#[rustfmt::skip] basic_test!( - blank_arg, None, Some(String::new()), ["x", ""], "a" -); -#[rustfmt::skip] basic_test!( - double_dash, None, Some("-a".to_string()), ["x", "--", "-a", "foo"], "a" -); -#[rustfmt::skip] basic_test!(no_opts_1, None, None, ["x"], "a"); -#[rustfmt::skip] basic_test!( - no_opts_2, None, Some("foo".to_string()), ["x", "foo"], "a" -); -#[rustfmt::skip] basic_test!( - no_opts_3, None, Some("foo".to_string()), ["x", "foo", "-a"], "a" -); -#[rustfmt::skip] basic_test!( - single_dash, None, Some("-".to_string()), ["x", "-", "-a", "foo"], "a" -); -#[rustfmt::skip] basic_test!( - single_opt, - Some(Opt('a', None)), - Some("foo".to_string()), - ["x", "-a", "foo"], - "a" -); -#[rustfmt::skip] basic_test!( - single_optarg, - Some(Opt('a', Some("foo".to_string()))), - None, - ["x", "-a", "foo"], - "a:" -); - -macro_rules! error_test { - ($name:ident, $expect:expr, [$($arg:expr),+], $optstr:expr) => ( - #[test] - fn $name() -> Result<(), String> { - let expect: String = $expect.to_string(); - let args: Vec = vec![$($arg),+] - .into_iter() - .map(String::from) - .collect(); - let mut opts = Parser::new(&args, $optstr); - - match opts.next() { - None => { - return Err(format!( - "unexpected successful response: end of options" - )) - }, - Some(Err(actual)) => { - let actual = actual.to_string(); - - if actual != expect { - return Err( - format!("expected {:?}; got {:?}", expect, actual) - ); - } - }, - Some(Ok(opt)) => { - return Err( - format!("unexpected successful response: {:?}", opt) - ) - }, - }; - - Ok(()) - } - ) -} - -#[rustfmt::skip] error_test!( - bad_opt, - "unknown option -- 'b'", - ["x", "-b"], - "a" -); - -#[rustfmt::skip] error_test!( - missing_optarg, - "option requires an argument -- 'a'", - ["x", "-a"], - "a:" -); - -#[test] -fn multiple() -> Result<(), String> { - let args: Vec = vec!["x", "-abc", "-d", "foo", "-e", "bar"] - .into_iter() - .map(String::from) - .collect(); - let optstring = "ab:d:e".to_string(); - let mut opts = Parser::new(&args, &optstring); - - macro_rules! check_result { - ($expect:expr) => { - let expect: Option = $expect; - match opts.next().transpose() { - Err(error) => { - return Err(format!("next() returned {:?}", error)); - }, - Ok(actual) => { - if actual != expect { - return Err( - format!("expected {:?}; got {:?}", expect, actual) - ); - } - } - }; - }; - } - - check_result!(Some(Opt('a', None))); - check_result!(Some(Opt('b', Some("c".to_string())))); - check_result!(Some(Opt('d', Some("foo".to_string())))); - check_result!(Some(Opt('e', None))); - check_result!(None); - - Ok(()) -} - -#[test] -fn continue_after_error() { - let args: Vec = vec!["x", "-z", "-abc"] - .into_iter() - .map(String::from) - .collect(); - let optstring = "ab:d:e".to_string(); - for _opt in Parser::new(&args, &optstring) { - // do nothing, should not panic - } -} diff --git a/src/intcmp.c b/src/intcmp.c deleted file mode 100644 index 408474b..0000000 --- a/src/intcmp.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2023 DTB - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include /* errno */ -#include /* fprintf(3), stderr */ -#include /* strtol(3), size_t, EXIT_FAILURE */ -#include /* getopt(3), optind */ -#include - -/* 0b00? */ /* Equal | -e | 0b001 | 1 */ -#define EQUAL 0x01 /* Greater | -g | 0b010 | 2 */ -/* 0b0?0 */ /* Greater or Equal | -ge | 0b011 | 3 */ -#define GREATER 0x02 /* Less | -l | 0b100 | 4 */ -/* 0b?00 */ /* Less or Equal | -le | 0b101 | 5 */ -#define LESS 0x04 /* Inequal (Greater or Less) | -gl | 0b110 | 6 */ - -static char *program_name = "intcmp"; - -int main(int argc, char *argv[]){ - int c; - size_t i; - unsigned char mode; - int r; /* reference integer */ - - mode = 0; - - if(argc < 3) - goto usage; - - while((c = getopt(argc, argv, "egl")) != -1) - switch(c){ - case 'e': mode |= EQUAL; break; - case 'g': mode |= GREATER; break; - case 'l': mode |= LESS; break; - default: goto usage; - } - - if(optind + 2 /* ref cmp */ > argc){ -usage: fprintf(stderr, - "Usage: %s (-eghl) [integer] [integer...]\n", - argv[0] == NULL ? program_name : argv[0]); - return EX_USAGE; - } - - i = optind; - - do{ r = c; - c = strtol(argv[i], &argv[i], 10); - if(*argv[i] != '\0' || errno != 0){ - fprintf(stderr, "%s: argument #%d: Invalid integer\n", - argv[0], (int)i); - return EX_USAGE; - } - - if(i == optind) - continue; - - /* rule enforcement; if a mode isn't permitted and the numbers - * correspond to it, return 1 */ - if( (!(mode & EQUAL) && r == c) - || (!(mode & GREATER) && r > c) - || (!(mode & LESS) && r < c)) - return 1; - }while(++i < argc); - - return 0; -} diff --git a/src/intcmp.rs b/src/intcmp.rs new file mode 100644 index 0000000..f504b50 --- /dev/null +++ b/src/intcmp.rs @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023–2024 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 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 Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +use std::{ + env::args, + process::ExitCode +}; + +extern crate getopt; +use getopt::GetOpt; + +extern crate sysexits; +use sysexits::EX_USAGE; + +fn usage(s: &str) -> ExitCode { + eprintln!("Usage: {} [-egl] integer integer...", s); + ExitCode::from(EX_USAGE as u8) +} + +fn main() -> ExitCode { + let argv = args().collect::>(); + let mut e = false; /* args can be == */ + let mut g = false; /* args can be > */ + let mut l = false; /* args can be < */ + let mut optind = 0; + + if argv.len() < 3 { return usage(&argv[0]); } + + while let Some(opt) = argv.getopt("egl") { + match opt.opt() { + Ok("e") => e = true, + Ok("g") => g = true, + Ok("l") => l = true, + _ => { return usage(&argv[0]); }, + } + optind = opt.ind(); + } + + if argv.len() - optind < 2 /* see usage */ { return usage(&argv[0]); } + + let mut prev: Option = None; /* no previous operand */ + let mut currn: usize; + + for arg in argv.iter().skip(optind) { /* iterate operands */ + match arg.parse::() { /* parse current operand */ + Ok(n) => currn = n, + _ => { + eprintln!("{}: {}: Invalid integer", &argv[0], arg); + return ExitCode::from(EX_USAGE as u8); + } + } + + if let Some(prevn) = prev { /* if there was a previous opr., test */ + if (!e && prevn == currn) + || (!g && prevn > currn) + || (!l && prevn < currn) + { return ExitCode::FAILURE; } + } + + prev = Some(currn); /* there is a previous operand */ + } + + ExitCode::SUCCESS +} diff --git a/src/libgetopt.rs b/src/libgetopt.rs new file mode 100644 index 0000000..8064c81 --- /dev/null +++ b/src/libgetopt.rs @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023–2024 Emma Tebibyte + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 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 Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +use std::ffi::{ c_int, c_char, CString, CStr }; + +/* binding to getopt(3p) */ +extern "C" { + static mut optarg: *mut c_char; + static mut _opterr: c_int; + static mut optind: c_int; + static mut optopt: c_int; + + fn getopt( + ___argc: c_int, + ___argv: *const *mut c_char, + __shortopts: *const c_char, + ) -> c_int; +} + +#[derive(Clone, Debug)] +pub enum OptError { + MissingArg(String), + UnknownOpt(String), +} + +#[derive(Clone, Debug)] +pub struct Opt { + arg: Option, /* option argument */ + ind: *mut i32, /* option index */ + opt: Result, /* option option */ +} + +impl Opt { + pub fn arg(&self) -> Option { self.arg.clone() } + + /* sets optarg if default is desired */ + pub fn arg_or(&self, default: impl std::fmt::Display) -> String { + default.to_string() + } + + /* makes matching the output of this method more bearable */ + pub fn opt(&self) -> Result<&str, OptError> { + self.opt.as_ref().map(|o| o.as_str()).map_err(OptError::clone) + } + + /* From getopt(3p): + * + * The variable optind is the index of the next element of the argv[] + * vector to be processed. It shall be initialized to 1 by the system, and + * getopt() shall update it when it finishes with each element of argv[]. + * If the application sets optind to zero before calling getopt(), the + * behavior is unspecified. When an element of argv[] contains multiple + * option characters, it is unspecified how getopt() determines which + * options have already been processed. */ + pub fn ind(&self) -> usize { unsafe { *self.ind as usize } } + + /* this is patently terrible and is only happening because I’m stubborn */ + pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } } +} + +/* function signature */ +pub trait GetOpt { + fn getopt(&self, optstring: &str) -> Option; +} + +impl GetOpt for Vec { + fn getopt(&self, optstring: &str) -> Option { + let c_strings: Vec<_> = self + .iter() + .cloned() + .map(|x| CString::new(x).unwrap().into_raw()) + .collect(); + + /* god knows what this does */ + let boxed = Box::into_raw(c_strings.into_boxed_slice()); + let argv = boxed as *const *mut c_char; + + /* operations are separated out so that everything lives long enough */ + let opts = CString::new(optstring).unwrap().into_raw(); + let len = self.len() as c_int; + + unsafe { + let ret = match getopt(len, argv, opts) { + /* From getopt(3p): + * + * The getopt() function shall return the next option character + * specified on the command line. + * + * A (':') shall be returned if getopt() detects a + * missing argument and the first character of optstring was a + * (':'). + * + * A ('?') shall be returned if getopt() + * encounters an option character not in optstring or detects a + * missing argument and the first character of optstring was not + * a (':'). + * + * Otherwise, getopt() shall return -1 when all command line + * options are parsed. */ + 58 => { /* ASCII ':' */ + Some(Opt { + arg: None, + ind: std::ptr::addr_of_mut!(optind), + /* error containing option */ + opt: Err(OptError::MissingArg(optopt.to_string())), + }) + }, + 63 => { /* ASCII '?' */ + Some(Opt { + arg: None, + ind: std::ptr::addr_of_mut!(optind), + /* error containing option */ + opt: Err(OptError::UnknownOpt(optopt.to_string())), + }) + }, + /* From getopt(3p): + * + * If, when getopt() is called: + * + * argv[optind] is a null pointer + * *argv[optind] is not the character - + * argv[optind] points to the string "-" + * + * getopt() shall return -1 without changing optind. If: + * + * argv[optind] points to the string "--" + * + * getopt() shall return -1 after incrementing optind. */ + -1 => return None, + opt => { + let arg: Option; + + if optarg.is_null() { arg = None; } + else { + arg = Some(CStr::from_ptr(optarg) + .to_string_lossy() + .into_owned()); + } + + Some(Opt { + arg, + ind: std::ptr::addr_of_mut!(optind), + /* I didn’t need to cast this before; I rewrote the + * pointer logic and now I do + * + * I don’t know why this is */ + opt: Ok((opt as u8 as char).to_string()), + }) + }, + }; + + /* delloc argv (something online said I should do this) */ + let _ = Box::from_raw(boxed); + return ret; + } + } +} + +/* tests (good) */ +#[cfg(test)] +mod tests { + use GetOpt; + + #[test] + fn testing() { + let argv: Vec = ["test", "-b", "-f", "arg", "-o", "arg"] + .iter() + .map(|s| s.to_string()) + .collect(); + + while let Some(opt) = argv.getopt(":abf:o:") { + match opt.opt() { + Ok("a") => assert_eq!(opt.ind(), 1), + Ok("b") => assert_eq!(opt.ind(), 2), + Ok("f") | Ok("o") => { + assert_eq!(opt.arg(), Some("arg".into())); + }, + _ => assert!(false), + }; + } + + if let Some(opt) = argv.getopt("abc:") { + opt.clone().set_ind(1); + assert_eq!(opt.ind(), 1); + } + } +} diff --git a/src/strerror.rs b/src/libstrerror.rs similarity index 100% rename from src/strerror.rs rename to src/libstrerror.rs diff --git a/src/mm.c b/src/mm.c deleted file mode 100644 index dc337b7..0000000 --- a/src/mm.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2024 DTB - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 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 Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include /* errno */ -#include /* signal(2), SIG_ERR, SIG_IGN, SIGINT */ -#include /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3), - * setvbuf(3), size_t, _IONBF, NULL */ -#include /* free(3), realloc(3) */ -#include /* strcmp(3), strerror(3) */ -#include /* getopt(3) */ -#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \ - || !defined EX_USAGE -# include -#endif -extern int errno; - -/* This structure is how open files are tracked. */ -struct Files{ - size_t a; /* allocation */ - size_t s; /* used size */ - char *mode; /* file opening mode */ - char **names; /* file names */ - FILE **files; /* file pointers */ -}; - -/* How much to grow the allocation when it's saturated. */ -#ifndef ALLOC_INCREMENT -# define ALLOC_INCREMENT 1 -#endif - -/* How much to grow the allocation at program start. */ -#ifndef ALLOC_INITIAL -# define ALLOC_INITIAL 10 -#endif - -/* pre-allocated strings */ -static char *program_name = ""; -static char *stdin_name = ""; -static char *stdout_name = ""; -static char *stderr_name = ""; -static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} }; -static char *wharsh = "wb"; - -/* Adds the open FILE pointer for the file at the path s to the files struct, - * returning the FILE if successful and NULL if not, allocating more memory in - * the files buffers as needed. */ -static FILE * -Files_append(struct Files *files, FILE *file, char *name){ - - if(file == NULL || (files->s == files->a - && ((files->files = realloc(files->files, - (files->a += (files->a == 0) - ? ALLOC_INITIAL - : ALLOC_INCREMENT) - * sizeof *(files->files))) == NULL - || (files->names = realloc(files->names, - files->a * sizeof *(files->names))) == NULL))) - return NULL; - - files->names[files->s] = name; - return files->files[files->s++] = file; -} - -/* Opens the file at the path p and puts it in the files struct, returning NULL - * if either the opening or the placement of the open FILE pointer fail. */ -#define Files_open(files, p) \ - Files_append((files), fopen((p), (files)->mode), (p)) - -/* Prints a diagnostic message based on errno and returns an exit status - * appropriate for an OS error. */ -static int -oserr(char *s, char *r){ - - fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno)); - - return EX_OSERR; -} - -/* Hijacks i and j from main and destructs the files[2] struct used by main by - * closing its files and freeing its files and names arrays, returning retval - * from main. */ -#define terminate \ - for(i = 0; i < 2; ++i){ \ - for(j = 0; j < files[i].s; ++j) \ - if(files[i].files[j] != stdin \ - && files[i].files[j] != stdout \ - && files[i].files[j] != stderr) \ - fclose(files[i].files[j]); \ - free(files[i].files); \ - free(files[i].names); \ - } \ - return retval - -/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and - * returns an exit status appropriate for a usage error. */ -int usage(char *s){ - - fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s); - - return EX_USAGE; -} - -int main(int argc, char *argv[]){ - int c; - struct Files files[2]; /* {read, write} */ - size_t i; - size_t j; - size_t k; /* loop index but also unbuffer status */ - int retval; - - /* Initializes the files structs with their default values, standard - * input and standard output. If an input or an output is specified - * these initial values will be overwritten, so to, say, use mm(1) - * equivalently to tee(1p), -o - will need to be specified before - * additional files to ensure standard output is still written. */ - for(i = 0; i < 2; ++i){ - files[i].a = 0; - files[i].s = 0; - files[i].mode = fmode[i]; - files[i].files = NULL; - files[i].names = NULL; - Files_append(&files[i], i == 0 ? stdin : stdout, - i == 0 ? stdin_name : stdout_name); - files[i].s = 0; - } - - k = 0; - - if(argc > 0) - program_name = argv[0]; - - if(argc > 1) - while((c = getopt(argc, argv, "aehi:no:u")) != -1) - switch(c){ - case 'a': /* "rb+" -> "ab" */ - files[1].mode[0] = 'a'; - files[1].mode[2] = '\0'; - break; - case 'e': - if(Files_append(&files[1], stderr, stderr_name) != NULL) - break; - retval = oserr(argv[0], "-e"); - terminate; - case 'i': - if((strcmp(optarg, "-") == 0 && Files_append(&files[0], - stdin, stdin_name) != NULL) - || Files_open(&files[0], optarg) != NULL) - break; - retval = oserr(argv[0], optarg); - terminate; - case 'o': - if((strcmp(optarg, "-") == 0 && Files_append(&files[1], - stdout, stdout_name) != NULL) - || Files_open(&files[1], optarg) != NULL) - break; - /* does not exist, so try to create it */ - if(errno == ENOENT){ - files[1].mode = wharsh; - if(Files_open(&files[1], optarg) != NULL){ - files[1].mode = fmode[1]; - break; - } - } - retval = oserr(argv[0], optarg); - terminate; - case 'n': - if(signal(SIGINT, SIG_IGN) != SIG_ERR) - break; - retval = oserr(argv[0], "-n"); - terminate; - case 'u': - k = 1; - break; - default: - retval = usage(argv[0]); - terminate; - } - - if(optind != argc){ - retval = usage(argv[0]); - terminate; - } - - files[0].s += files[0].s == 0; - files[1].s += files[1].s == 0; - - /* Unbuffer files. */ - if(k){ - for(i = 0; - i < files[0].s; - setvbuf(files[0].files[i++], NULL, _IONBF, 0)); - for(i = 0; - i < files[1].s; - setvbuf(files[1].files[i++], NULL, _IONBF, 0)); - } - - retval = EX_OK; - - /* Actual program loop. */ - for(i = 0; i < files[0].s; ++i) /* iterate ins */ - while((c = getc(files[0].files[i])) != EOF) /* iterate chars */ - for(j = 0; j < files[1].s; ++j) /* iterate outs */ - if(putc(c, files[1].files[j]) == EOF){ - /* notebook's full */ - retval = EX_IOERR; - fprintf(stderr, "%s: %s: %s\n", - program_name, files[1].names[j], strerror(errno)); - if(fclose(files[1].files[j]) == EOF) - fprintf(stderr, "%s: %s: %s\n", - program_name, files[1].names[j], strerror(errno)); - /* massage out the tense muscle */ - for(k = j--; k < files[1].s - 1; ++k){ - files[1].files[k] = files[1].files[k+1]; - files[1].names[k] = files[1].names[k+1]; - } - if(--files[1].s == 0) - terminate; - } - - terminate; -} diff --git a/src/mm.rs b/src/mm.rs new file mode 100644 index 0000000..41b7857 --- /dev/null +++ b/src/mm.rs @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2024 Emma Tebibyte + * Copyright (c) 2024 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 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 Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +use std::{ + env::args, + fs::File, + io::{ stdin, stdout, stderr, BufWriter, Read, Write }, + os::fd::{ AsRawFd, FromRawFd }, + process::{ exit, ExitCode }, +}; + +extern crate getopt; +extern crate strerror; +extern crate sysexits; + +use getopt::GetOpt; +use strerror::StrError; +use sysexits::{ EX_IOERR, EX_USAGE }; + +use ArgMode::*; + +enum ArgMode { In, Out } + +fn main() -> ExitCode { + let argv = args().collect::>(); + let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]); + + let mut a = false; /* append to the file */ + let mut e = false; /* use stderr as an output */ + let mut t = true; /* do not truncate the file before writing */ + let mut u = false; /* unbuffer i/o */ + let mut ins = Vec::new(); /* initial input file path vector */ + let mut outs = Vec::new(); /* initial output file path vector */ + let mut mode: Option = None; /* mode set by last-used option */ + let mut optind = 0; + + while let Some(opt) = argv.getopt("aei:o:tu") { + match opt.opt() { + Ok("a") => a = true, + Ok("e") => e = true, + Ok("u") => u = true, + Ok("t") => t = false, + Ok("i") => { /* add inputs */ + let input = opt.arg().unwrap(); + ins.push(input); + mode = Some(In); /* latest argument == -i */ + }, + Ok("o") => { /* add output */ + let output = opt.arg().unwrap(); + outs.push(output); + mode = Some(Out); /* latest argument == -o */ + }, + Err(_) | Ok(_) => { + eprintln!("{}", usage); + return ExitCode::from(EX_USAGE as u8); + }, + }; + + optind = opt.ind(); + } + + let remaining = argv.iter().skip(optind); + + /* check the last flag specified */ + if let Some(m) = mode { + for arg in remaining { + /* move the subsequent arguments to the list of inputs or outputs */ + match m { + In => ins.push(arg.to_string()), + Out => outs.push(arg.to_string()), + }; + } + } else { + eprintln!("{}", usage); + return ExitCode::from(EX_USAGE as u8); + } + + /* use stdin if no inputs are specified */ + if ins.is_empty() { ins.push("-".to_string()); } + + /* use stdout if no outputs are specified */ + if outs.is_empty() && !e { outs.push("-".to_string()); } + + /* map all path strings to files */ + let inputs = ins.iter().map(|file| { + /* if a file is “-”, it is stdin */ + if *file == "-" { + /* portable way to access stdin as a file */ + return unsafe { File::from_raw_fd(stdin().as_raw_fd()) }; + } + + match File::open(file) { + Ok(f) => f, + Err(e) => { + eprintln!("{}: {}: {}", argv[0], file, e.strerror()); + exit(EX_IOERR); + }, + } + }).collect::>(); + + /* map all path strings to files */ + let mut outputs = outs.iter().map(|file| { + /* of a file is “-”, it is stdout */ + if *file == "-" { + /* portable way to access stdout as a file */ + return unsafe { File::from_raw_fd(stdout().as_raw_fd()) }; + } + + let options = File::options() + /* don’t truncate if -t is specified, append if -a is specified */ + .truncate(t) + .append(a) + /* enable the ability to create and write to files */ + .create(true) + .write(true) + /* finally, open the file! */ + .open(file); + + match options { + Ok(f) => return f, + Err(e) => { + eprintln!("{}: {}: {}", argv[0], file, e.strerror()); + exit(EX_IOERR); + }, + }; + }).collect::>(); + + /* if -e is specified, use stderr */ + if e { + /* portable way to access stderr as a file */ + outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) }); + } + + let mut outputs = outputs.iter().map(|o| { + if u { + /* unbuffered writing through a buffer of capacity 0 */ + BufWriter::with_capacity(0, o) + } else { + /* theoretically buffered writing */ + BufWriter::new(o) + } + }).collect::>(); + + for file in inputs { + for byte in file.bytes().map(|b| { + b.unwrap_or_else(|e| { + eprintln!("{}: {}", argv[0], e.strerror()); + exit(EX_IOERR); + }) + }) { + for out in &mut outputs { + if let Err(e) = out.write(&[byte]) { + eprintln!("{}: {}", argv[0], e.strerror()); + return ExitCode::from(EX_IOERR as u8); + } + + if u { + /* immediately flush the output for -u */ + if let Err(e) = out.flush() { + eprintln!("{}: {}", argv[0], e.strerror()); + return ExitCode::from(EX_IOERR as u8); + } + } + } + } + } + + ExitCode::SUCCESS +} 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..33eab10 100644 --- a/src/strcmp.c +++ b/src/strcmp.c @@ -1,24 +1,43 @@ -#include /* fprintf(3), stderr */ -#include /* EXIT_FAILURE */ -#include +/* + * Copyright (c) 2022–2024 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 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 Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ -static char *program_name = "strcmp"; +#include /* fprintf(3), stderr */ +#include /* size_t */ +#include /* EX_USAGE */ + +char *program_name = "strcmp"; int main(int argc, char *argv[]){ - int i; - - if(argc < 3){ - fprintf(stderr, "Usage: %s [string] [string...]\n", - argv[0] == NULL ? program_name : argv[0]); + if (argc < 3) { + fprintf(stderr, "Usage: %s string string...\n", + argv[0] == NULL ? program_name : argv[0] + ); return EX_USAGE; } - for(; *argv[1] != '\0'; ++argv[1]) - for(i = 2; i < argc; ++i) - if(*argv[i-1] > *argv[i]) - return 1; - else if(*argv[i-1] < *argv[i]++) - return -1; /* actually 255 */ + /* This compares the Nth character of arg[2] onward with argv[1]'s Nth + * character, rather than comparing each arg with argv[1] sequentially. */ + for (; *argv[1] != '\0'; ++argv[1]) { /* iterate chars in argv[1] */ + for (size_t i = 2; i < argc; ++argv[i], ++i) { /* iterate &argv[2] */ + /* this never overruns because of nul termination */ + if (*argv[i-1] != *argv[i]) { return *argv[i-1] - *argv[i]; } + } + } return 0; } diff --git a/src/swab.rs b/src/swab.rs index ca944d9..dd5b464 100644 --- a/src/swab.rs +++ b/src/swab.rs @@ -1,5 +1,6 @@ /* * Copyright (c) 2024 DTB + * Copyright (c) 2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under @@ -18,24 +19,27 @@ use std::{ env::args, - io::{ stdin, stdout, Error, ErrorKind, Read, Write }, + io::{ stdin, stdout, Error, Read, Write }, process::ExitCode, vec::Vec }; extern crate getopt; -use getopt::{ Opt, Parser }; +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: {} [-w word_size]", s); ExitCode::from(EX_USAGE as u8) } @@ -45,27 +49,26 @@ fn main() -> ExitCode { let mut input = stdin(); let mut output = stdout().lock(); - let mut opts = Parser::new(&argv, "fw:"); - let mut force = false; - let mut wordsize: usize = 2; + let mut optind: usize = 1; // argv[0] + let mut wordsize: usize = 2; // Equivalent to dd(1p). - loop { - match opts.next() { - None => break, - Some(opt) => - match opt { - Ok(Opt('f', None)) => force = true, - Ok(Opt('w', Some(arg))) => { - match arg.parse::() { - Ok(w) if w % 2 == 0 => { wordsize = w; () }, - _ => { return usage(&argv[0]); }, - } - }, - _ => { return usage(&argv[0]); } - } + while let Some(opt) = argv.getopt("w:") { + match opt.opt() { + Ok("w") => { + match opt.arg().unwrap().parse::() { + Ok(w) if w % 2 == 0 => { wordsize = w; }, + _ => { return usage(&argv[0]); }, + } + optind = opt.ind(); + }, + _ => { return usage(&argv[0]); } } } + if optind < argv.len() { + return usage(&argv[0]); + } + buf.resize(wordsize, 0); loop { @@ -83,7 +86,6 @@ fn main() -> ExitCode { break oserr(&argv[0], e) } }, - Err(e) if e.kind() == ErrorKind::Interrupted && force => continue, Err(e) => break oserr(&argv[0], e) } }