52 Commits

Author SHA1 Message Date
DTB
bf06e91be5 Merge branch 'main' into dj 2024-07-08 11:20:52 -06:00
DTB
5d5a6d2172 dj(1): fix retvals 2024-07-07 21:13:44 -06:00
DTB
691e94c0c1 dj(1): error reporting more of the time 2024-07-07 20:33:54 -06:00
cf744efc1b swab(1): fixes not using strerror(3) 2024-07-07 18:21:48 -06:00
DTB
bab3cdd90e dj(1): Io_write: don't add to bufuse 2024-07-07 18:14:48 -06:00
DTB
abfe7046e7 dj(1): fix some type issues 2024-07-05 08:02:09 -06:00
DTB
6ed7089b25 dj(1): statistics now track hard seeks 2024-07-04 21:32:05 -06:00
DTB
571796fe0d dj.1: update man page to match behavior 2024-07-04 21:05:15 -06:00
DTB
9e8b82c4bb dj(1): fix inaccurate statistics after Io_read and Io_write 2024-07-04 20:47:30 -06:00
DTB
906eb92f5a dj(1): (broken) move hard skipping to the main loop 2024-07-04 20:27:31 -06:00
DTB
9f420131ee dj(1): more work adapting hard skipping to the main loop 2024-07-04 20:16:54 -06:00
DTB
1fab60d779 dj(1): no more pointer arithmetic 2024-07-04 20:05:18 -06:00
DTB
fe175cab19 dj(1): add a variable for skipping in the main loop 2024-07-04 20:00:40 -06:00
DTB
8c33f0116c dj(1): move open(2) flags, remove unnecessary comments 2024-07-04 19:45:53 -06:00
DTB
f8c0e0570c dj(1): make Io_write handle prec and rec 2024-07-04 19:36:32 -06:00
DTB
fc0d9e374b dj(1): make printio fprintio 2024-07-04 19:23:09 -06:00
DTB
f49a2d2eb8 dj(1): move prec and rec adjustment into Io_read 2024-07-04 19:21:40 -06:00
DTB
4004a4a006 dj(1): use the retvals of Io_read and Io_write 2024-07-04 18:41:20 -06:00
DTB
cc64561388 dj(1): only include sysexits if they aren't defined 2024-07-03 20:52:41 -06:00
DTB
5b1d4fef88 dj(1): remove Io_fdopen 2024-07-03 19:22:34 -06:00
DTB
2b593559af dj(1): remove Io_bufrpad 2024-07-03 19:06:59 -06:00
DTB
b74160fa4e dj(1): remove Io_bufxfer 2024-07-03 19:04:01 -06:00
DTB
3e1735f778 dj(1): clean up some stray ends 2024-07-03 18:44:42 -06:00
DTB
adda0d9580 dj.1: elaborate on skip/seek behavior, provide another example 2024-07-03 18:30:54 -06:00
DTB
2167f35f58 dj(1): fix segfault when bses are mismatched 2024-07-03 17:59:21 -06:00
DTB
f4b97be1f1 dj(1): iron out Io_bufxapp 2024-07-03 17:50:04 -06:00
DTB
7fe122ac3b dj.1: clarify skip/seek behavior with regards to statistics output 2024-07-03 16:13:20 -06:00
DTB
944feef434 dj(1): Refactor out Io_fdseek entirely 2024-07-03 16:07:02 -06:00
DTB
66ca4b9a12 dj(1): remove unnecessary stderr checks 2024-07-03 15:47:48 -06:00
DTB
3897f44cf8 dj(1): prefix getopt optstring with : 2024-07-03 15:46:11 -06:00
DTB
6548a448c7 dj(1): fix potential skip/seek bug in non-std io 2024-07-03 15:44:42 -06:00
DTB
aff658d611 dj(1): remove debugging vestige, reflow output into printio 2024-07-03 14:50:50 -06:00
DTB
76252305f9 dj(1): remove Io_bufalloc 2024-07-03 14:46:56 -06:00
DTB
1cf67af281 dj(1): add a ton of assertions, fix if statement, fix io[i] mixups 2024-07-03 14:22:23 -06:00
DTB
064abb82a6 dj(1): fix option parsing regression 2024-07-03 13:50:24 -06:00
d1eefcb37e Merge branch 'makefile-improved' 2024-06-30 22:17:39 -06:00
984c1c1f9a Makefile: fixes portability issue 2024-06-30 21:21:02 -06:00
DTB
69510d76af Merge branch 'main' into dj 2024-06-29 19:23:03 -06:00
DTB
6b28a12b73 dj.1: last minute changes 2024-06-29 19:14:08 -06:00
e38ea5b35d Makefile: fixes dist 2024-06-29 08:36:12 -06:00
261c98ad14 Makefile: docs no longer builds every invocation, normalize PREFIX for setting man dir 2024-06-29 08:28:49 -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
4 changed files with 329 additions and 386 deletions

