26 Commits

Author SHA1 Message Date
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
3 changed files with 178 additions and 283 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

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-06-29 "Harakit X.X.X"
.SH NAME
dj \(en disk jockey
.\"
.SH SYNOPSIS
dj
.RB [ -AdHnq ]
.RB [ -Hn ]
.RB [ -a\ byte ]
.RB [ -c\ count ]
@@ -35,7 +35,9 @@ respectively. This language is inherited from the
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.
immediately subsequent to the specified byte. Seeks and skips aren\(cqt counted
in the output statistics because they're guaranteed to succeed (or the utility
will exit unsuccessfully).
.\"
.SH OPTIONS
@@ -44,58 +46,46 @@ 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 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 +110,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,9 +124,7 @@ 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

374
src/dj.c
View File

@@ -16,17 +16,21 @@
* 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 */
#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. */
@@ -41,95 +45,30 @@ struct 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 */
};
/* 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 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 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;
}
/* Macro to check if fd is stdin or stdout */
#define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO)
/* 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){
assert(io != NULL);
memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
io->bufuse = io->bs;
@@ -144,6 +83,8 @@ static struct Io*
Io_bufxapp(struct Io *dest, struct Io *src){
int n;
assert(dest != NULL && src != NULL);
n = MIN(src->bufuse, dest->bs - dest->bufuse);
memcpy(dest->buf + dest->bufuse, src->buf, n);
dest->bufuse += n;
@@ -160,32 +101,27 @@ Io_bufxapp(struct Io *dest, struct Io *src){
static struct Io*
Io_bufxfer(struct Io *dest, struct Io *src, int n){
assert(dest != NULL && src != NULL);
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;
assert(io != NULL);
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){
&& (fdisstd(io->fd) || close(io->fd) == 0)){
io->fd = fd;
io->fn = fn;
}
@@ -193,44 +129,6 @@ Io_fdopen(struct Io *io, char *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. */
@@ -242,23 +140,6 @@ Io_read(struct Io *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
* bytes to the start of io->buf and updating io->bufuse. If io->bufuse doesn't
* change, errno will probably be set. Returns io. */
@@ -284,14 +165,13 @@ oserr(char *s){
}
/* 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){
printio(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(stderr, fmt,
io[0].rec, io[0].prec, io[1].rec, io[1].prec,
io[0].bytes, io[1].bytes);
return;
}
@@ -311,9 +191,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 +202,169 @@ 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 */
struct Io io[2];
char noerror; /* 0=exits (default) 1=retries on partial reads or writes */
setdefaults;
/* Set defaults. */
align = -1;
count = 0;
fmt = 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 == 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].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)
}else if(Io_fdopen(&io[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;
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();
return usage(program_name);
}
for(i = 0; i <= 1; ++i){
if(Io_bufalloc(&ep[i]) == NULL){
for(i = 0; i < 2; ++i){
/* buffer allocation */
if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){
fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, ep[i].bs);
terminate(ep);
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;
}
/* hard skipping */
if(io[0].seek > 0){
do{
if((io[0].bufuse = read(
io[0].fd, io[0].buf, MIN(io[0].bs, io[0].seek)))
== 0)
/* second chance */
io->bufuse = read(
io[0].fd, io[0].buf, MIN(io[0].bs, io[0].seek));
}while((io[0].seek -= io[0].bufuse) > 0 && io[0].bufuse != 0);
io[0].bufuse = 0;
}
/* hard seeking */
if(io[1].seek > 0){
memset(io[1].buf, '\0', io[1].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[1].bufuse = write(
io[1].fd, io[1].buf, MIN(io[1].bs, io[1].seek)))
== 0)
/* second chance */
io[1].bufuse = write(
io[1].fd, io[1].buf, MIN(io[1].bs, io[1].seek));
}while((io[1].seek -= io[1].bufuse) > 0 && io[1].bufuse != 0);
io[1].bufuse = 0;
}
/* Sought bytes aren't counted in the statistics because successful seeking
* is guaranteed here. */
for(i = 0; i < 2; ++i)
if(io[i].seek > 0)
return oserr(io[i].fn);
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 */
Io_read(&io[0]);
if(!noerror && io[0].bufuse == 0)
Io_read(&io[0]); /* second chance */
if(io[0].bufuse == 0) /* that's all she wrote */
break;
else if(ep[0].bufuse < ep[0].bs){
++ep[0].prec;
if(debug >= 2){
fprintf(stderr, "%s: Partial read:\n\t", program_name);
output();
}
else if(io[0].bufuse < io[0].bs){
++io[0].prec;
fprintf(stderr, "%s: Partial read:\n\t", program_name);
printio(fmt, io);
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&ep[0], align);
Io_bufrpad(&io[0], align);
}else
++ep[0].rec;
++io[0].rec;
/* 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)
do{
int t;
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 */
}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;
Io_write(&ep[1]);
if(!noerror && ep[1].bufuse == c)
Io_write(&ep[1]); /* second chance */
if(c == ep[1].bufuse){ /* no more love */
t = io[1].bufuse;
Io_write(&io[1]);
if(!noerror && io[1].bufuse == t)
Io_write(&io[1]); /* second chance */
if(t == io[1].bufuse){ /* no more love */
count = 1;
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();
}
}else if(t > io[1].bufuse && io[1].bufuse > 0){
io[1].prec += 1;
fprintf(stderr, "%s: Partial write:\n\t", program_name);
printio(fmt, io);
if(!noerror)
count = 1;
}else if(ep[1].bufuse == 0 && c < ep[1].bs)
++ep[1].prec;
}else if(io[1].bufuse == 0 && t < io[1].bs)
++io[1].prec;
else
++ep[1].rec;
}while(ep[0].bufuse > 0);
++io[1].rec;
}while(io[0].bufuse > 0);
}while(count == 0 || --count > 0);
output();
terminate(ep);
printio(fmt, io);
return EX_OK;
}