59 Commits

Author SHA1 Message Date
e7a6632b41 mm(1): improves comments 2024-07-15 12:52:02 -06:00
48dbea0228 mm(1): imports full enum 2024-07-15 12:52:02 -06:00
64f3f73d96 mm(1): formatting 2024-07-15 12:52:02 -06:00
eda5385058 mm.1: small correction 2024-07-15 12:52:02 -06:00
7a0ad78000 mm.1: reflects new changes 2024-07-15 12:52:02 -06:00
DTB
8dc763f05e mm(1): treats trailing arguments as optargs for the last opt 2024-07-15 12:52:01 -06:00
20692d581a mm(1): makes -e block inferring stdout as an output, mm.1: reflects changes to -e 2024-07-15 12:52:01 -06:00
8d693b6664 mm(1): removes debug print 2024-07-15 12:52:01 -06:00
37804aab6b mm(1): fixes comment about flags -a & -t 2024-07-15 12:52:01 -06:00
3b76254599 mm(1): fixes creating files 2024-07-15 12:52:01 -06:00
e972ff468a mm(1): added -t for disabling truncation; mm.1: updated docs 2024-07-15 12:52:01 -06:00
1b3b03cae0 mm(1): rewritten in Rust 2024-07-15 12:51:48 -06:00
DTB
9addfc9284 Merge branch 'dj' 2024-07-15 02:36:07 -06:00
DTB
8f8de5de2b dj(1): fix io[0].bufuse underflow 2024-07-08 22:53:44 -06:00
DTB
0df2c9f566 dj(1): fix infiniskipping 2024-07-08 22:48:16 -06:00
DTB
b7bc1f16ad swab(1): use the getopt error message 2024-07-08 14:34:42 -06:00
ca6865688a swab(1): updates getopt usage 2024-07-08 13:23:23 -06:00
DTB
1fd768057c swab(1): don't accept positional arguments 2024-07-08 11:45:01 -06:00
DTB
35d54d84b0 swab(1): don't use the getopt error message 2024-07-08 11:31:35 -06:00
DTB
a141b95293 swab(1): remove -f 2024-07-08 11:30:21 -06:00
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
DTB
adda0d9580 dj.1: elaborate on skip/seek behavior, provide another example 2024-07-03 18:30:54 -06:00
DTB
2167f35f58 dj(1): fix segfault when bses are mismatched 2024-07-03 17:59:21 -06:00
DTB
f4b97be1f1 dj(1): iron out Io_bufxapp 2024-07-03 17:50:04 -06:00
DTB
7fe122ac3b dj.1: clarify skip/seek behavior with regards to statistics output 2024-07-03 16:13:20 -06:00
DTB
944feef434 dj(1): Refactor out Io_fdseek entirely 2024-07-03 16:07:02 -06:00
DTB
66ca4b9a12 dj(1): remove unnecessary stderr checks 2024-07-03 15:47:48 -06:00
DTB
3897f44cf8 dj(1): prefix getopt optstring with : 2024-07-03 15:46:11 -06:00
DTB
6548a448c7 dj(1): fix potential skip/seek bug in non-std io 2024-07-03 15:44:42 -06:00
DTB
aff658d611 dj(1): remove debugging vestige, reflow output into printio 2024-07-03 14:50:50 -06:00
DTB
76252305f9 dj(1): remove Io_bufalloc 2024-07-03 14:46:56 -06:00
DTB
1cf67af281 dj(1): add a ton of assertions, fix if statement, fix io[i] mixups 2024-07-03 14:22:23 -06:00
DTB
064abb82a6 dj(1): fix option parsing regression 2024-07-03 13:50:24 -06:00
d1eefcb37e Merge branch 'makefile-improved' 2024-06-30 22:17:39 -06:00
984c1c1f9a Makefile: fixes portability issue 2024-06-30 21:21:02 -06:00
e38ea5b35d Makefile: fixes dist 2024-06-29 08:36:12 -06:00
261c98ad14 Makefile: docs no longer builds every invocation, normalize PREFIX for setting man dir 2024-06-29 08:28:49 -06:00
8 changed files with 484 additions and 503 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
@@ -111,8 +114,8 @@ build/bin/intcmp: src/intcmp.c build
.PHONY: mm .PHONY: mm
mm: build/bin/mm mm: build/bin/mm
build/bin/mm: src/mm.c build build/bin/mm: src/mm.rs build rustlibs
$(CC) $(CFLAGS) -o $@ src/mm.c $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/mm.rs
.PHONY: npc .PHONY: npc
npc: build/bin/npc npc: build/bin/npc

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-06-29 "Harakit X.X.X" .TH DJ 1 2024-07-03 "Harakit X.X.X"
.SH NAME .SH NAME
dj \(en disk jockey dj \(en disk jockey
.\" .\"
@@ -34,8 +34,13 @@ 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.
When seeking or skipping to a byte, writing or reading starts at the byte The offset used when skipping or seeking refers to how many bytes are skipped
immediately subsequent to the specified byte. or sought. Running
.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
@@ -80,6 +85,54 @@ 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
@@ -128,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
@@ -151,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)

View File

@@ -3,14 +3,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 MM 1 2024-06-17 "Harakit X.X.X" .TH MM 1 2024-07-14 "Harakit X.X.X"
.SH NAME .SH NAME
mm \(en middleman mm \(en middleman
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
mm mm
.RB [ -aenu ] .RB [ -aetu ]
.RB [ -i\ input ] .RB [ -i\ input ]
.RB [ -o\ output ] .RB [ -o\ output ]
.\" .\"
@@ -21,19 +21,25 @@ Catenate input files and write them to the start of each output file or stream.
.SH OPTIONS .SH OPTIONS
.IP \fB-a\fP .IP \fB-a\fP
Opens subsequent outputs for appending rather than updating. Opens outputs for appending rather than updating.
.IP \fB-e\fP .IP \fB-e\fP
Use the standard error as an output. Use the standard error as an output.
.IP \fB-i\fP\ \fIinput\fP .IP \fB-t\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if Causes outputs to be overwritten instead of being truncated.
no inputs are specified, the standard input shall be used.
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified, the standard output shall be used.
.IP \fB-u\fP .IP \fB-u\fP
Ensures neither input or output will be buffered. Ensures neither input or output will be buffered.
.IP \fB-n\fP .IP \fB-i\fP\ \fIinput\fP
Causes SIGINT signals to be ignored. Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
no inputs are specified, the standard input shall be used. If specified as the
last option and if there are trailing arguments to the program, they shall be
appended to the list of files to use as inputs.
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified and the
.B -e
option is not specified, the standard output shall be used. If specified as the
last option and if there are trailing arguments to the program, they shall be
appended to the list of files to use as outputs.
.\" .\"
.SH DIAGNOSTICS .SH DIAGNOSTICS
@@ -45,10 +51,6 @@ exits with the appropriate
.BR sysexits.h (3) .BR sysexits.h (3)
status. status.
.\" .\"
.SH CAVEATS
Existing files are not truncated on ouput and are instead overwritten.
.\"
.SH RATIONALE .SH RATIONALE
The The

View File

@@ -11,7 +11,6 @@ swab \(en swap bytes
.SH SYNOPSIS .SH SYNOPSIS
swab swab
.RB [ -f ]
.RB [ -w\ word_size ] .RB [ -w\ word_size ]
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
@@ -20,8 +19,6 @@ Swap the latter and former halves of a block of bytes.
.\" .\"
.SH OPTIONS .SH OPTIONS
.IP \fB-f\fP
Ignore SIGINT signal.
.IP \fB-w\fP\ \fIword_size\fP .IP \fB-w\fP\ \fIword_size\fP
Configures the word size; that is, the size in bytes of the block size on which Configures the word size; that is, the size in bytes of the block size on which
to operate. The default word size is 2. The word size must be cleanly divisible to operate. The default word size is 2. The word size must be cleanly divisible

408
src/dj.c
View File

@@ -16,12 +16,15 @@
* 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 */ #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), #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,
@@ -34,198 +37,90 @@ 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 */
int 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 */
int prec; /* partial records processed */ int fd; /* file descriptor */
int 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_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";
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 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.
* Returns io. */
static struct Io *
Io_bufrpad(struct Io *io, int padding){
memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
io->bufuse = io->bs;
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 * static struct Io *
Io_read(struct Io *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; 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;
if((t = write(io->fd, io->buf, io->bufuse)) > 0) assert(io->bufuse > 0);
memmove(io->buf, io->buf + t, (io->bufuse -= t)); 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->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 *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;
} }
/* 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){ 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].rec, io[0].prec, io[1].rec, io[1].prec,
io[0].bytes, io[1].bytes); io[0].bytes, io[1].bytes);
@@ -258,24 +153,26 @@ 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];
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_output = 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].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].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;
@@ -285,19 +182,26 @@ 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{
break; int fd;
terminate(io);
return oserr(optarg); if((fd = open(optarg, io[i].fl, creat_mode)) != -1
case 'n': noerror = 1; break; && (fdisstd(io[i].fd) || close(io[i].fd) == 0)){
case 'H': fmt_output = fmt_human; break; io[i].fd = fd;
io[i].fn = optarg;
break;
}
}
return oserr(optarg, errno);
case 'n': noerror = 1; 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,87 +211,143 @@ 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);
} }
} }
if(argc > optind){ assert(io->fd != STDIN_FILENO || io->fl == read_flags);
terminate(io); assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
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){
if(Io_bufalloc(&io[i]) == NULL){ /* buffer allocation */
fprintf(stderr, "%s: Failed to allocate %d bytes\n", if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){
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]); /* 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;
return oserr(io[i].fn);
}
} }
do{ /* read */ /* hard seeking */
Io_read(&io[0]); if(io[1].seek > 0){
if(!noerror && io[0].bufuse == 0) size_t t;
Io_read(&io[0]); /* second chance */ do{
if(io[0].bufuse == 0) /* that's all she wrote */ memset(io[1].buf, '\0',
break; (t = io[1].bufuse = MIN(io[1].bs, io[1].seek)));
else if(io[0].bufuse < io[0].bs){ if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
++io[0].prec; Io_write(&io[1]); /* second chance */
fprintf(stderr, "%s: Partial read:\n\t", program_name); if(io[1].error != 0)
output(io, fmt_output); return oserr(io[1].fn, io[1].error);
if(!noerror) }while((io[1].seek -= (t - io[1].bufuse)) > 0 && io[1].bufuse != t);
count = 1; io[1].bufuse = 0;
if(align >= 0) }
Io_bufrpad(&io[0], align);
}else if(io[1].seek > 0){
++io[0].rec; fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno);
}
do{
assert(io[0].bufuse == 0);
{ /* read */
long skipping;
size_t t;
/* hack to intentionally get a partial read from Io_read */
if((skipping = MIN(io[0].seek, io[0].bs)) > 0)
io[0].bufuse = io[0].bs - (size_t)skipping;
t = io[0].bufuse;
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 > 0){
io[0].seek -= skipping;
io[0].bufuse = 0;
count += (count != 0);
continue;
}
}
/* write */ /* write */
do{ do{
int t; int t;
if(io[1].bs > io[0].bs){ if(io[0].bs <= io[1].bs){
Io_bufxapp(&io[1], &io[0]); int n;
/* 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; /* we could write more */ continue; /* obuf not saturated - 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;
Io_write(&io[1]); if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0)
if(!noerror && io[1].bufuse == t)
Io_write(&io[1]); /* second chance */ 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; 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);
output(io, fmt_output); fprintio(stderr, fmt, io);
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);
output(io, fmt_output); fprintio(stderr, fmt, io);
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;
} }

236
src/mm.c
View File

@@ -1,236 +0,0 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <errno.h> /* errno */
#include <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */
#include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3),
* setvbuf(3), size_t, _IONBF, NULL */
#include <stdlib.h> /* free(3), realloc(3) */
#include <string.h> /* strcmp(3), strerror(3) */
#include <unistd.h> /* getopt(3) */
#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \
|| !defined EX_USAGE
# include <sysexits.h>
#endif
extern int errno;
/* This structure is how open files are tracked. */
struct Files{
size_t a; /* allocation */
size_t s; /* used size */
char *mode; /* file opening mode */
char **names; /* file names */
FILE **files; /* file pointers */
};
/* How much to grow the allocation when it's saturated. */
#ifndef ALLOC_INCREMENT
# define ALLOC_INCREMENT 1
#endif
/* How much to grow the allocation at program start. */
#ifndef ALLOC_INITIAL
# define ALLOC_INITIAL 10
#endif
/* pre-allocated strings */
static char *program_name = "<no argv[0]>";
static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>";
static char *stderr_name = "<stderr>";
static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} };
static char *wharsh = "wb";
/* Adds the open FILE pointer for the file at the path s to the files struct,
* returning the FILE if successful and NULL if not, allocating more memory in
* the files buffers as needed. */
static FILE *
Files_append(struct Files *files, FILE *file, char *name){
if(file == NULL || (files->s == files->a
&& ((files->files = realloc(files->files,
(files->a += (files->a == 0)
? ALLOC_INITIAL
: ALLOC_INCREMENT)
* sizeof *(files->files))) == NULL
|| (files->names = realloc(files->names,
files->a * sizeof *(files->names))) == NULL)))
return NULL;
files->names[files->s] = name;
return files->files[files->s++] = file;
}
/* Opens the file at the path p and puts it in the files struct, returning NULL
* if either the opening or the placement of the open FILE pointer fail. */
#define Files_open(files, p) \
Files_append((files), fopen((p), (files)->mode), (p))
/* Prints a diagnostic message based on errno and returns an exit status
* appropriate for an OS error. */
static int
oserr(char *s, char *r){
fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno));
return EX_OSERR;
}
/* Hijacks i and j from main and destructs the files[2] struct used by main by
* closing its files and freeing its files and names arrays, returning retval
* from main. */
#define terminate \
for(i = 0; i < 2; ++i){ \
for(j = 0; j < files[i].s; ++j) \
if(files[i].files[j] != stdin \
&& files[i].files[j] != stdout \
&& files[i].files[j] != stderr) \
fclose(files[i].files[j]); \
free(files[i].files); \
free(files[i].names); \
} \
return retval
/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and
* returns an exit status appropriate for a usage error. */
int usage(char *s){
fprintf(stderr, "Usage: %s [-aenu] [-i input]... [-o output]...\n", s);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
struct Files files[2]; /* {read, write} */
size_t i;
size_t j;
size_t k; /* loop index but also unbuffer status */
int retval;
/* Initializes the files structs with their default values, standard
* input and standard output. If an input or an output is specified
* these initial values will be overwritten, so to, say, use mm(1)
* equivalently to tee(1p), -o - will need to be specified before
* additional files to ensure standard output is still written. */
for(i = 0; i < 2; ++i){
files[i].a = 0;
files[i].s = 0;
files[i].mode = fmode[i];
files[i].files = NULL;
files[i].names = NULL;
Files_append(&files[i], i == 0 ? stdin : stdout,
i == 0 ? stdin_name : stdout_name);
files[i].s = 0;
}
k = 0;
if(argc > 0)
program_name = argv[0];
if(argc > 1)
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
switch(c){
case 'a': /* "rb+" -> "ab" */
files[1].mode[0] = 'a';
files[1].mode[2] = '\0';
break;
case 'e':
if(Files_append(&files[1], stderr, stderr_name) != NULL)
break;
retval = oserr(argv[0], "-e");
terminate;
case 'i':
if((strcmp(optarg, "-") == 0 && Files_append(&files[0],
stdin, stdin_name) != NULL)
|| Files_open(&files[0], optarg) != NULL)
break;
retval = oserr(argv[0], optarg);
terminate;
case 'o':
if((strcmp(optarg, "-") == 0 && Files_append(&files[1],
stdout, stdout_name) != NULL)
|| Files_open(&files[1], optarg) != NULL)
break;
/* does not exist, so try to create it */
if(errno == ENOENT){
files[1].mode = wharsh;
if(Files_open(&files[1], optarg) != NULL){
files[1].mode = fmode[1];
break;
}
}
retval = oserr(argv[0], optarg);
terminate;
case 'n':
if(signal(SIGINT, SIG_IGN) != SIG_ERR)
break;
retval = oserr(argv[0], "-n");
terminate;
case 'u':
k = 1;
break;
default:
retval = usage(argv[0]);
terminate;
}
if(optind != argc){
retval = usage(argv[0]);
terminate;
}
files[0].s += files[0].s == 0;
files[1].s += files[1].s == 0;
/* Unbuffer files. */
if(k){
for(i = 0;
i < files[0].s;
setvbuf(files[0].files[i++], NULL, _IONBF, 0));
for(i = 0;
i < files[1].s;
setvbuf(files[1].files[i++], NULL, _IONBF, 0));
}
retval = EX_OK;
/* Actual program loop. */
for(i = 0; i < files[0].s; ++i) /* iterate ins */
while((c = getc(files[0].files[i])) != EOF) /* iterate chars */
for(j = 0; j < files[1].s; ++j) /* iterate outs */
if(putc(c, files[1].files[j]) == EOF){
/* notebook's full */
retval = EX_IOERR;
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
if(fclose(files[1].files[j]) == EOF)
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
/* massage out the tense muscle */
for(k = j--; k < files[1].s - 1; ++k){
files[1].files[k] = files[1].files[k+1];
files[1].names[k] = files[1].names[k+1];
}
if(--files[1].s == 0)
terminate;
}
terminate;
}