View File

@@ -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 <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | tr ' ' '\n' \
| sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc
@@ -29,7 +32,7 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
CFLAGS += -I$(SYSEXITS)
.PHONY: all
all: docs dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
# keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
@@ -40,8 +43,8 @@ build:
clean:
rm -rf build dist
dist: all
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1
dist: all docs
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1

145
docs/dj.1
View File

@@ -4,14 +4,14 @@
.\" 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/>.
.\"
.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 [ -Hn ]
.RB [ -a\ byte ]
.RB [ -c\ count ]
@@ -34,8 +34,13 @@ 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
@@ -44,58 +49,94 @@ Takes a file path as an argument and opens it for use as an input.
.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\ \fIoffset\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.
.IP \fB-o\fP\ \fIfile\fP
.IP \fB-s\fP
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\ \fIblock_size\fP
Does the same as
.B -b
but for the output buffer.
.IP \fB-S\fP\ \fIoffset\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.
.IP \fB-a\fP\ \fIbyte\fP
.IP \fB-S\fP
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.
.IP \fB-c\fP\ \fIcount\fP
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 </dev/zero | dj -c 1 -b 6 -o hello.txt
tr '\(rs0' H </dev/zero | dj -c 1 -b 1 -o hello.txt
tr '\(rs0' e </dev/zero | dj -c 1 -b 1 -o hello.txt -S 1
tr '\(rs0' l </dev/zero | dj -c 1 -b 2 -o hello.txt -S 2
tr '\(rs0' o </dev/zero | dj -c 1 -b 1 -o hello.txt -S 4
tr '\(rs0' '\(rsn' </dev/zero | dj -c 1 -b 1 -o hello.txt -S 5
dj -i hello.txt
.RE
Produce the following output:
.RS
Hello
.RE
It may be particularly illuminating to print the contents of the example
.B hello.txt
after each
.BR dj (1)
invocation.
.\"
.SH DIAGNOSTICS
On a partial or empty read, unless the
.B -q
option is specified, a diagnostic message is printed. Then, the program exits
unless the
On a partial or empty read, a diagnostic message is printed. Then, the program
exits unless the
.B -n
option is specified.
@@ -120,20 +161,6 @@ option may be specified. In this event, the following format is used instead:
{ASCII line feed}
.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
diagnostic message is printed and the program exits with the appropriate
.BR sysexits.h (3)
@@ -148,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
@@ -179,3 +215,4 @@ Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.\"
.SH SEE ALSO
.BR dd (1p)
.BR lseek (3p)

552
src/dj.c
View File

