Compare commits

...

17 Commits

3 changed files with 139 additions and 223 deletions

View File

@ -16,9 +16,12 @@
DESTDIR ?= dist DESTDIR ?= dist
PREFIX ?= /usr/local 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' || 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' | sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc CC ?= cc
@ -29,7 +32,7 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
CFLAGS += -I$(SYSEXITS) CFLAGS += -I$(SYSEXITS)
.PHONY: all .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 # keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703 # https://github.com/rust-lang/rust-bindgen/issues/2703
@ -40,8 +43,8 @@ build:
clean: clean:
rm -rf build dist rm -rf build dist
dist: all dist: all docs
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 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, .\" 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 "Harakit X.X.X" .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\ byte ] .RB [ -a\ byte ]
.RB [ -c\ count ] .RB [ -c\ count ]
@ -44,58 +44,46 @@ Takes a file path as an argument and opens it for use as an input.
.IP \fB-b\fP\ \fIblock_size\fP .IP \fB-b\fP\ \fIblock_size\fP
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\ \fIoffset\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\ \fIfile\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\ \fIblock_size\fP .IP \fB-B\fP\ \fIblock_size\fP
Does the same as Does the same as
.B -b .B -b
but for the output buffer. but for the output buffer.
.IP \fB-S\fP\ \fIoffset\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,
.IP \fB-a\fP\ \fIbyte\fP null characters are printed.
.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 .IP \fB-c\fP
incomplete read. This is equivalent to specifying
.B -a
with a null byte instead of a character.
.IP \fB-c\fP\ \fIcount\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.
@ -120,20 +108,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)
@ -148,9 +122,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

271
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(). */
*/
/* (-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"; 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"; 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,9 +247,9 @@ 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 file] [-b block_size] [-s offset]\n" "\t[-i file] [-b block_size] [-s offset]\n"
"\t[-o file] [-B block_size] [-S offset]\n", "\t[-o file] [-B block_size] [-S offset]\n",
program_name); program_name);
@ -322,131 +258,136 @@ usage(void){
} }
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(); output(io, fmt_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(); output(io, fmt_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;
} }