23 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
3 changed files with 163 additions and 161 deletions

View File

@@ -41,10 +41,6 @@ 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.
Seeks and skips aren\(cqt counted in the output statistics because they're
guaranteed to succeed (or the utility will exit unsuccessfully, before it has
written any data).
.\"
.SH OPTIONS
@@ -185,9 +181,20 @@ option is specified, this could make written data nonsensical.
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
@@ -208,3 +215,4 @@ Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.\"
.SH SEE ALSO
.BR dd (1p)
.BR lseek (3p)

267
src/dj.c
View File

@@ -22,7 +22,9 @@
#include <stdio.h> /* fprintf(3), stderr */
#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,
@@ -35,16 +37,17 @@ char *program_name = "dj";
* writing ends of its jockeyed "pipe". User-configurable members are noted
* with their relevant options. */
struct Io{
int bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */
char *buf; /* buffer */
char *fn; /* file name (-io) */
size_t bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */
size_t bytes; /* bytes processed */
int fd; /* file descriptor */
int fl; /* file opening flags */
char *fn; /* file name (may be stdin_name or stdout_name) (-io) */
size_t prec; /* partial records processed */
size_t rec; /* records processed */
long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */
long seek; /* remaining bytes to seek/skip (-sS) */
int error; /* errno */
int fd; /* file descriptor */
int fl; /* file opening flags */
};
/* To be assigned to main:fmt and used with printio(). */
@@ -54,6 +57,8 @@ static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
static char *stdin_name = "<stdin>";
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 write_flags = O_WRONLY | O_CREAT;
@@ -62,95 +67,60 @@ static int write_flags = O_WRONLY | O_CREAT;
/* 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;
return io;
}
/* 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){
assert(dest != NULL && src != NULL);
memcpy(dest->buf, src->buf, (dest->bufuse = n));
memmove(src->buf, src->buf + n, (src->bufuse -= n));
return dest;
}
/* 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
&& (fdisstd(io->fd) || close(io->fd) == 0)){
io->fd = fd;
io->fn = fn;
}
return fd;
}
/* 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;
}
/* 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. */
static void
printio(char *fmt, struct Io io[2]){
fprintio(FILE *stream, char *fmt, struct Io io[2]){
fprintf(stderr, fmt,
fprintf(stream, fmt,
io[0].rec, io[0].prec, io[1].rec, io[1].prec,
io[0].bytes, io[1].bytes);
@@ -187,21 +157,22 @@ int main(int argc, char *argv[]){
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 */
struct Io io[2 /* { in, out } */];
/* Set defaults. */
align = -1;
count = 0;
fmt = fmt_asv;
noerror = 0;
for(i = 0; i < 2; ++i){
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;
@@ -218,9 +189,17 @@ int main(int argc, char *argv[]){
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
break;
}else if(Io_fdopen(&io[i], optarg) != -1)
}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);
}
}
return oserr(optarg, errno);
case 'n': noerror = 1; break;
case 'H': fmt = fmt_human; break;
case 'a':
@@ -246,14 +225,13 @@ int main(int argc, char *argv[]){
assert(io->fd != STDIN_FILENO || io->fl == read_flags);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
if(argc > optind){
if(argc > optind)
return usage(program_name);
}
for(i = 0; i < 2; ++i){
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 %d bytes\n",
fprintf(stderr, "%s: Failed to allocate %zd bytes\n",
program_name, io[i].bs);
return EX_OSERR;
}
@@ -262,100 +240,113 @@ int main(int argc, char *argv[]){
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. */
size_t t;
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);
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;
}
/* 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);
if(io[1].seek > 0){
fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno);
}
do{ /* read */
Io_read(&io[0]);
if(!noerror && io[0].bufuse == 0)
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 */
if(io[0].bufuse == 0) /* that's all she wrote */
assert(io[0].bufuse >= t);
if(io[0].bufuse == t) /* that's all she wrote */
break;
else if(io[0].bufuse < io[0].bs){
++io[0].prec;
if(/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs){
fprintf(stderr, "%s: Partial read:\n\t", program_name);
printio(fmt, io);
fprintio(stderr, fmt, io);
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&io[0], align);
}else
++io[0].rec;
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[1].bs > io[0].bs){
if(io[0].bs <= io[1].bs){
int n;
/* copy from ibuf as much as possible to the obuf */
memcpy(io[1].buf + io[1].bufuse, io[0].buf,
/* 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);
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; /* we could write more */
}else
Io_bufxfer(&io[1], &io[0], MIN(io[0].bufuse, io[1].bs));
continue; /* obuf not saturated - we could write more */
}
t = io[1].bufuse;
Io_write(&io[1]);
if(!noerror && io[1].bufuse == t)
if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
Io_write(&io[1]); /* second chance */
if(t == io[1].bufuse){ /* no more love */
assert(io[1].bufuse <= t);
if(io[1].bufuse == t){ /* no more love */
count = 1;
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);
printio(fmt, io);
fprintio(stderr, fmt, io);
if(!noerror)
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(count == 0 || --count > 0);
printio(fmt, io);
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)
}