22 Commits

Author SHA1 Message Date
DTB
6b28a12b73 dj.1: last minute changes 2024-06-29 19:14:08 -06:00
DTB
3b5ddede98 Merge branch 'main' into dj 2024-06-29 05:56:11 -06:00
DTB
67b60e20cc dj.1: Man page fixes 2024-06-29 05:55:29 -06:00
DTB
3a66022c6d dj(1): more refactor (get rid of the c scratch variable, use scoping) 2024-06-26 15:28:02 -06:00
DTB
2cfae0e8d7 dj(1): refactor (remove Io_setdefaults and other stuff) 2024-06-26 15:15:37 -06:00
DTB
fb74e7bef0 dj(1), dj.1: Remove -A (use -a "\0") (see #101) 2024-06-26 13:45:36 -06:00
DTB
e65f6b650d dj(1): more refactor (get rid of ep pun) 2024-06-26 13:41:24 -06:00
DTB
b70b356ce5 dj(1): remove Io_buffree 2024-06-26 12:45:50 -06:00
DTB
66f5498232 dj(1): refactor Io_fdseek 2024-06-26 12:40:36 -06:00
DTB
45a880455d dj(1): refactor to build again and to get rid of globals 2024-06-26 12:22:33 -06:00
DTB
95f7992e0f dj(1): fix usage text to be consistent with man page 2024-06-26 11:39:34 -06:00
DTB
d3f5246242 dj(1), dj.1: remove the unnecessary -d and -q 2024-06-26 11:36:52 -06:00
0fc9a6b533 Makefile, docs: programmatically generate version for docs (i got tired of doing it myself) 2024-06-23 23:47:29 -06:00
8b400d8a62 Makefile, docs: rename 2024-06-23 23:34:23 -06:00
4b3333d8d3 fop(1): record separator worky now? 2024-06-21 03:26:42 -06:00
578d947561 Merge branch 'README-clarity' 2024-06-19 23:30:03 -06:00
2e91338101 Merge branch 'makefile-posix-2024' 2024-06-19 23:29:42 -06:00
72f57ba08b Makefile: adds octal disclaimer 2024-06-19 23:29:22 -06:00
35f49a699f fop(1): fixes record separator, again 2024-06-19 15:26:46 -06:00
125b4c8930 README: updated for clarity 2024-06-19 02:52:58 -06:00
f553cff096 Makefile: removes unneeded comment 2024-06-18 16:33:22 -06:00
d201f9228c Makefile: updates to use new POSIX 2024 standard features! 2024-06-18 16:32:20 -06:00
17 changed files with 169 additions and 249 deletions

View File

@@ -8,11 +8,10 @@
# permitted in any medium without royalty provided the copyright notice and this # permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty. # notice are preserved. This file is offered as-is, without any warranty.
.POSIX: # The octal escape \043 is utilized twice in this file as make(1p) will
# interpret a hash in a rule as an inline comment.
# if using BSD make(1), remove these pragmas because they break it .POSIX:
.PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
.PRAGMA: command_comment # breaks without this?
DESTDIR ?= dist DESTDIR ?= dist
PREFIX ?= /usr/local PREFIX ?= /usr/local
@@ -30,12 +29,12 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
CFLAGS += -I$(SYSEXITS) CFLAGS += -I$(SYSEXITS)
.PHONY: all .PHONY: all
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true all: docs dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
# keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
build: build:
# keep build/include until bindgen(1) has stdin support mkdir -p build/bin build/docs build/include build/lib build/o build/test
# https://github.com/rust-lang/rust-bindgen/issues/2703
mkdir -p build/bin build/include build/lib build/o build/test
.PHONY: clean .PHONY: clean
clean: clean:
@@ -44,7 +43,7 @@ clean:
dist: all dist: all
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
.PHONY: install .PHONY: install
install: dist install: dist
@@ -55,6 +54,13 @@ test: build
tests/posix-compat.sh tests/posix-compat.sh
$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt $(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt
.PHONY: docs
docs: docs/ build
for file in docs/*; do original="$$(sed -n '/^\.TH/p' <"$$file")"; \
title="$$(printf '%s\n' "$$original" | sed \
"s/X\.X\.X/$$(git describe --tags --long | cut -d'-' -f1)/g")"; \
sed "s/$$original/$$title/g" <"$$file" >"build/$$file"; done
.PHONY: rustlibs .PHONY: rustlibs
rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \ rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libstrerror.rlib build/o/libstrerror.rlib
@@ -67,9 +73,9 @@ build/o/libstrerror.rlib: build src/strerror.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \ $(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \
src/strerror.rs src/strerror.rs
# bandage solution until bindgen(1) gets stdin support
build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h
# bandage solution until bindgen(1) gets stdin support printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \
printf '#define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \
> build/include/sysexits.h > build/include/sysexits.h
bindgen --default-macro-constant-type signed --use-core --formatter=none \ bindgen --default-macro-constant-type signed --use-core --formatter=none \
build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ - build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ -

23
README
View File

@@ -1,26 +1,27 @@
“Seek not to walk the path of the masters; seek what they sought.” “Seek not to walk the path of the masters; seek what they sought.”
Matsuo Basho Matsuo Basho
The Bonsai core utilities are the result of the careful examination of the The Bonsai harakit utilities are a replacement for standard POSIX utilities
current state of POSIX and Unix utilies. The Unix Philosophy, “do one thing and which aim to fill its niche while expanding on their capabilities. These new
do it well” is its core but these tools do not cling to the names of the past. tools are the result of the careful examination of the current state of POSIX
and Unix utilies. The Unix Philosophy of “do one thing and do it well” are their
core but they avoid clinging to the past.
The era of the original Unix tools has been long and fruitful, but they have The era of the original Unix tools has been long and fruitful, but they have
their flaws. The new, non-POSIX era of this project started with frustration their flaws. This project originated from frustrations with the way certain
with the way certain tools work and how other projects that extend POSIX dont tools work and how other projects that extend POSIX dont make anything better.
make anything better.
This project will not follow in the footsteps of GNU; extensions of POSIX will This project will not follow in the footsteps of GNU; extensions of POSIX will
not be found here. GNU extensions are a gateway to the misuse of the shell. The not be found here. GNU extensions are a gateway to the misuse of the shell. The
Bonsai core utilities will intentionally discourage use of the shell for harakit utilities will intentionally discourage use of the shell for purposes
purposes beyond its scope. beyond its scope.
See docs/ for more on the specific utilities currently implemented. See docs/ for more on the specific utilities currently implemented.
Building Building
The coreutils require a POSIX-compliant environment to compile, including a C Harakit utilities require a POSIX-compliant environment to compile, including a
compiler and preprocessor (cc(1) and cpp(1) by default), an edition 2023 Rust C compiler and preprocessor (cc(1) and cpp(1) by default), an edition 2023 Rust
compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant make(1) compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant make(1)
utility. utility.
@@ -38,7 +39,7 @@ To test the utilities:
$ make test $ make test
To remove all untracked files: To remove all build and distributable files:
$ make clean $ make clean

View File

@@ -4,14 +4,14 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH DJ 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH DJ 1 2024-06-29 "Harakit X.X.X"
.SH NAME .SH NAME
dj \(en disk jockey dj \(en disk jockey
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
dj dj
.RB ( -AdHnq ) .RB ( -Hn )
.RB ( -a .RB ( -a
.RB [ byte ]) .RB [ byte ])
.RB ( -c .RB ( -c
@@ -53,9 +53,9 @@ Takes a file path as an argument and opens it for use as an input.
Takes a numeric argument as the size in bytes of the input buffer, the default Takes a numeric argument as the size in bytes of the input buffer, the default
being 1024. being 1024.
.IP \fB-s\fP .IP \fB-s\fP
Takes a numeric argument as the number of bytes to skip into the input Takes a numeric argument as the index of the byte at which reading will
before starting to read. If the standard input is used, bytes read to this point commence; \(lqskips\(rq that number of bytes. If the standard input is used,
are discarded. bytes read to this point are discarded.
.IP \fB-o\fP .IP \fB-o\fP
Takes a file path as an argument and opens it for use as an output. Takes a file path as an argument and opens it for use as an output.
.IP \fB-B\fP .IP \fB-B\fP
@@ -63,47 +63,35 @@ Does the same as
.B -b .B -b
but for the output buffer. but for the output buffer.
.IP \fB-S\fP .IP \fB-S\fP
Seeks a number of bytes through the output before starting to write from Takes a numeric argument as the index of the byte at which writing will
the input. If the output is a stream, null characters are printed. commence; \(lqseeks\(rq that number of bytes. If the standard output is used,
null characters are printed.
.IP \fB-a\fP .IP \fB-a\fP
Accepts a single literal byte with which the input buffer is padded in the event Accepts a single literal byte with which the input buffer is padded in the event
of an incomplete read from the input file. of an incomplete read from the input file. If the option argument is empty, the
.IP \fB-A\fP null byte is used.
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.
.IP \fB-c\fP .IP \fB-c\fP
Specifies a number of reads to make. The default is 0, in which case the 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. 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 .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. DIAGNOSTICS section.
.IP \fB-n\fP .IP \fB-n\fP
Retries failed reads once before exiting. 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 .SH STANDARD INPUT
The standard input shall be used as an input if no inputs are specified or if 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 .SH STANDARD OUTPUT
The standard output shall be used as an output if no inputs are specified or if 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 DIAGNOSTICS .SH DIAGNOSTICS
On a partial or empty read, unless the On a partial or empty read, a diagnostic message is printed. Then, the program
.B -q exits unless the
option is specified, a diagnostic message is printed. Then, the program exits
unless the
.B -n .B -n
option is specified. option is specified.
@@ -128,20 +116,6 @@ option may be specified. In this event, the following format is used instead:
{ASCII line feed} {ASCII line feed}
.RE .RE
If the
.B -d
option is specified, debug information will be printed at the beginning of
execution. This output contains information regarding how the program was
invoked. The following example is the result of running the program with
.B -d
as the only argument:
.RS
argv0=dj
in=<stdin> ibs=1024 skip=0 align=ff count=0
out=<stdout> obs=1024 seek=0 debug= 3 noerror=0
.RE
In non-recoverable errors that don\(cqt pertain to the read-write cycle, a 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 diagnostic message is printed and the program exits with the appropriate
.BR sysexits.h (3) .BR sysexits.h (3)
@@ -156,9 +130,7 @@ is specified along with the
option and a count, actual byte output is the product of the count and the input 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 block size and therefore may be lower than expected. If the
.B -a .B -a
or option is specified, this could make written data nonsensical.
.B -A
options are specified, this could make written data nonsensical.
.\" .\"
.SH CAVEATS .SH CAVEATS

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH FALSE 1 2024-06-06 "Bonsai Core Utilites 0.13.9" .TH FALSE 1 2024-06-06 "Harakit X.X.X"
.SH NAME .SH NAME
false \(en do nothing, unsuccessfully false \(en do nothing, unsuccessfully
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH FOP 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH FOP 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
fop \(en field operator fop \(en field operator
.\" .\"

View File

@@ -3,7 +3,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH HRU 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH HRU 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
hru \(en human readable units hru \(en human readable units
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH INTCMP 1 2024-06-06 "Bonsai Core Utilites 0.13.9" .TH INTCMP 1 2024-06-06 "Harakit X.X.X"
.SH NAME .SH NAME
intcmp \(en compare integers intcmp \(en compare integers
.\" .\"

View File

@@ -3,7 +3,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH MM 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH MM 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
mm \(en middleman mm \(en middleman
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH NPC 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH NPC 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
npc \(en show non-printing characters npc \(en show non-printing characters
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH RPN 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH RPN 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
rpn \(en reverse polish notation evaluation rpn \(en reverse polish notation evaluation
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH SCRUT 1 2024-06-06 "Bonsai Core Utilites 0.13.9" .TH SCRUT 1 2024-06-06 "Harakit X.X.X"
.SH NAME .SH NAME
scrut \(en scrutinize file properties scrut \(en scrutinize file properties
.SH SYNOPSIS .SH SYNOPSIS

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH STR 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH STR 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
str \(en test string arguments str \(en test string arguments
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH STRCMP 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH STRCMP 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
strcmp \(en compare strings strcmp \(en compare strings
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH SWAB 1 2024-06-17 "Bonsai Core Utilites 0.13.9" .TH SWAB 1 2024-06-17 "Harakit X.X.X"
.SH NAME .SH NAME
swab \(en swap bytes swab \(en swap bytes
.\" .\"

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH TRUE 1 2024-06-06 "Bonsai Core Utilites 0.13.9" .TH TRUE 1 2024-06-06 "Harakit X.X.X"
.SH NAME .SH NAME
true \(en do nothing, successfully true \(en do nothing, successfully
.\" .\"

281
src/dj.c
View File

@@ -16,7 +16,6 @@
* along with this program. If not, see https://www.gnu.org/licenses/. * along with this program. If not, see https://www.gnu.org/licenses/.
*/ */
#include <ctype.h> /* isupper(3), tolower(3) */
#include <errno.h> /* errno */ #include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */ #include <fcntl.h> /* open(2) */
#include <stdio.h> /* fprintf(3), stderr */ #include <stdio.h> /* fprintf(3), stderr */
@@ -25,8 +24,12 @@
#include <sysexits.h> /* EX_OK, EX_USAGE */ #include <sysexits.h> /* EX_OK, EX_USAGE */
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2), #include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */ * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH,
S_IWUSR */
extern int errno; extern int errno;
char *program_name = "dj";
/* dj uses two structures that respectively correspond to the reading and /* dj uses two structures that respectively correspond to the reading and
* writing ends of its jockeyed "pipe". User-configurable members are noted * writing ends of its jockeyed "pipe". User-configurable members are noted
* with their relevant options. */ * with their relevant options. */
@@ -41,56 +44,17 @@ struct Io{
int prec; /* partial records processed */ int prec; /* partial records processed */
int rec; /* records processed */ int rec; /* records processed */
long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */ long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */
} ep[2]; /* "engineered pipe"; also "extended play", for the deejay */ };
/* Additionally, the following global variables are used to store user options. /* To be assigned to main:fmt_output and used with output(). */
*/ 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 = "<no argv[0]>";
static char *stdin_name = "<stdin>"; static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>"; static char *stdout_name = "<stdout>";
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. */ static int read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */
#define setdefaults do{ \ static int write_flags = O_WRONLY | O_CREAT;
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)
#define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b))
@@ -104,8 +68,8 @@ static int write_flags = O_WRONLY | O_CREAT; /* dd(1). */
* particular io[2] used in main. Error conditions are not checked because this * 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). */ * is only used when the program is about to terminate (hence its name). */
#define terminate(io) do{ \ #define terminate(io) do{ \
Io_buffree(&(io)[0]); \ free((io[0]).buf); \
Io_buffree(&(io)[1]); \ free((io[1]).buf); \
Io_fdclose(&(io)[0]); \ Io_fdclose(&(io)[0]); \
Io_fdclose(&(io)[1]); }while(0) Io_fdclose(&(io)[1]); }while(0)
@@ -116,15 +80,6 @@ Io_bufalloc(struct Io *io){
return (io->buf = malloc(io->bs * (sizeof *io->buf))); 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. /* Fills the unused portion of io's buffer with padding, updating io->bufuse.
* Returns io. */ * Returns io. */
static struct Io * static struct Io *
@@ -193,18 +148,18 @@ Io_fdopen(struct Io *io, char *fn){
return fd; return fd;
} }
/* Seeks io->seek bytes through *io's file descriptor, (counter-intuitively) /* Seeks io->seek bytes through *io's file descriptor, subtracting the number
* returning -1 if successful and a sysexits.h exit code if an unrecoverable * of sought bytes from io->seek. This procedure leaves garbage in io->buf. */
* error occurred. io->buf will be cleared of useful bytes and io->seek will static void
* be set to zero to indicate the seek occurred. */
static int
Io_fdseek(struct Io *io){ Io_fdseek(struct Io *io){
int (*op)(int, void *, size_t);
if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) if(io->seek != 0
return -1; || (!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1))
return;
if(io->fl == write_flags)
memset(io->buf, '\0', io->bs);
/* repeated code to get the condition out of the loop */
if(io->fl == write_flags){ if(io->fl == write_flags){
memset(io->buf, '\0', io->bs); memset(io->buf, '\0', io->bs);
/* We're going to cheat and use bufuse as the retval for write(2), /* We're going to cheat and use bufuse as the retval for write(2),
@@ -223,12 +178,11 @@ Io_fdseek(struct Io *io){
/* second chance */ /* second chance */
io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)); io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else }
return EX_SOFTWARE;
io->bufuse = 0; io->bufuse = 0;
return -1; return;
} }
/* Reads io->bs bytes from *io's file descriptor into io->buf, storing the /* Reads io->bs bytes from *io's file descriptor into io->buf, storing the
@@ -242,23 +196,6 @@ Io_read(struct Io *io){
return io; 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 /* 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 * bytes to the start of io->buf and updating io->bufuse. If io->bufuse doesn't
* change, errno will probably be set. Returns io. */ * change, errno will probably be set. Returns io. */
@@ -284,14 +221,13 @@ oserr(char *s){
} }
/* Prints statistics regarding the use of dj, particularly partially and /* 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 static void
output(void){ output(struct Io io[2], char *fmt){
if(debug >= 1) fprintf(stderr, fmt,
fprintf(stderr, fmt_output, io[0].rec, io[0].prec, io[1].rec, io[1].prec,
ep[0].rec, ep[0].prec, ep[1].rec, ep[1].prec, io[0].bytes, io[1].bytes);
ep[0].bytes, ep[1].bytes);
return; return;
} }
@@ -311,142 +247,147 @@ parse(char *s){
} }
static int static int
usage(void){ usage(char *s){
fprintf(stderr, "Usage: %s (-AdfHqQ) (-a [byte]) (-c [count])\n" fprintf(stderr, "Usage: %s (-Hn) (-a [byte]) (-c [count])\n"
"\t(-i [input file]) (-b [input block size]) (-s [input offset])\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", "\t(-o [output file]) (-B [output block size]) (-S [output offset])\n",
program_name); s);
return EX_USAGE; return EX_USAGE;
} }
int main(int argc, char *argv[]){ int main(int argc, char *argv[]){
int c; int align; /* low 8b used, negative if no alignment is being done */
int i; int count; /* 0 if dj(1) runs until no more reads are possible */
char *fmt_output; /* == fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */
struct Io io[2];
char noerror; /* 0=exits (default) 1=retries on partial reads or writes */
setdefaults; /* Set defaults. */
align = -1;
count = 0;
fmt_output = fmt_asv;
noerror = 0;
for(i = 0; i < 2; ++i){
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bytes = 0;
io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name;
io[i].fl = i ? read_flags : write_flags;
io[i].prec = 0;
io[i].rec = 0;
io[i].seek = 0;
}
if(argc > 0){ if(argc > 0){
int c;
program_name = argv[0]; 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){ switch(c){
case 'i': case 'o': case 'i': case 'o': i = (c == 'o');
i = (c == 'o');
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */ if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */
ep[i].fd = (i == 0) ? STDIN_FILENO : STDOUT_FILENO; io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
ep[i].fn = (i == 0) ? stdin_name : stdout_name; io[i].fn = i ? stdin_name : stdout_name;
break; break;
}else if(Io_fdopen(&ep[i], optarg) != -1) }else if(Io_fdopen(&io[i], optarg) != -1)
break; break;
terminate(ep); terminate(io);
return oserr(optarg); return oserr(optarg);
case 'A': align = '\0'; break;
case 'd': ++debug; break;
case 'n': noerror = 1; break; case 'n': noerror = 1; break;
case 'H': fmt_output = fmt_human; break; case 'H': fmt_output = fmt_human; break;
case 'q': --debug; break;
case 'a': case 'a':
if(optarg[0] != '\0' && optarg[1] == '\0'){ if(optarg[0] == '\0' || optarg[1] == '\0'){
align = optarg[0]; align = optarg[0];
break; break;
} }
/* FALLTHROUGH */ /* 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) if(c == 'c' && (count = parse(optarg)) >= 0)
break; break;
i = isupper(c); i = (c >= 'A' && c <= 'Z'); /* uppercase changes output */
c = tolower(c); c &= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */
if((c == 'b' && (ep[i].bs = parse(optarg)) > 0) if((c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (ep[i].seek = parse(optarg)) >= 0)) || (c == 's' && (io[i].seek = parse(optarg)) >= 0))
break; break;
/* FALLTHROUGH */ /* FALLTHROUGH */
default: default:
terminate(ep); terminate(io);
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);
if(argc > optind){ if(argc > optind){
terminate(ep); terminate(io);
return usage(); return usage(program_name);
} }
for(i = 0; i <= 1; ++i){ for(i = 0; i < 2; ++i){
if(Io_bufalloc(&ep[i]) == NULL){ if(Io_bufalloc(&io[i]) == NULL){
fprintf(stderr, "%s: Failed to allocate %d bytes\n", fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, ep[i].bs); program_name, io[i].bs);
terminate(ep); terminate(io);
return EX_OSERR; return EX_OSERR;
}else if(ep[i].seek > 0) }else if(io[i].seek > 0)
switch(Io_fdseek(&ep[i])){ Io_fdseek(&io[i]);
case EX_OK: if(io[i].seek > 0){
output(); terminate(io);
terminate(ep); return oserr(io[i].fn);
return EX_OK;
} }
} }
do{ /* read */ do{ /* read */
Io_read(&ep[0]); Io_read(&io[0]);
if(!noerror && ep[0].bufuse == 0) if(!noerror && io[0].bufuse == 0)
Io_read(&ep[0]); /* second chance */ Io_read(&io[0]); /* second chance */
if(ep[0].bufuse == 0) /* that's all she wrote */ if(io[0].bufuse == 0) /* that's all she wrote */
break; break;
else if(ep[0].bufuse < ep[0].bs){ else if(io[0].bufuse < io[0].bs){
++ep[0].prec; ++io[0].prec;
if(debug >= 2){ fprintf(stderr, "%s: Partial read:\n\t", program_name);
fprintf(stderr, "%s: Partial read:\n\t", program_name); output(io, fmt_output);
output();
}
if(!noerror) if(!noerror)
count = 1; count = 1;
if(align >= 0) if(align >= 0)
Io_bufrpad(&ep[0], align); Io_bufrpad(&io[0], align);
}else }else
++ep[0].rec; ++io[0].rec;
/* write */ /* write */
do{ if(ep[1].bs > ep[0].bs){ /* io[1].bs > io[0].bs */ do{
Io_bufxapp(&ep[1], &ep[0]); int t;
if(ep[0].bs + ep[1].bufuse <= ep[1].bs && count != 1)
if(io[1].bs > io[0].bs){
Io_bufxapp(&io[1], &io[0]);
if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1)
continue; /* we could write more */ continue; /* we could write more */
}else }else
Io_bufxfer(&ep[1], &ep[0], MIN(ep[0].bufuse, ep[1].bs)); Io_bufxfer(&io[1], &io[0], MIN(io[0].bufuse, io[1].bs));
c = ep[1].bufuse; t = io[1].bufuse;
Io_write(&ep[1]); Io_write(&io[1]);
if(!noerror && ep[1].bufuse == c) if(!noerror && io[1].bufuse == t)
Io_write(&ep[1]); /* second chance */ Io_write(&io[1]); /* second chance */
if(c == ep[1].bufuse){ /* no more love */ if(t == io[1].bufuse){ /* no more love */
count = 1; count = 1;
break; break;
}else if(c > ep[1].bufuse && ep[1].bufuse > 0){ }else if(t > io[1].bufuse && io[1].bufuse > 0){
ep[1].prec += 1; io[1].prec += 1;
if(debug >= 2){ fprintf(stderr, "%s: Partial write:\n\t", program_name);
fprintf(stderr, "%s: Partial write:\n\t", program_name); output(io, fmt_output);
output();
}
if(!noerror) if(!noerror)
count = 1; count = 1;
}else if(ep[1].bufuse == 0 && c < ep[1].bs) }else if(io[1].bufuse == 0 && t < io[1].bs)
++ep[1].prec; ++io[1].prec;
else else
++ep[1].rec; ++io[1].rec;
}while(ep[0].bufuse > 0); }while(io[0].bufuse > 0);
}while(count == 0 || --count > 0); }while(count == 0 || --count > 0);
output(); output(io, fmt_output);
terminate(ep); terminate(io);
return EX_OK; return EX_OK;
} }

View File

@@ -32,7 +32,7 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
fn main() { fn main() {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut d = 0x1E.to_string(); let mut d = '\u{1E}'.to_string();
let mut arg_parser = Parser::new(&argv, "d:"); let mut arg_parser = Parser::new(&argv, "d:");
while let Some(opt) = arg_parser.next() { while let Some(opt) = arg_parser.next() {