Compare commits

..

No commits in common. "bf06e91be5c2779b9f40309a821569c85f29e45c" and "cf744efc1b13d7482c2f234c9f05490e443bb8f3" have entirely different histories.

2 changed files with 232 additions and 256 deletions

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 DJ 1 2024-07-03 "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
.\" .\"
@ -34,13 +34,8 @@ respectively. This language is inherited from the
.BR dd (1p) .BR dd (1p)
utility and used here to decrease ambiguity. utility and used here to decrease ambiguity.
The offset used when skipping or seeking refers to how many bytes are skipped When seeking or skipping to a byte, writing or reading starts at the byte
or sought. Running immediately subsequent to the specified byte.
.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 .SH OPTIONS
@ -85,54 +80,6 @@ input file is \(lq-\(rq.
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
the output file 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 .SH DIAGNOSTICS
On a partial or empty read, a diagnostic message is printed. Then, the program On a partial or empty read, a diagnostic message is printed. Then, the program
@ -181,20 +128,9 @@ option is specified, this could make written data nonsensical.
Existing files are not truncated on ouput and are instead overwritten. Existing files are not truncated on ouput and are instead overwritten.
The options Many lowercase options have capitalized variants and vice-versa which can be
.B -b confusing. Capitalized options tend to affect output or are more intense
and versions of lowercase options.
.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 .SH RATIONALE
@ -215,4 +151,3 @@ Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.\" .\"
.SH SEE ALSO .SH SEE ALSO
.BR dd (1p) .BR dd (1p)
.BR lseek (3p)

375
src/dj.c
View File

@ -16,15 +16,12 @@
* 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 <assert.h> /* assert(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 */
#include <stdlib.h> /* malloc(3), strtol(3), size_t */ #include <stdlib.h> /* free(3), malloc(3), strtol(3), size_t */
#include <string.h> /* memcpy(3), memmove(3), memset(3) */ #include <string.h> /* memcpy(3), memmove(3), memset(3) */
#if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE #include <sysexits.h> /* EX_OK, EX_USAGE */
# include <sysexits.h>
#endif
#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, #include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH,
@ -37,90 +34,198 @@ char *program_name = "dj";
* 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. */
struct Io{ struct Io{
char *buf; /* buffer */ int bs; /* buffer size (-bB) */
char *fn; /* file name (-io) */
size_t bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */ size_t bufuse; /* buffer usage */
size_t bytes; /* bytes processed */ char *buf; /* buffer */
size_t prec; /* partial records processed */ int bytes; /* bytes processed */
size_t rec; /* records processed */
long seek; /* remaining bytes to seek/skip (-sS) */
int error; /* errno */
int fd; /* file descriptor */ int fd; /* file descriptor */
int fl; /* file opening flags */ 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) */
}; };
/* To be assigned to main:fmt and used with printio(). */ /* 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_asv = "%d\037%d\036%d\037%d\035%d\036%d\034";
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n"; static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
static char *stdin_name = "<stdin>"; static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>"; static char *stdout_name = "<stdout>";
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 read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */
static int write_flags = O_WRONLY | O_CREAT; static int write_flags = O_WRONLY | O_CREAT;
#define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b))
/* Macro to check if fd is stdin or stdout */ /* Macro to check if fd is a std* file, e.g. stdin. */
#define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO) #define fdisstd(fd) \
((fd) == STDIN_FILENO \
|| (fd) == STDOUT_FILENO \
|| (fd) == STDERR_FILENO)
static struct Io * /* Macro to call the cleanup functions that operate on struct io on the
Io_read(struct Io *io){ * particular io[2] used in main. Error conditions are not checked because this
int t; * is only used when the program is about to terminate (hence its name). */
#define terminate(io) do{ \
free((io[0]).buf); \
free((io[1]).buf); \
Io_fdclose(&(io)[0]); \
Io_fdclose(&(io)[1]); }while(0)
assert(io->bs > 0); /* Allocates *io's buffer. Returns NULL if unsuccessful. */
assert(io->bufuse < io->bs); static void *
Io_bufalloc(struct Io *io){
if((t = read(io->fd, &(io->buf)[io->bufuse], io->bs - io->bufuse)) < 0){ return (io->buf = malloc(io->bs * (sizeof *io->buf)));
io->error = errno;
t = 0;
} }
io->bufuse += t; /* Fills the unused portion of io's buffer with padding, updating io->bufuse.
io->bytes += t; * Returns io. */
io->prec += (0 < io->bufuse && io->bufuse < io->bs); static struct Io *
io->rec += (io->bufuse == io->bs); Io_bufrpad(struct Io *io, int padding){
assert(io->bufuse <= io->bs); memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
io->bufuse = io->bs;
return io; 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, subtracting the number
* of sought bytes from io->seek. This procedure leaves garbage in io->buf. */
static void
Io_fdseek(struct Io *io){
if(io->seek != 0
|| (!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1))
return;
if(io->fl == write_flags)
memset(io->buf, '\0', io->bs);
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);
}
io->bufuse = 0;
return;
}
/* 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){
io->bytes += (io->bufuse = read(io->fd, io->buf, io->bs));
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 * static struct Io *
Io_write(struct Io *io){ Io_write(struct Io *io){
int t; int t;
assert(io->bufuse > 0); if((t = write(io->fd, io->buf, io->bufuse)) > 0)
assert(io->bufuse <= io->bs); memmove(io->buf, io->buf + t, (io->bufuse -= t));
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->bytes += t;
io->prec += (t > 0 && io->bufuse > 0);
io->rec += (t > 0 && io->bufuse == 0);
return io; 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 static int
oserr(char *e, int n){ oserr(char *s){
fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n));
fprintf(stderr, "%s: %s: %s\n", program_name, s, strerror(errno));
return EX_OSERR; return EX_OSERR;
} }
/* 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. */ * completely read and written records. */
static void static void
fprintio(FILE *stream, char *fmt, struct Io io[2]){ output(struct Io io[2], char *fmt){
fprintf(stream, fmt, fprintf(stderr, fmt,
io[0].rec, io[0].prec, io[1].rec, io[1].prec, io[0].rec, io[0].prec, io[1].rec, io[1].prec,
io[0].bytes, io[1].bytes); io[0].bytes, io[1].bytes);
@ -155,24 +260,22 @@ usage(char *s){
int main(int argc, char *argv[]){ int main(int argc, char *argv[]){
int align; /* low 8b used, negative if no alignment is being done */ 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 */ int count; /* 0 if dj(1) runs until no more reads are possible */
char *fmt; /* == fmt_asv (default) or fmt_human (-H) */ char *fmt_output; /* == fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */ size_t i; /* side of io being modified */
struct Io io[2];
char noerror; /* 0=exits (default) 1=retries on partial reads or writes */ char noerror; /* 0=exits (default) 1=retries on partial reads or writes */
struct Io io[2 /* { in, out } */];
/* Set defaults. */ /* Set defaults. */
align = -1; align = -1;
count = 0; count = 0;
fmt = fmt_asv; fmt_output = fmt_asv;
noerror = 0; noerror = 0;
for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ for(i = 0; i < 2; ++i){
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */ io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bufuse = 0;
io[i].bytes = 0; io[i].bytes = 0;
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO; io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name; io[i].fn = i ? stdin_name : stdout_name;
io[i].fl = i == 0 ? read_flags : write_flags; io[i].fl = i ? read_flags : write_flags;
io[i].error = 0;
io[i].prec = 0; io[i].prec = 0;
io[i].rec = 0; io[i].rec = 0;
io[i].seek = 0; io[i].seek = 0;
@ -182,26 +285,19 @@ int main(int argc, char *argv[]){
int c; int c;
program_name = argv[0]; program_name = argv[0];
while((c = getopt(argc, argv, ":a:b:B:c:i:hHns: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': i = (c == 'o'); case 'i': case 'o': i = (c == 'o');
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */ if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO; io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name; io[i].fn = i ? stdin_name : stdout_name;
break; break;
}else{ }else if(Io_fdopen(&io[i], optarg) != -1)
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; break;
} terminate(io);
} return oserr(optarg);
return oserr(optarg, errno);
case 'n': noerror = 1; break; case 'n': noerror = 1; break;
case 'H': fmt = fmt_human; break; case 'H': fmt_output = fmt_human; 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];
@ -211,142 +307,87 @@ int main(int argc, char *argv[]){
case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */ 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 = (c >= 'A' && c <= 'Z'); i = (c >= 'A' && c <= 'Z'); /* uppercase changes output */
c |= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */ c &= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */
if((c == 'b' && (io[i].bs = parse(optarg)) > 0) if((c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0)) || (c == 's' && (io[i].seek = parse(optarg)) >= 0))
break; break;
/* FALLTHROUGH */ /* FALLTHROUGH */
default: default:
terminate(io);
return usage(program_name); return usage(program_name);
} }
} }
assert(io->fd != STDIN_FILENO || io->fl == read_flags); if(argc > optind){
assert(io->fd != STDOUT_FILENO || io->fl == write_flags); terminate(io);
if(argc > optind)
return usage(program_name); return usage(program_name);
}
for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ for(i = 0; i < 2; ++i){
/* buffer allocation */ if(Io_bufalloc(&io[i]) == NULL){
if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){ fprintf(stderr, "%s: Failed to allocate %d bytes\n",
fprintf(stderr, "%s: Failed to allocate %zd bytes\n",
program_name, io[i].bs); program_name, io[i].bs);
terminate(io);
return EX_OSERR; return EX_OSERR;
}else if(io[i].seek > 0)
Io_fdseek(&io[i]);
if(io[i].seek > 0){
terminate(io);
return oserr(io[i].fn);
} }
/* easy seeking */
if(!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1)
io[i].seek = 0;
} }
/* hard seeking */ do{ /* read */
if(io[1].seek > 0){ Io_read(&io[0]);
size_t t; if(!noerror && io[0].bufuse == 0)
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;
}
if(io[1].seek > 0){
fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno);
}
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 */ Io_read(&io[0]); /* second chance */
assert(io[0].bufuse >= t); if(io[0].bufuse == 0) /* that's all she wrote */
if(io[0].bufuse == t) /* that's all she wrote */
break; break;
else if(io[0].bufuse < io[0].bs){
if(/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs){ ++io[0].prec;
fprintf(stderr, "%s: Partial read:\n\t", program_name); fprintf(stderr, "%s: Partial read:\n\t", program_name);
fprintio(stderr, fmt, io); output(io, fmt_output);
if(!noerror) if(!noerror)
count = 1; count = 1;
if(align >= 0){ if(align >= 0)
/* fill the rest of the ibuf with padding */ Io_bufrpad(&io[0], align);
memset(&(io[0].buf)[io[0].bufuse], align, }else
io[0].bs - io[0].bufuse); ++io[0].rec;
io->bufuse = io->bs;
}
}
if(skipping){
io[0].bufuse = 0;
count += (count != 0);
continue;
}
}
/* write */ /* write */
do{ do{
int t; int t;
if(io[0].bs <= io[1].bs){ if(io[1].bs > io[0].bs){
int n; Io_bufxapp(&io[1], &io[0]);
/* 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) if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1)
continue; /* obuf not saturated - we could write more */ continue; /* we could write more */
} }else
Io_bufxfer(&io[1], &io[0], MIN(io[0].bufuse, io[1].bs));
t = io[1].bufuse; t = io[1].bufuse;
if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) Io_write(&io[1]);
if(!noerror && io[1].bufuse == t)
Io_write(&io[1]); /* second chance */ Io_write(&io[1]); /* second chance */
assert(io[1].bufuse <= t); if(t == io[1].bufuse){ /* no more love */
if(io[1].bufuse == t){ /* no more love */
count = 1; count = 1;
break; break;
} }else if(t > io[1].bufuse && io[1].bufuse > 0){
io[1].prec += 1;
if(0 < io[1].bufuse /* && io[1].bufuse < t */){
fprintf(stderr, "%s: Partial write:\n\t", program_name); fprintf(stderr, "%s: Partial write:\n\t", program_name);
fprintio(stderr, fmt, io); output(io, fmt_output);
if(!noerror) if(!noerror)
count = 1; count = 1;
} }else if(io[1].bufuse == 0 && t < io[1].bs)
++io[1].prec;
else
++io[1].rec;
}while(io[0].bufuse > 0); }while(io[0].bufuse > 0);
}while(count == 0 || --count > 0); }while(count == 0 || --count > 0);
fprintio(stderr, fmt, io); output(io, fmt_output);
terminate(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; return EX_OK;
} }