13 Commits

3 changed files with 87 additions and 105 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

@@ -35,7 +35,9 @@ respectively. This language is inherited from the
utility and used here to decrease ambiguity. utility and used here to decrease ambiguity.
When seeking or skipping to a byte, writing or reading starts at the byte 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 .SH OPTIONS

171
src/dj.c
View File

@@ -16,10 +16,11 @@
* 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> /* 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 <string.h> /* memcpy(3), memmove(3), memset(3) */
#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),
@@ -46,7 +47,7 @@ struct Io{
long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */ long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */
}; };
/* To be assigned to main:fmt_output and used with output(). */ /* 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_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";
@@ -58,33 +59,16 @@ 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 a std* file, e.g. stdin. */ /* Macro to check if fd is stdin or stdout */
#define fdisstd(fd) \ #define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO)
((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{ \
free((io[0]).buf); \
free((io[1]).buf); \
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)));
}
/* 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 *
Io_bufrpad(struct Io *io, int padding){ Io_bufrpad(struct Io *io, int padding){
assert(io != NULL);
memset(io->buf + io->bufuse, padding, io->bs - io->bufuse); memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
io->bufuse = io->bs; io->bufuse = io->bs;
@@ -99,6 +83,8 @@ static struct Io*
Io_bufxapp(struct Io *dest, struct Io *src){ Io_bufxapp(struct Io *dest, struct Io *src){
int n; int n;
assert(dest != NULL && src != NULL);
n = MIN(src->bufuse, dest->bs - dest->bufuse); n = MIN(src->bufuse, dest->bs - dest->bufuse);
memcpy(dest->buf + dest->bufuse, src->buf, n); memcpy(dest->buf + dest->bufuse, src->buf, n);
dest->bufuse += n; dest->bufuse += n;
@@ -115,32 +101,27 @@ Io_bufxapp(struct Io *dest, struct Io *src){
static struct Io* static struct Io*
Io_bufxfer(struct Io *dest, struct Io *src, int n){ Io_bufxfer(struct Io *dest, struct Io *src, int n){
assert(dest != NULL && src != NULL);
memcpy(dest->buf, src->buf, (dest->bufuse = n)); memcpy(dest->buf, src->buf, (dest->bufuse = n));
memmove(src->buf, src->buf + n, (src->bufuse -= n)); memmove(src->buf, src->buf + n, (src->bufuse -= n));
return dest; 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, /* Opens io->fn and saves the file descriptor into io->fd. Returns io->fd,
* which will be -1 if an error occured. */ * which will be -1 if an error occured. */
static int static int
Io_fdopen(struct Io *io, char *fn){ Io_fdopen(struct Io *io, char *fn){
int fd; int fd;
assert(io != NULL);
if((fd = open(fn, io->fl, if((fd = open(fn, io->fl,
/* these are the flags used by touch(1p) */ /* these are the flags used by touch(1p) */
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH))
!= -1 != -1
&& Io_fdclose(io) == 0){ && (fdisstd(io->fd) || close(io->fd) == 0)){
io->fd = fd; io->fd = fd;
io->fn = fn; io->fn = fn;
} }
@@ -148,43 +129,6 @@ Io_fdopen(struct Io *io, char *fn){
return fd; 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 /* 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 * number of read bytes in io->bufuse and updating io->bytes. If io->bufuse is
* 0, errno will probably be set. Returns io. */ * 0, errno will probably be set. Returns io. */
@@ -223,7 +167,7 @@ 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. */ * completely read and written records. */
static void static void
output(struct Io io[2], char *fmt){ printio(char *fmt, struct Io io[2]){
fprintf(stderr, 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,
@@ -260,7 +204,7 @@ 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_output; /* == 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 */
struct Io io[2]; 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 */
@@ -268,14 +212,14 @@ int main(int argc, char *argv[]){
/* Set defaults. */ /* Set defaults. */
align = -1; align = -1;
count = 0; count = 0;
fmt_output = fmt_asv; fmt = fmt_asv;
noerror = 0; noerror = 0;
for(i = 0; i < 2; ++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].bytes = 0; io[i].bytes = 0;
io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO; io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name; io[i].fn = i == 0 ? stdin_name : stdout_name;
io[i].fl = i ? read_flags : write_flags; io[i].fl = i == 0 ? read_flags : write_flags;
io[i].prec = 0; io[i].prec = 0;
io[i].rec = 0; io[i].rec = 0;
io[i].seek = 0; io[i].seek = 0;
@@ -285,19 +229,18 @@ 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 ? STDIN_FILENO : STDOUT_FILENO; io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name; io[i].fn = i == 0 ? stdin_name : stdout_name;
break; break;
}else if(Io_fdopen(&io[i], optarg) != -1) }else if(Io_fdopen(&io[i], optarg) != -1)
break; break;
terminate(io);
return oserr(optarg); return oserr(optarg);
case 'n': noerror = 1; break; case 'n': noerror = 1; break;
case 'H': fmt_output = fmt_human; break; case 'H': fmt = 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];
@@ -307,36 +250,71 @@ 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'); /* uppercase changes output */ i = (c >= 'A' && c <= 'Z');
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);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
if(argc > optind){ if(argc > optind){
terminate(io);
return usage(program_name); return usage(program_name);
} }
for(i = 0; i < 2; ++i){ for(i = 0; i < 2; ++i){
if(Io_bufalloc(&io[i]) == NULL){ /* 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 %d 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]); /* easy seeking */
if(io[i].seek > 0){ if(!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1)
terminate(io); 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); return oserr(io[i].fn);
}
}
do{ /* read */ do{ /* read */
Io_read(&io[0]); Io_read(&io[0]);
@@ -347,7 +325,7 @@ int main(int argc, char *argv[]){
else if(io[0].bufuse < io[0].bs){ else if(io[0].bufuse < io[0].bs){
++io[0].prec; ++io[0].prec;
fprintf(stderr, "%s: Partial read:\n\t", program_name); fprintf(stderr, "%s: Partial read:\n\t", program_name);
output(io, fmt_output); printio(fmt, io);
if(!noerror) if(!noerror)
count = 1; count = 1;
if(align >= 0) if(align >= 0)
@@ -376,7 +354,7 @@ int main(int argc, char *argv[]){
}else if(t > io[1].bufuse && io[1].bufuse > 0){ }else if(t > io[1].bufuse && io[1].bufuse > 0){
io[1].prec += 1; io[1].prec += 1;
fprintf(stderr, "%s: Partial write:\n\t", program_name); fprintf(stderr, "%s: Partial write:\n\t", program_name);
output(io, fmt_output); printio(fmt, io);
if(!noerror) if(!noerror)
count = 1; count = 1;
}else if(io[1].bufuse == 0 && t < io[1].bs) }else if(io[1].bufuse == 0 && t < io[1].bs)
@@ -386,8 +364,7 @@ int main(int argc, char *argv[]){
}while(io[0].bufuse > 0); }while(io[0].bufuse > 0);
}while(count == 0 || --count > 0); }while(count == 0 || --count > 0);
output(io, fmt_output); printio(fmt, io);
terminate(io);
return EX_OK; return EX_OK;
} }