13 Commits

3 changed files with 110 additions and 85 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 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 bytes; in this analogy, the offset given is the index of the byte at which to
start reading or writing. 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 .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. Existing files are not truncated on ouput and are instead overwritten.
Many lowercase options have capitalized variants and vice-versa which can be The options
confusing. Capitalized options tend to affect output or are more intense .B -b
versions of lowercase options. 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 .SH RATIONALE
@@ -208,3 +215,4 @@ 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)

166
src/dj.c
View File

@@ -37,16 +37,17 @@ 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{
int bs; /* buffer size (-bB) */ char *buf; /* buffer */
char *fn; /* file name (-io) */
size_t bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */ size_t bufuse; /* buffer usage */
char *buf; /* buffer */ size_t bytes; /* bytes processed */
size_t bytes; /* bytes processed */ size_t prec; /* partial records processed */
int fd; /* file descriptor */ size_t rec; /* records processed */
int fl; /* file opening flags */ long seek; /* remaining bytes to seek/skip (-sS) */
char *fn; /* file name (may be stdin_name or stdout_name) (-io) */ int error; /* errno */
size_t prec; /* partial records processed */ int fd; /* file descriptor */
size_t rec; /* records processed */ int fl; /* file opening flags */
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 and used with printio(). */
@@ -56,7 +57,6 @@ 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 static int creat_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
| S_IWOTH; /* Consistent with touch(1p). */ | 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). */
@@ -69,16 +69,23 @@ static int write_flags = O_WRONLY | O_CREAT;
static struct Io * static struct Io *
Io_read(struct Io *io){ Io_read(struct Io *io){
int t;
assert(io->bs > 0); assert(io->bs > 0);
assert(io->bufuse < io->bs);
io->bytes += (io->bufuse = read(io->fd, io->buf, 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); assert(io->bufuse <= io->bs);
io->prec += (0 < io->bufuse && io->bufuse < io->bs);
io->rec += (io->bufuse == io->bs);
return io; return io;
} }
@@ -89,8 +96,11 @@ Io_write(struct Io *io){
assert(io->bufuse > 0); assert(io->bufuse > 0);
assert(io->bufuse <= io->bs); assert(io->bufuse <= io->bs);
if((t = write(io->fd, io->buf, io->bufuse)) > 0) if((t = write(io->fd, io->buf, io->bufuse)) < 0){
memmove(io->buf, io->buf + t, (io->bufuse -= t)); 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->prec += (t > 0 && io->bufuse > 0);
@@ -99,13 +109,9 @@ Io_write(struct Io *io){
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 *s){ oserr(char *e, int n){
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;
} }
@@ -152,20 +158,21 @@ int main(int argc, char *argv[]){
char *fmt; /* == fmt_asv (default) or fmt_human (-H) */ char *fmt; /* == fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */ size_t i; /* side of io being modified */
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]; struct Io io[2 /* { in, out } */];
/* Set defaults. */ /* Set defaults. */
align = -1; align = -1;
count = 0; count = 0;
fmt = fmt_asv; fmt = fmt_asv;
noerror = 0; 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].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bufuse = 0; 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 == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name; io[i].fn = i == 0 ? stdin_name : stdout_name;
io[i].fl = i == 0 ? read_flags : write_flags; io[i].fl = i == 0 ? 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;
@@ -192,7 +199,7 @@ int main(int argc, char *argv[]){
break; break;
} }
} }
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 = fmt_human; break;
case 'a': case 'a':
@@ -221,10 +228,10 @@ int main(int argc, char *argv[]){
if(argc > optind) if(argc > optind)
return usage(program_name); return usage(program_name);
for(i = 0; i < 2; ++i){ for(i = 0; i < (sizeof io) / (sizeof *io); ++i){
/* buffer allocation */ /* buffer allocation */
if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == 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);
return EX_OSERR; return EX_OSERR;
} }
@@ -233,58 +240,60 @@ int main(int argc, char *argv[]){
io[i].seek = 0; 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 */ /* hard seeking */
if(io[1].seek > 0){ if(io[1].seek > 0){
memset(io[1].buf, '\0', io[1].bs); size_t t;
/* 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{ do{
if((io[1].bufuse = write( memset(io[1].buf, '\0',
io[1].fd, io[1].buf, MIN(io[1].bs, io[1].seek))) (t = io[1].bufuse = MIN(io[1].bs, io[1].seek)));
== 0) if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
/* second chance */ Io_write(&io[1]); /* second chance */
io[1].bufuse = write( if(io[1].error != 0)
io[1].fd, io[1].buf, MIN(io[1].bs, io[1].seek)); return oserr(io[1].fn, io[1].error);
}while((io[1].seek -= io[1].bufuse) > 0 && io[1].bufuse != 0); }while((io[1].seek -= (t - io[1].bufuse)) > 0 && io[1].bufuse != t);
io[1].bufuse = 0; io[1].bufuse = 0;
} }
/* Sought bytes aren't counted in the statistics because successful seeking if(io[1].seek > 0){
* is guaranteed here. */ fprintio(stderr, fmt, io);
for(i = 0; i < 2; ++i) return oserr(io[1].fn, errno);
if(io[i].seek > 0) }
return oserr(io[i].fn);
do{ /* read */ do{
if(Io_read(&io[0])->bufuse == 0 && !noerror) assert(io[0].bufuse == 0);
Io_read(&io[0]); /* second chance */
if(io[0].bufuse == 0) /* that's all she wrote */
break;
if(io[0].bufuse < io[0].bs){ { /* read */
fprintf(stderr, "%s: Partial read:\n\t", program_name); char skipping;
fprintio(stderr, fmt, io); size_t t;
if(!noerror)
count = 1; /* hack to intentionally get a partial read from Io_read */
if(align >= 0){ if((skipping = (io[0].seek > 0)) && io[0].seek < io[0].bs)
/* fill the rest of the ibuf with padding */ io[0].bufuse = io[0].bs - io[0].seek;
memset(io[0].buf + io[0].bufuse, align,
io[0].bs - io[0].bufuse); t = io[0].bufuse;
io->bufuse = io->bs; 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;
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;
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;
} }
} }
@@ -299,16 +308,16 @@ int main(int argc, char *argv[]){
memcpy(io[1].buf, io[0].buf, memcpy(io[1].buf, io[0].buf,
(io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))); (io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs))));
/* permute the copied units out of ibuf */ /* permute the copied units out of ibuf */
memmove(io[0].buf, io[0].buf + n, (io[0].bufuse -= n)); memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n));
}else /* if(io[0].bs < io[1].bs) */ { }else /* if(io[0].bs < io[1].bs) */ {
int n; int n;
/* drain what we can from ibuf */ /* drain what we can from ibuf */
memcpy(io[1].buf + io[1].bufuse, io[0].buf, memcpy(&(io[1].buf)[io[1].bufuse], io[0].buf,
(n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse))); (n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse)));
io[1].bufuse += n; io[1].bufuse += n;
/* permute out the copied units */ /* 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; 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)
@@ -316,14 +325,15 @@ int main(int argc, char *argv[]){
} }
t = io[1].bufuse; t = io[1].bufuse;
if(Io_write(&io[1])->bufuse == t && !noerror) if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
Io_write(&io[1]); /* second chance */ Io_write(&io[1]); /* second chance */
assert(io[1].bufuse <= t);
if(io[1].bufuse == t){ /* no more love */ if(io[1].bufuse == t){ /* no more love */
count = 1; count = 1;
break; break;
} }
if(0 < io[1].bufuse && io[1].bufuse < t){ 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); fprintio(stderr, fmt, io);
if(!noerror) if(!noerror)
@@ -334,5 +344,9 @@ int main(int argc, char *argv[]){
fprintio(stderr, 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; return EX_OK;
} }

View File

@@ -29,8 +29,11 @@ use getopt::GetOpt;
extern crate sysexits; extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
extern crate strerror;
use strerror::StrError;
fn oserr(s: &str, e: Error) -> ExitCode { fn oserr(s: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", s, e); eprintln!("{}: {}", s, e.strerror());
ExitCode::from(EX_OSERR as u8) ExitCode::from(EX_OSERR as u8)
} }