185
src/mm.rs Normal file
View File

@@ -0,0 +1,185 @@
/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
env::args,
fs::File,
io::{ stdin, stdout, stderr, BufWriter, Read, Write },
os::fd::{ AsRawFd, FromRawFd },
process::{ exit, ExitCode },
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::GetOpt;
use strerror::StrError;
use sysexits::{ EX_IOERR, EX_USAGE };
use ArgMode::*;
enum ArgMode { In, Out }
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
let mut a = false; /* append to the file */
let mut e = false; /* use stderr as an output */
let mut t = true; /* do not truncate the file before writing */
let mut u = false; /* unbuffer i/o */
let mut ins = Vec::new(); /* initial input file path vector */
let mut outs = Vec::new(); /* initial output file path vector */
let mut mode: Option<ArgMode> = None; /* mode set by last-used option */
let mut optind = 0;
while let Some(opt) = argv.getopt("aei:o:tu") {
match opt.opt() {
Ok("a") => a = true,
Ok("e") => e = true,
Ok("u") => u = true,
Ok("t") => t = false,
Ok("i") => { /* add inputs */
let input = opt.arg().unwrap();
ins.push(input);
mode = Some(In); /* latest argument == -i */
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
outs.push(output);
mode = Some(Out); /* latest argument == -o */
},
Err(_) | Ok(_) => {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
},
};
optind = opt.ind();
}
let remaining = argv.iter().skip(optind);
/* check the last flag specified */
if let Some(m) = mode {
for arg in remaining {
/* move the subsequent arguments to the list of inputs or outputs */
match m {
In => ins.push(arg.to_string()),
Out => outs.push(arg.to_string()),
};
}
} else {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
}
/* use stdin if no inputs are specified */
if ins.is_empty() { ins.push("-".to_string()); }
/* use stdout if no outputs are specified */
if outs.is_empty() && !e { outs.push("-".to_string()); }
/* map all path strings to files */
let inputs = ins.iter().map(|file| {
/* if a file is “-”, it is stdin */
if *file == "-" {
/* portable way to access stdin as a file */
return unsafe { File::from_raw_fd(stdin().as_raw_fd()) };
}
match File::open(file) {
Ok(f) => f,
Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
},
}
}).collect::<Vec<_>>();
/* map all path strings to files */
let mut outputs = outs.iter().map(|file| {
/* of a file is “-”, it is stdout */
if *file == "-" {
/* portable way to access stdout as a file */
return unsafe { File::from_raw_fd(stdout().as_raw_fd()) };
}
let options = File::options()
/* dont truncate if -t is specified, append if -a is specified */
.truncate(t)
.append(a)
/* enable the ability to create and write to files */
.create(true)
.write(true)
/* finally, open the file! */
.open(file);
match options {
Ok(f) => return f,
Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
},
};
}).collect::<Vec<_>>();
/* if -e is specified, use stderr */
if e {
/* portable way to access stderr as a file */
outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) });
}
let mut outputs = outputs.iter().map(|o| {
if u {
/* unbuffered writing through a buffer of capacity 0 */
BufWriter::with_capacity(0, o)
} else {
/* theoretically buffered writing */
BufWriter::new(o)
}
}).collect::<Vec<_>>();
for file in inputs {
for byte in file.bytes().map(|b| {
b.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
})
}) {
for out in &mut outputs {
if let Err(e) = out.write(&[byte]) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
}
if u {
/* immediately flush the output for -u */
if let Err(e) = out.flush() {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
}
}
}
}
}
ExitCode::SUCCESS
}

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024 DTB <trinity@trinity.moe> * Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
* *
* This program is free software: you can redistribute it and/or modify it under * This program is free software: you can redistribute it and/or modify it under
@@ -18,7 +19,7 @@
use std::{ use std::{
env::args, env::args,
io::{ stdin, stdout, Error, ErrorKind, Read, Write }, io::{ stdin, stdout, Error, Read, Write },
process::ExitCode, process::ExitCode,
vec::Vec vec::Vec
}; };
@@ -29,13 +30,16 @@ 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)
} }
fn usage(s: &str) -> ExitCode { fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-f] [-w word_size]", s); eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE as u8) ExitCode::from(EX_USAGE as u8)
} }
@@ -45,24 +49,26 @@ fn main() -> ExitCode {
let mut input = stdin(); let mut input = stdin();
let mut output = stdout().lock(); let mut output = stdout().lock();
let mut force = false; let mut optind: usize = 1; // argv[0]
let mut wordsize: usize = 2; let mut wordsize: usize = 2; // Equivalent to dd(1p).
while let Some(opt) = argv.getopt("fw:") { while let Some(opt) = argv.getopt("w:") {
match opt.opt() { match opt.opt() {
Ok("f") => force = true,
Ok("w") => { Ok("w") => {
if let Some(arg) = opt.arg() { match opt.arg().unwrap().parse::<usize>() {
match arg.parse::<usize>() { Ok(w) if w % 2 == 0 => { wordsize = w; },
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); }, _ => { return usage(&argv[0]); },
} }
} optind = opt.ind();
}, },
_ => { return usage(&argv[0]); } _ => { return usage(&argv[0]); }
} }
} }
if optind < argv.len() {
return usage(&argv[0]);
}
buf.resize(wordsize, 0); buf.resize(wordsize, 0);
loop { loop {
@@ -80,7 +86,6 @@ fn main() -> ExitCode {
break oserr(&argv[0], e) break oserr(&argv[0], e)
} }
}, },
Err(e) if e.kind() == ErrorKind::Interrupted && force => continue,
Err(e) => break oserr(&argv[0], e) Err(e) => break oserr(&argv[0], e)
} }
} }