@@ -16,282 +16,113 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <ctype.h> /* isupper(3), tolower(3) */
#include <assert.h> /* assert(3) */
#include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */
#include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* free(3), malloc(3), strtol(3), size_t */
#include <stdlib.h> /* malloc(3), strtol(3), size_t */
#include <string.h> /* memcpy(3), memmove(3), memset(3) */
#include <sysexits.h> /* EX_OK, EX_USAGE */
#if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE
# include <sysexits.h>
#endif
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* 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;
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 = "<no argv[0]>";
static char *stdin_name = "<stdin>";
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. */
#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,9 +142,9 @@ parse(char *s){
}
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 file] [-b block_size] [-s offset]\n"
"\t[-o file] [-B block_size] [-S offset]\n",
program_name);
@@ -322,131 +153,200 @@ usage(void){
}
int main(int argc, char *argv[]){
int c;
int i;
int align; /* low 8b used, negative if no alignment is being done */
int count; /* 0 if dj(1) runs until no more reads are possible */
char *fmt; /* == fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */
char noerror; /* 0=exits (default) 1=retries on partial reads or writes */
struct Io io[2 /* { in, out } */];
setdefaults;
/* Set defaults. */
align = -1;
count = 0;
fmt = fmt_asv;
noerror = 0;
for(i = 0; i < (sizeof io) / (sizeof *io); ++i){
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bufuse = 0;
io[i].bytes = 0;
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
io[i].fl = i == 0 ? read_flags : write_flags;
io[i].error = 0;
io[i].prec = 0;
io[i].rec = 0;
io[i].seek = 0;
}
if(argc > 0){
int c;
program_name = argv[0];
while((c = getopt(argc, argv, "a:Ab:B:c:di:hHnqs:S:o:")) != -1)
while((c = getopt(argc, argv, ":a:b:B:c:i:hHns:S:o:")) != -1)
switch(c){
case 'i': case 'o':
i = (c == 'o');
case 'i': case 'o': i = (c == 'o');
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */
ep[i].fd = (i == 0) ? STDIN_FILENO : STDOUT_FILENO;
ep[i].fn = (i == 0) ? stdin_name : stdout_name;
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
break;
}else if(Io_fdopen(&ep[i], optarg) != -1)
break;
terminate(ep);
return oserr(optarg);
case 'A': align = '\0'; break;
case 'd': ++debug; break;
case 'n': noerror = 1; break;
case 'H': fmt_output = fmt_human; break;
case 'q': --debug; break;
}else{
int fd;
if((fd = open(optarg, io[i].fl, creat_mode)) != -1
&& (fdisstd(io[i].fd) || close(io[i].fd) == 0)){
io[i].fd = fd;
io[i].fn = optarg;
break;
}
}
return oserr(optarg, errno);
case 'n': noerror = 1; break;
case 'H': fmt = fmt_human; break;
case 'a':
if(optarg[0] != '\0' && optarg[1] == '\0'){
if(optarg[0] == '\0' || optarg[1] == '\0'){
align = optarg[0];
break;
}
/* FALLTHROUGH */
case 'c': case 'b': case 's': case 'B': case 'S':
case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */
if(c == 'c' && (count = parse(optarg)) >= 0)
break;
i = isupper(c);
c = tolower(c);
if((c == 'b' && (ep[i].bs = parse(optarg)) > 0)
|| (c == 's' && (ep[i].seek = parse(optarg)) >= 0))
i = (c >= 'A' && c <= 'Z');
c |= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */
if((c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0))
break;
/* FALLTHROUGH */
default:
terminate(ep);
return usage();
return usage(program_name);
}
}
if(debug >= 3)
fprintf(stderr,
"argv0=%s\n"
"in=%s\tibs=%d\tskip=%ld\talign=%hhx\tcount=%d\n"
"out=%s\tobs=%d\tseek=%ld\tdebug=%2d\tnoerror=%d\n",
program_name,
ep[0].fn, ep[0].bs, ep[0].seek, align, count,
ep[1].fn, ep[1].bs, ep[1].seek, debug, noerror);
assert(io->fd != STDIN_FILENO || io->fl == read_flags);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
if(argc > optind){
terminate(ep);
return usage();
}
if(argc > optind)
return usage(program_name);
for(i = 0; i <= 1; ++i){
if(Io_bufalloc(&ep[i]) == NULL){
fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, ep[i].bs);
terminate(ep);
for(i = 0; i < (sizeof io) / (sizeof *io); ++i){
/* buffer allocation */
if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){
fprintf(stderr, "%s: Failed to allocate %zd bytes\n",
program_name, io[i].bs);
return EX_OSERR;
}else if(ep[i].seek > 0)
switch(Io_fdseek(&ep[i])){
case EX_OK:
output();
terminate(ep);
return EX_OK;
}
}
/* easy seeking */
if(!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1)
io[i].seek = 0;
}
do{ /* read */
Io_read(&ep[0]);
if(!noerror && ep[0].bufuse == 0)
Io_read(&ep[0]); /* second chance */
if(ep[0].bufuse == 0) /* that's all she wrote */
break;
else if(ep[0].bufuse < ep[0].bs){
++ep[0].prec;
if(debug >= 2){
fprintf(stderr, "%s: Partial read:\n\t", program_name);
output();
}
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&ep[0], align);
}else
++ep[0].rec;
/* hard seeking */
if(io[1].seek > 0){
size_t t;
do{
memset(io[1].buf, '\0',
(t = io[1].bufuse = MIN(io[1].bs, io[1].seek)));
if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
Io_write(&io[1]); /* second chance */
if(io[1].error != 0)
return oserr(io[1].fn, io[1].error);
}while((io[1].seek -= (t - io[1].bufuse)) > 0 && io[1].bufuse != t);
io[1].bufuse = 0;
}
/* write */
do{ if(ep[1].bs > ep[0].bs){ /* io[1].bs > io[0].bs */
Io_bufxapp(&ep[1], &ep[0]);
if(ep[0].bs + ep[1].bufuse <= ep[1].bs && count != 1)
continue; /* we could write more */
}else
Io_bufxfer(&ep[1], &ep[0], MIN(ep[0].bufuse, ep[1].bs));
if(io[1].seek > 0){
fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno);
}
c = ep[1].bufuse;
Io_write(&ep[1]);
if(!noerror && ep[1].bufuse == c)
Io_write(&ep[1]); /* second chance */
if(c == ep[1].bufuse){ /* no more love */
count = 1;
do{
assert(io[0].bufuse == 0);
{ /* read */
char skipping;
size_t t;
/* hack to intentionally get a partial read from Io_read */
if((skipping = (io[0].seek > 0)) && io[0].seek < io[0].bs)
io[0].bufuse = io[0].bs - io[0].seek;
t = io[0].bufuse;
if(Io_read(&io[0])->bufuse == t && !noerror && io[0].error == 0)
Io_read(&io[0]); /* second chance */
assert(io[0].bufuse >= t);
if(io[0].bufuse == t) /* that's all she wrote */
break;
}else if(c > ep[1].bufuse && ep[1].bufuse > 0){
ep[1].prec += 1;
if(debug >= 2){
fprintf(stderr, "%s: Partial write:\n\t", program_name);
output();
}
if(/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs){
fprintf(stderr, "%s: Partial read:\n\t", program_name);
fprintio(stderr, fmt, io);
if(!noerror)
count = 1;
}else if(ep[1].bufuse == 0 && c < ep[1].bs)
++ep[1].prec;
else
++ep[1].rec;
}while(ep[0].bufuse > 0);
if(align >= 0){
/* fill the rest of the ibuf with padding */
memset(&(io[0].buf)[io[0].bufuse], align,
io[0].bs - io[0].bufuse);
io->bufuse = io->bs;
}
}
if(skipping){
io[0].bufuse = 0;
count += (count != 0);
continue;
}
}
/* write */
do{
int t;
if(io[0].bs <= io[1].bs){
int n;
/* saturate obuf */
memcpy(io[1].buf, io[0].buf,
(io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs))));
/* permute the copied units out of ibuf */
memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n));
}else /* if(io[0].bs < io[1].bs) */ {
int n;
/* drain what we can from ibuf */
memcpy(&(io[1].buf)[io[1].bufuse], io[0].buf,
(n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse)));
io[1].bufuse += n;
/* permute out the copied units */
memmove(io[0].buf, &(io[0].buf)[n], io[0].bs - n);
io[0].bufuse -= n;
if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1)
continue; /* obuf not saturated - we could write more */
}
t = io[1].bufuse;
if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
Io_write(&io[1]); /* second chance */
assert(io[1].bufuse <= t);
if(io[1].bufuse == t){ /* no more love */
count = 1;
break;
}
if(0 < io[1].bufuse /* && io[1].bufuse < t */){
fprintf(stderr, "%s: Partial write:\n\t", program_name);
fprintio(stderr, fmt, io);
if(!noerror)
count = 1;
}
}while(io[0].bufuse > 0);
}while(count == 0 || --count > 0);
output();
terminate(ep);
fprintio(stderr, fmt, io);
for(i = 0; i < (sizeof io) / (sizeof *io); ++i)
if(io[i].error)
return oserr(io[i].fn, io[i].error);
return EX_OK;
}

View File

@@ -29,8 +29,11 @@ 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)
}