Compare commits

..

No commits in common. "main" and "0.13.6" have entirely different histories.
main ... 0.13.6

64 changed files with 2200 additions and 2526 deletions

View File

@ -99,10 +99,9 @@ their editor or terminal.
For usage text and help messages, do not implement a -h option. Instead, print For usage text and help messages, do not implement a -h option. Instead, print
usage information when any erroneous option is specified. Follow the NetBSD usage information when any erroneous option is specified. Follow the NetBSD
style guide for the usage texts output format [0]. style guide for the usage texts output format [1].
If committing a new utility, please include tests and documentation (see [1] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
tests/ and docs/) for the new tool.
If committing a new source file, format the commit message following these If committing a new source file, format the commit message following these
guidelines: guidelines:
@ -131,7 +130,6 @@ $ git commit -m 'tool(1): fix #42 & add feature x'
Commit messages should be written in the present tense. Commit messages should be written in the present tense.
[0] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
-- --
This work © 20232024 by Emma Tebibyte is licensed under CC BY-SA 4.0. To view a This work © 20232024 by Emma Tebibyte is licensed under CC BY-SA 4.0. To view a
copy of this license, visit <http://creativecommons.org/licenses/by-sa/4.0/> copy of this license, visit <http://creativecommons.org/licenses/by-sa/4.0/>

106
Makefile
View File

@ -8,100 +8,72 @@
# permitted in any medium without royalty provided the copyright notice and this # permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty. # notice are preserved. This file is offered as-is, without any warranty.
# The octal escape \043 is utilized twice in this file as make(1p) will
# interpret a hash in a rule as an inline comment.
.POSIX: .POSIX:
# if using BSD make(1), remove these pragmas because they break it
.PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
.PRAGMA: command_comment # breaks without this?
DESTDIR ?= dist DESTDIR ?= dist
PREFIX ?= /usr/local PREFIX ?= /usr/local
# for conditionally compiling OS features MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \
OS != uname
OS_INCLUDE != test -e include/$(OS).mk && printf 'include/$(OS).mk\n' \
|| printf '/dev/null\n'
# normalized prefix
PREFIX_N != dirname $(PREFIX)/.
MANDIR != test $(PREFIX_N) = / && printf '/usr/share/man\n' \
|| printf '/share/man\n' || printf '/share/man\n'
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | tr ' ' '\n' \ SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \
| sed -n 's/sysexits\.h//p' || printf 'include\n' | sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc CC ?= cc
RUSTC ?= rustc RUSTC ?= rustc
RUSTFLAGS += --extern getopt=build/o/libgetopt.rlib \ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
--extern strerror=build/o/libstrerror.rlib \ --extern sysexits=build/o/libsysexits.rlib \
--extern sysexits=build/o/libsysexits.rlib --extern strerror=build/o/libstrerror.rlib
CFLAGS += -I$(SYSEXITS) CFLAGS += -I$(SYSEXITS)
# testing requires the absolute path to the bin directory set
BIN = build/bin
.PHONY: default
default: all test
.PHONY: all .PHONY: all
all: 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
# https://github.com/rust-lang/rust-bindgen/issues/2703
build: build:
mkdir -p build/bin build/docs build/include build/lib build/o build/test # keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
mkdir -p build/bin build/include build/lib build/o build/test
.PHONY: clean .PHONY: clean
clean: clean:
rm -rf build dist rm -rf build dist
dist: all docs dist: all
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 cp docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
.PHONY: install .PHONY: install
install: dist install: dist
cp -r $(DESTDIR)/* / cp -r $(DESTDIR)/* /
include tests/tests.mk
.PHONY: test .PHONY: test
test: all $(TESTS) /tmp/getopt test: build
@echo $(TESTS) tests/posix-compat.sh
/tmp/getopt $(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt
/tmp/getopt: src/libgetopt.rs
$(RUSTC) --test -o /tmp/getopt src/libgetopt.rs
.PHONY: docs
docs: docs/ build
for file in docs/*; do original="$$(sed -n '/^\.TH/p' <"$$file")"; \
title="$$(printf '%s\n' "$$original" | sed \
"s/X\.X\.X/$$(git describe --tags --long | cut -d'-' -f1)/g")"; \
sed "s/$$original/$$title/g" <"$$file" >"build/$$file"; done
# include OS feature libraries for compilation
include $(OS_INCLUDE)
.PHONY: rustlibs .PHONY: rustlibs
rustlibs: build/o/libgetopt.rlib build/o/libstrerror.rlib \ rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libsysexits.rlib $(OSLIB) build/o/libstrerror.rlib
build/o/libgetopt.rlib: build src/libgetopt.rs build/o/libgetopt.rlib: build src/getopt-rs/lib.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \ $(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
-o $@ src/libgetopt.rs -o $@ src/getopt-rs/lib.rs
build/o/libstrerror.rlib: build src/libstrerror.rs build/o/libstrerror.rlib: build src/strerror.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \ $(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \
src/libstrerror.rs src/strerror.rs
build/o/libsysexits.rlib: build/include/sysexits.h build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h
# bandage solution until bindgen(1) gets stdin support
printf '#define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \
> build/include/sysexits.h
bindgen --default-macro-constant-type signed --use-core --formatter=none \ bindgen --default-macro-constant-type signed --use-core --formatter=none \
build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ - build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ -
# bandage solution until bindgen(1) gets stdin support
build/include/sysexits.h: build $(SYSEXITS)sysexits.h
printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h > $@
.PHONY: dj .PHONY: dj
dj: build/bin/dj dj: build/bin/dj
build/bin/dj: src/dj.c build build/bin/dj: src/dj.c build
@ -115,32 +87,32 @@ build/bin/false: src/false.c build
.PHONY: fop .PHONY: fop
fop: build/bin/fop fop: build/bin/fop
build/bin/fop: src/fop.rs build rustlibs build/bin/fop: src/fop.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/fop.rs $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/fop.rs
.PHONY: hru .PHONY: hru
hru: build/bin/hru hru: build/bin/hru
build/bin/hru: src/hru.rs build rustlibs build/bin/hru: src/hru.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/hru.rs $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/hru.rs
.PHONY: intcmp .PHONY: intcmp
intcmp: build/bin/intcmp intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.rs build rustlibs build/bin/intcmp: src/intcmp.c build
$(RUSTC) $(RUSTFLAGS) -o $@ src/intcmp.rs $(CC) $(CFLAGS) -o $@ src/intcmp.c
.PHONY: mm .PHONY: mm
mm: build/bin/mm mm: build/bin/mm
build/bin/mm: src/mm.rs build rustlibs build/bin/mm: src/mm.c build
$(RUSTC) $(RUSTFLAGS) -o $@ src/mm.rs $(CC) $(CFLAGS) -o $@ src/mm.c
.PHONY: npc .PHONY: npc
npc: build/bin/npc npc: build/bin/npc
build/bin/npc: src/npc.c build build/bin/npc: src/npc.c build
$(CC) $(CFLAGS) -o $@ src/npc.c $(CC) $(CFLAGAS) -o $@ src/npc.c
.PHONY: rpn .PHONY: rpn
rpn: build/bin/rpn rpn: build/bin/rpn
build/bin/rpn: src/rpn.rs build rustlibs build/bin/rpn: src/rpn.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/rpn.rs $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs
.PHONY: scrut .PHONY: scrut
scrut: build/bin/scrut scrut: build/bin/scrut
@ -159,8 +131,10 @@ build/bin/strcmp: src/strcmp.c build
.PHONY: swab .PHONY: swab
swab: build/bin/swab swab: build/bin/swab
build/bin/swab: src/swab.rs build rustlibs build/bin/swab: src/swab.rs build build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) -o $@ src/swab.rs $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/swab.rs
.PHONY: true .PHONY: true
true: build/bin/true true: build/bin/true

23
README
View File

@ -1,27 +1,26 @@
“Seek not to walk the path of the masters; seek what they sought.” “Seek not to walk the path of the masters; seek what they sought.”
Matsuo Basho Matsuo Basho
The Bonsai harakit utilities are a replacement for standard POSIX utilities The Bonsai core utilities are the result of the careful examination of the
which aim to fill its niche while expanding on their capabilities. These new current state of POSIX and Unix utilies. The Unix Philosophy, “do one thing and
tools are the result of the careful examination of the current state of POSIX do it well” is its core but these tools do not cling to the names of the past.
and Unix utilies. The Unix Philosophy of “do one thing and do it well” are their
core but they avoid clinging to the past.
The era of the original Unix tools has been long and fruitful, but they have The era of the original Unix tools has been long and fruitful, but they have
their flaws. This project originated from frustrations with the way certain their flaws. The new, non-POSIX era of this project started with frustration
tools work and how other projects that extend POSIX dont make anything better. with the way certain tools work and how other projects that extend POSIX dont
make anything better.
This project will not follow in the footsteps of GNU; extensions of POSIX will This project will not follow in the footsteps of GNU; extensions of POSIX will
not be found here. GNU extensions are a gateway to the misuse of the shell. The not be found here. GNU extensions are a gateway to the misuse of the shell. The
harakit utilities will intentionally discourage use of the shell for purposes Bonsai core utilities will intentionally discourage use of the shell for
beyond its scope. purposes beyond its scope.
See docs/ for more on the specific utilities currently implemented. See docs/ for more on the specific utilities currently implemented.
Building Building
Harakit utilities require a POSIX-compliant environment to compile, including a The coreutils require a POSIX-compliant environment to compile, including a C
C compiler and preprocessor (cc(1) and cpp(1) by default), an edition 2023 Rust compiler and preprocessor (cc(1) and cpp(1) by default), an edition 2023 Rust
compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant make(1) compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant make(1)
utility. utility.
@ -39,7 +38,7 @@ To test the utilities:
$ make test $ make test
To remove all build and distributable files: To remove all untracked files:
$ make clean $ make clean

124
STYLE
View File

@ -1,124 +0,0 @@
The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System.
0. Braces are mandatory for all control flow.
1. Nested indentation should be kept to a minimum.
2. Empty lines should be placed between different kinds of statements:
int 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;
3. Each block of code should be indented once more than the keyword which
initiated the block:
switch (c) {
case 'e': mode |= EQUAL; break;
case 'g': mode |= GREATER; break;
case 'l': mode |= LESS; break;
default: return usage(s);
}
4. In C, spaces should be placed in control flow statements after the keyword
and before the opening brace:
for (i = 2; i < argc; ++i) {
5. If a function, a C control flow statement, or a Rust macro has arguments that
cause the statement to be broken into multiple lines, this should be done by
placing the arguments on a new line inside the parentheses:
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
6. If Rust function arguments or fields are on their own lines, they should
always have a trailing comma:
return Err(EvaluationError {
message: format!("{}: Invalid token", i),
code: EX_DATAERR,
})
7. If text is on the same line as a brace, spaces should be placed after an
opening curly brace and before a closing one:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
8. If a control flow statement is short enough to be easily understood in a
glance, it may be placed on a single line:
if !(argc < 0) { usage(program_name); }
9. In C, note everything you use from a library in a comment subsequent to its
#include statement:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. In Rust, place extern statements after use statements that include standard
library crates. Group alike statements:
use std::fs::Path;
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE };
11. Do not use do while loops in C.
12. Adhere to the following rules from the paper The Power of 10: Rules for
Developing Safety-Critical Code [0]:
1. Avoid complex flow constructs, such as goto and recursion.
2. All loops must have fixed bounds. This prevents runaway code.
3. Avoid heap memory allocation.
4. Restrict functions to the length of a single printed page.
6. Restrict the scope of data to the smallest possible.
7. Check the return value of all non-void functions, or cast to void to
indicate the return value is useless (such as in the case of using
fprintf(3p) to print to the standard error).
8. Use the preprocessor sparingly.
9. Limit pointer use to a single dereference, and do not use function
pointers.
10. Compile with all possible warnings active; all warnings should then be
addressed before release of the software (for C compilers, compile with
-Wpedantic).
13. Remember this quote from The Elements of Programming Style by Brian
Kernighan:
Everyone knows that debugging is twice as hard as writing a program in the
first place. So if you're as clever as you can be when you write it, how
will you ever debug it?
References
==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
--
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.

230
docs/dj.1
View File

@ -4,150 +4,124 @@
.\" 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-14 "Harakit X.X.X" .TH DJ 1
.SH NAME .SH NAME
dj \(en disk jockey dj \(en disk jockey
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
dj dj
.RB [ -Hn ] .RB ( -AdHnq )
.RB [ -a\ byte ] .RB ( -a
.RB [ -c\ count ] .RB [ byte ])
.RB ( -c
.RB [ count ])
.RB [ -i\ file ] .RB ( -i
.RB [ -b\ block_size ] .R [
.RB [ -s\ offset ] .B input file
.R ])
.RB ( -b
.R [
.B input block size
.R ])
.RB ( -s
.R [
.B input offset
.R ])
.RB [ -o\ file ] .RB ( -o
.RB [ -B\ block_size ] .R [
.RB [ -S\ offset ] .B output file
.R ])
.RB ( -B
.R [
.B output block size
.R ])
.RB ( -S
.R [
.B output offset
.R ])
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
Perform precise read and write operations on files. This utility is useful for Perform precise read and write operations on files. This utility is useful for
reading and writing binary data to and from disks. reading and writing binary data to and from disks, hence the name.
This manual page uses the terms \(lqskip\(rq and \(lqseek\(rq to refer to moving This manual page uses the terms \(lqskip\(rq and \(lqseek\(rq to refer to moving
to a specified byte by index in the input and output of the program to a specified byte by index in the input and output of the program
respectively. This language is inherited from the respectively. This language is inherited from the
.BR dd (1p) .BR dd (1p)
utility and used here to decrease ambiguity. utility and is 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
.IP \fB-i\fP\ \fIfile\fP .IP \fB-i\fP
Takes a file path as an argument and opens it for use as an input. Takes a file path as an argument and opens it for use as an input.
.IP \fB-b\fP\ \fIblock_size\fP .IP \fB-b\fP
Takes a numeric argument as the size in bytes of the input buffer, the default Takes a numeric argument as the size in bytes of the input buffer, with the
being 1024. default being 1024.
.IP \fB-s\fP .IP \fB-s\fP
Takes a numeric argument as the index of the byte at which reading will Takes a numeric argument as the number of bytes to skip into the input
commence; \(lqskips\(rq that number of bytes. If the standard input is used, before starting to read. If the standard input is used, bytes read to this point
bytes read to this point are discarded. are discarded.
.IP \fB-o\fP .IP \fB-o\fP
Takes a file path as an argument and opens it for use as an output. Takes a file path as an argument and opens it for use as an output.
.IP \fB-B\fP\ \fIblock_size\fP .IP \fB-B\fP
Takes a numeric argument as the size in bytes of the output buffer, the default Does the same as
being 1024. Note that this option only affects the size of output writes and not .B -b
the amount of output data itself. See the CAVEATS section. but for the output buffer.
.IP \fB-S\fP .IP \fB-S\fP
Takes a numeric argument as the index of the byte at which writing will Seeks a number of bytes through the output before starting to write from
commence; \(lqseeks\(rq that number of bytes. If the standard output is used, the input. If the output is a stream, null characters are printed.
null characters are printed.
.IP \fB-a\fP .IP \fB-a\fP
Accepts a single literal byte with which the input buffer is padded in the event Accepts a single literal byte with which input buffer is padded in the event
of an incomplete read from the input file. If the option argument is empty, the of an incomplete read from the input file.
null byte is used. .IP \fB-A\fP
Specifying this option pads the input buffer with null bytes in the event of an
incomplete read. Equivalent to specifying
.B -a
with a null byte instead of a character.
.IP \fB-c\fP .IP \fB-c\fP
Specifies a number of blocks to read. The default is 0, in which case the input Specifies a number of reads to make. The default is zero, in which case the
is read until a partial or empty read is made. input is read until a partial or empty read is made.
.IP \fB-d\fP
Prints invocation information before program execution as described in the
DIAGNOSTICS section below. Each invocation increments the debug level of the
program.
.IP \fB-H\fP .IP \fB-H\fP
Prints diagnostic messages in a human-readable manner as described in the Prints diagnostics messages in a human-readable manner as described in the
DIAGNOSTICS section. DIAGNOSTICS section below.
.IP \fB-n\fP .IP \fB-n\fP
Retries failed reads once before exiting. Retries failed reads once more before exiting.
.IP \fB-q\fP
Suppresses error messages which print when a read or write is partial or
empty. Each invocation decrements the debug level of the program.
.\" .\"
.SH STANDARD INPUT .SH STANDARD INPUT
The standard input shall be used as an input if no inputs are specified or if The standard input shall be used as an input if no inputs are specified or if
input file is \(lq-\(rq. one or more of the input files is \(lq-\(rq.
.\"
.SH STANDARD OUTPUT
The standard output shall be used as an output if no inputs are specified or if
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 (unless the
exits unless the .B -q
option is specified) and the program exits (unless the
.B -n .B -n
option is specified. option is specified).
By default, statistics are printed for input and output to the standard error in By default, statistics are printed for input and output to the standard error in
the following format: the following format:
.RS .RS
{records read} {ASCII unit separator} {partial records read} .R {records read} {ASCII unit separator} {partial records read}
{ASCII record separator} {records written} {ASCII unit separator} .R {ASCII record separator} {records written} {ASCII unit separator}
{partial records written} {ASCII group separator} {bytes read} .R {partial records written} {ASCII group separator} {bytes read}
{ASCII record separator} {bytes written} {ASCII file separator} .R {ASCII record separator} {bytes written} {ASCII file separator}
.RE .RE
This format for diagnostic output is designed to be machine-parseable for This format for diagnostic output is designed to be machine-parseable for
@ -156,9 +130,23 @@ convenience. For a more human-readable format, the
option may be specified. In this event, the following format is used instead: option may be specified. In this event, the following format is used instead:
.RS .RS
{records read} '+' {partial records read} '>' {records written} .R {records read} '+' {partial records read} '>' {records written}
'+' {partial records written} ';' {bytes read} '>' {bytes written} .R '+' {partial records written} ';' {bytes read} '>' {bytes written}
{ASCII line feed} .R {ASCII line feed}
.RE
If the
.B -d
option is specified, debug output will be printed at the beginning of
execution. This debug information contains information regarding how the program
was invoked. The following example is the result of running the program with
.B -d
as the only argument:
.RS
.R argv0=dj
.R in=<stdin> ibs=1024 skip=0 align=ff count=0
.R out=<stdout> obs=1024 seek=0 debug= 3 noerror=0
.RE .RE
In non-recoverable errors that don\(cqt pertain to the read-write cycle, a In non-recoverable errors that don\(cqt pertain to the read-write cycle, a
@ -172,36 +160,20 @@ If
.B -n .B -n
is specified along with the is specified along with the
.B -c .B -c
option and a count, actual byte output is the product of the count and the input option and a count, actual byte output may be lower than expected (the product
block size and therefore may be lower than expected. If the of the count and the input block size). If the
.B -a .B -a
option is specified, this could make written data nonsensical. or
.B -A
options are used, this could make data written nonsensical.
.\" .\"
.SH CAVEATS .SH CAVEATS
Existing files are not truncated on ouput and are instead overwritten. Existing files are not truncated on ouput and are instead overwritten.
Option variants that have lowercase and uppercase forms could be confused for Many lowercase options have capitalized variants and vice-versa which can be
each other. The former affects input and the latter affects output. confusing. Capitalized options tend to affect output or are more intense
versions of lowercase options.
The
.B -B
option could be mistaken for the count in bytes of data written to the output.
This conception is intuitive but incorrect, as the
.B -c
option controls the number of blocks to read and the
.B -b
option sets the size of the blocks. The
.B -B
option is similar to the latter but sets the size of blocks to be written,
regardless of the amount of data that will actually be written. In practice,
this means the input buffer should be very large to make use of modern hardware
input and output speeds.
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
@ -222,5 +194,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)
.BR mm (1)

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 FALSE 1 2024-06-06 "Harakit X.X.X" .TH FALSE 1
.SH NAME .SH NAME
false \(en do nothing, unsuccessfully false \(en do nothing, unsuccessfully
.\" .\"

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 FOP 1 2024-06-17 "Harakit X.X.X" .TH fop 1
.SH NAME .SH NAME
fop \(en field operator fop \(en field operator
.\" .\"
@ -18,14 +18,18 @@ fop
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
Performs operations on specified fields in data read from the standard input. Performs operations on specified fields in input data.
.\" .\"
.SH OPTIONS .SH OPTIONS
.IP \fB-d\fP\ \fIdelimiter\fP .IP \fB-d\fP
Sets a delimiter by which the input data will be split into fields. The default Sets a delimiter by which the input data will be split into fields. The default
is an ASCII record separator. is an ASCII record separator.
.\" .\"
.SH STANDARD INPUT
Data will be read from the standard input.
.\"
.SH CAVEATS .SH CAVEATS
Field indices are zero-indexed, which may be unexpected behavior for some users. Field indices are zero-indexed, which may be unexpected behavior for some users.
@ -47,12 +51,6 @@ but there was no easy way to modify the field in the ouput of
.BR ls (1p) .BR ls (1p)
without creating a new tool. without creating a new tool.
.\" .\"
.SH AUTHOR
Written by Emma Tebibyte
.MT emma@tebibyte.media
.ME .
.\"
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later Copyright \(co 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later

View File

@ -3,7 +3,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 HRU 1 2024-06-17 "Harakit X.X.X" .TH HRU 1
.SH NAME .SH NAME
hru \(en human readable units hru \(en human readable units
.\" .\"
@ -15,9 +15,9 @@ hru
Convert counts to higher units. Convert counts to higher units.
Byte counts will be read in the form of whole numbers from the standard input The program will read byte counts in the form of whole numbers from the standard
and be written to the standard output the same number converted to a higher unit input and write to the standard output the same number converted to a higher
of data as defined by the \fIInternational System of Units\fP. unit of data as defined by the International System of Units.
The program will convert the byte count to the highest unit possible where the The program will convert the byte count to the highest unit possible where the
value is greater than one. value is greater than one.
@ -35,8 +35,10 @@ The GNU project\(cqs
.BR ls (1) .BR ls (1)
implementation contains a human-readable option (\fB-h\fP) that, when specified, implementation contains a human-readable option (\fB-h\fP) that, when specified,
makes the tool print size information in a format more immediately makes the tool print size information in a format more immediately
readable. This functionality is useful not only in this context, so the decision readable. This functionality is useful not only in the context of
was made to split it into a new tool. The original functionality from GNU\(cqs .BR ls (1)
so the decision was made to split it into a new tool. The original functionality
in GNU\(cqs
.BR ls (1) .BR ls (1)
can be emulated with can be emulated with
.BR fop (1) .BR fop (1)
@ -44,12 +46,8 @@ combined with this program.
.\" .\"
.SH STANDARDS .SH STANDARDS
The standard unit prefixes as specified by the The standard unit prefixes as specified by the Bureau International des Poids
.I Bureau International des Poids et Mesures et Mesures (BIPM) in the ninth edition of The International System of Units (SI)
.RI ( BIPM )
in the ninth edition of
.I The International System of Units
.RI ( SI )
are utilized for the ouput of conversions. are utilized for the ouput of conversions.
.\" .\"
.SH AUTHOR .SH AUTHOR
@ -66,4 +64,4 @@ Copyright \(co 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later
.SH SEE ALSO .SH SEE ALSO
GNU GNU
.BR ls (1), .BR ls (1),
.I The International System of Units (SI) 9th Edition The International System of Units (SI) 9th Edition

View File

@ -4,14 +4,16 @@
.\" 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 INTCMP 1 2024-06-06 "Harakit X.X.X" .TH INTCMP 1
.SH NAME .SH NAME
intcmp \(en compare integers intcmp \(en compare integers
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
intcmp intcmp
.RB [ -egl ]\ integer\ integer... .RB ( -egl )
.RB [ integer ]
.RB [ integer... ]
.SH DESCRIPTION .SH DESCRIPTION
Compare integers to each other. Compare integers to each other.
.\" .\"
@ -26,23 +28,19 @@ Permits a given integer to be less than the following integer.
.\" .\"
.SH EXAMPLES .SH EXAMPLES
It may help to think of the It may help to think of the -e, -g, and -l options as equivalent to the
.BR -e , infix algebraic \(lq=\(rq, \(lq>\(rq, and \(lq<\(rq operators respectively, with
.BR -g , each option putting its symbol between every given integer. The following
and example is equivalent to evaluating \(lq1 < 2 < 3\(rq:
.B -l
options as equivalent to the infix algebraic \(lq=\(rq, \(lq>\(rq, and \(lq<\(rq
operators respectively, with each option putting its symbol between every given
integer. The following example is equivalent to evaluating \(lq1 < 2 < 3\(rq:
\" \"
.RS .RS
intcmp -l 1 2 3 .R intcmp -l 1 2 3
.RE .RE
.\" .\"
.SH DIAGNOSTICS .SH DIAGNOSTICS
The program will exit with a successfully for a valid expression and with an The program will exit with a status code of 0 for a valid expression and with a
error code of 1 for an invalid expression. code of 1 for an invalid expression.
In the event of an error, a debug message will be printed and the program will In the event of an error, a debug message will be printed and the program will
exit with the appropriate exit with the appropriate
@ -51,8 +49,7 @@ error code.
.\" .\"
.SH BUGS .SH BUGS
.BR -egl , -egl, \(lqequal to or less than or greater than\(rq, exits 0 no matter what for
\(lqequal to or less than or greater than\(rq, always exits successfully for
valid program usage and may be abused to function as an integer validator. Use valid program usage and may be abused to function as an integer validator. Use
.BR str (1) .BR str (1)
instead. instead.
@ -60,17 +57,9 @@ instead.
.SH CAVEATS .SH CAVEATS
There are multiple ways to express compound comparisons; \(lqless than or equal There are multiple ways to express compound comparisons; \(lqless than or equal
to\(rq can be to\(rq can be -le or -el, for example.
.B -le
or
.BR -el ,
for example.
The inequality comparison is The inequality comparison is -gl or -lg for \(lqless than or greater than\(rq;
.B -gl
.B or
.B -lg
for \(lqless than or greater than\(rq;
this is elegant but unintuitive. this is elegant but unintuitive.
.\" .\"
.SH RATIONALE .SH RATIONALE

View File

@ -3,16 +3,18 @@
.\" 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-07-14 "Harakit X.X.X" .TH mm 1
.SH NAME .SH NAME
mm \(en middleman mm \(en middleman
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
mm mm
.RB [ -aetu ] .RB ( -aenu )
.RB [ -i\ input ] .RB ( -i
.RB [ -o\ output ] .RB [ input ])
.RB ( -o
.RB [ output ])
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
@ -21,36 +23,34 @@ 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 outputs for appending rather than updating. Opens subsequent 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-t\fP .IP \fB-i\fP
Causes outputs to be overwritten instead of being truncated. 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.
.IP \fB-o\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-i\fP\ \fIinput\fP .IP \fB-n\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if Causes SIGINT signals to be ignored.
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
If an output cannot be written to, an error occurs; however, exiting will be If an output cannot be written to, an error occurs. Additional outputs are not
deferred until writing to any other specified outputs completes. affected and writing to them continues.
When an error is encountered, a diagnostic message is printed and the program When an error is encountered, a diagnostic message is printed and the program
exits with the appropriate 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

@ -4,14 +4,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 NPC 1 2024-06-17 "Harakit X.X.X" .TH NPC 1
.SH NAME .SH NAME
npc \(en show non-printing characters npc \(en show non-printing characters
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
npc npc
.RB [ -et ] .RB ( -et )
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
@ -25,10 +25,10 @@ becomes '^?'. Characters with the high bit set (>127) are printed as 'M-'
followed by the graphical representation for the same character without the followed by the graphical representation for the same character without the
high bit set. high bit set.
.\" .\"
.SH OPTIONS .SH USAGE
.IP \fB-e\fP .IP \fB-e\fP
Prints a dollar sign ('$') before each newline. Prints a dollar sign ('$') before each line ending.
.IP \fB-t\fP .IP \fB-t\fP
Prints tab characters as '^I' rather than a literal horizontal tab. Prints tab characters as '^I' rather than a literal horizontal tab.
.\" .\"
@ -52,8 +52,8 @@ the
.B -v .B -v
option, is the bandage solution GNU and other software suites use. option, is the bandage solution GNU and other software suites use.
This functionality is included in a separate tool because its usefulness extends This functionality is a separate tool because its usefulness extends beyond that
beyond that of of
.BR cat (1p). .BR cat (1p).
.\" .\"
.SH AUTHOR .SH AUTHOR

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 RPN 1 2024-06-17 "Harakit X.X.X" .TH rpn 1
.SH NAME .SH NAME
rpn \(en reverse polish notation evaluation rpn \(en reverse polish notation evaluation
.\" .\"
@ -18,12 +18,12 @@ rpn
Evaluate reverse polish notation. Evaluate reverse polish notation.
The program evaluates reverse polish notation expressions read either from the The program evaluates reverse polish notation expressions either read from the
standard input or parsed from provided arguments. See the STANDARD INPUT standard input or parsed from provided arguments. See the STANDARD INPUT
section. section.
Upon evaluation, the resulting number on the stack will be printed to the Upon evaluation, the program will print the resulting number on the stack to the
standard output. Any further numbers specified will be placed at the end of the standard output. Any further specified numbers will be placed at the end of the
stack. stack.
For information on for reverse polish notation syntax, see For information on for reverse polish notation syntax, see
@ -31,8 +31,8 @@ For information on for reverse polish notation syntax, see
.\" .\"
.SH STANDARD INPUT .SH STANDARD INPUT
If arguments are specified, they are interpreted as an expression to be If arguments are passed, they are interpreted as an expression to be
evaluated. Otherwise, whitespace-delimited numbers and operations are read from evaluated. Otherwise, it reads whitespace-delimited numbers and operations from
the standard input. the standard input.
.\" .\"
.SH DIAGNOSTICS .SH DIAGNOSTICS
@ -46,13 +46,11 @@ error message and continue accepting input.
.SH CAVEATS .SH CAVEATS
Due to precision constraints and the way floats are represented in accordance Due to precision constraints and the way floats are represented in accordance
with the with the IEEE Standard for Floating Point Arithmetic (\fIIEEE 754\fP),
.I IEEE Standard for Floating Point Arithmetic floating-point arithmetic has rounding errors. This is somewhat curbed by using
(\fIIEEE 754\fP), floating-point arithmetic has rounding errors. This is the machine epsilon as provided by the Rust standard library to which to round
somewhat curbed by using the machine epsilon as provided by the Rust standard numbers. Because of this, variation is expected in the number of decimal places
library to which numbers are rounded. Because of this, variation is expected in the program can handle based on the platform and hardware of any given machine.
the number of decimal places the program can handle based on the platform and
hardware of any given machine.
.\" .\"
.SH RATIONALE .SH RATIONALE
@ -65,8 +63,8 @@ program. A
pre-dates the standardized pre-dates the standardized
.BR bc (1p), .BR bc (1p),
the latter originally being a preprocessor for the former, and was included in the latter originally being a preprocessor for the former, and was included in
Second Edition UNIX and onward. While it implements reverse polish notation, it UNIX v2 onward. While it implements reverse polish notation, it still suffers
still suffers from being unable to accept an expression as an argument. from being unable to accept an expression as an argument.
.\" .\"
.SH AUTHOR .SH AUTHOR

View File

@ -4,14 +4,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 SCRUT 1 2024-06-06 "Harakit X.X.X" .TH scrut 1
.SH NAME .SH NAME
scrut \(en scrutinize file properties scrut \(en scrutinize file properties
.SH SYNOPSIS .SH SYNOPSIS
scrut scrut
.RB [ -LSbcdefgkprsuwx ] .RB ( -LSbcdefgkprsuwx )
.B file... .RB [ file... ]
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION

View File

@ -4,18 +4,19 @@
.\" 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 STR 1 2024-06-17 "Harakit X.X.X" .TH STR 1
.SH NAME .SH NAME
str \(en test string arguments str \(en test the character types of string arguments
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
str str
.B type string... .RB [ type ]
.RB [ string... ]
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
Test the character types of string arguments. Test string arguments.
The tests in this program are equivalent to the functions with the same names in The tests in this program are equivalent to the functions with the same names in
.BR ctype.h (0p) .BR ctype.h (0p)
@ -23,8 +24,11 @@ and are the methods by which string arguments are tested.
.\" .\"
.SH DIAGNOSTICS .SH DIAGNOSTICS
If all tests pass, the program will exit successfully. If any of the tests fail, If all tests pass, the program will exit with an exit code of 0. If any of the
the program will exit unsuccessfully with an error code of 1. tests fail, the program will exit unsuccessfully with an error code of 1.
An empty string will cause an unsuccessful exit as none of its contents pass any
tests.
When invoked incorrectly, a debug message will be printed and the program will When invoked incorrectly, a debug message will be printed and the program will
exit with the appropriate exit with the appropriate
@ -33,9 +37,6 @@ error code.
.\" .\"
.SH CAVEATS .SH CAVEATS
None of an empty string\(cqs contents pass any of the tests, so the program will
exit unsuccessfully if one is specified.
There\(cqs no way of knowing which argument failed the test without re-testing There\(cqs no way of knowing which argument failed the test without re-testing
arguments individually. arguments individually.

View File

@ -4,14 +4,15 @@
.\" 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 STRCMP 1 2024-07-15 "Harakit X.X.X" .TH STRCMP 1
.SH NAME .SH NAME
strcmp \(en compare strings strcmp \(en compare strings
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
strcmp strcmp
.B string string... .RM [ string ]
.RB [ strings... ]
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
@ -20,18 +21,18 @@ Check whether string arguments are the same.
.SH DIAGNOSTICS .SH DIAGNOSTICS
The program will exit successfully if the strings are identical. Otherwise, it The program will exit successfully if the strings are identical. Otherwise, it
will exit with an error code less than 128 if a string passed has a lesser byte will exit with an error code of 1 if a string passed has a lesser byte value
value than one of the prior strings: than one of the prior strings:
.RS .RS
strcmp b a .R strcmp b a
.RE .RE
or with an error code greater than 128 if it has a greater byte value than one and with an error code of 255 if it has a greater byte value than one of the
of the prior strings: prior strings:
.RS .RS
strcmp a b .R strcmp a b
.RE .RE
When invoked incorrectly, a debug message will be printed and the program will When invoked incorrectly, a debug message will be printed and the program will
@ -53,8 +54,7 @@ been
This tool also handles integer comparisons and file scrutiny. These parts of its This tool also handles integer comparisons and file scrutiny. These parts of its
functionality have been broken out into multiple utilities. functionality have been broken out into multiple utilities.
This program\(cqs functionality may be performed on a POSIX-compliant system This program\(cqs functionality may be performed on a POSIX-compliant system with
with
.BR test (1p). .BR test (1p).
.\" .\"
.SH AUTHOR .SH AUTHOR

View File

@ -4,14 +4,18 @@
.\" 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 SWAB 1 2024-06-17 "Harakit X.X.X" .TH SWAB 1
.SH NAME .SH NAME
swab \(en swap bytes swab \(en swap bytes
.\" .\"
.SH SYNOPSIS .SH SYNOPSIS
swab swab
.RB [ -w\ word_size ] .RB ( -f )
.RB ( -w
.R [
.B word size
.R ])
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
@ -19,10 +23,13 @@ Swap the latter and former halves of a block of bytes.
.\" .\"
.SH OPTIONS .SH OPTIONS
.IP \fB-w\fP\ \fIword_size\fP .IP \fB-f\fP
Configures the word size; that is, the size in bytes of the block size on which Ignore SIGINT signal.
to operate. The default word size is 2. The word size must be cleanly divisible .IP \fB-w\fP
by 2, otherwise the block of bytes being processed can\(cqt be halved. 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 by 2, otherwise the block of bytes being processed can\(cqt be
halved.
.\" .\"
.SH EXAMPLES .SH EXAMPLES
@ -31,14 +38,13 @@ The following
line: line:
.RS .RS
printf 'hello world!\(rsn' | swab .R printf 'hello world!\(rsn' | swab
.RE .RE
.\" If you change this, make sure to change it in tests/bonsai/swab.mk too.
Produces the following output: Produces the following output:
.RS .RS
ehll oowlr!d .R ehll oowlr!d
.RE .RE
.\" .\"
.SH DIAGNOSTICS .SH DIAGNOSTICS
@ -50,9 +56,7 @@ error code.
.\" .\"
.SH RATIONALE .SH RATIONALE
This program was modeled and named after the This program was modeled and named after the conv=swab functionality specified
.B conv=swab
functionality specified
in the in the
.BR dd (1p) .BR dd (1p)
utility. It additionally allows the word size to be configured. utility. It additionally allows the word size to be configured.
@ -60,12 +64,6 @@ utility. It additionally allows the word size to be configured.
This functionality is useful for fixing the endianness of binary files produced This functionality is useful for fixing the endianness of binary files produced
on other machines. on other machines.
.\" .\"
.SH AUTHOR
Written by DTB
.MT trinity@trinity.moe
.ME .
.\"
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later

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 TRUE 1 2024-06-06 "Harakit X.X.X" .TH TRUE 1
.SH NAME .SH NAME
true \(en do nothing, successfully true \(en do nothing, successfully
.\" .\"

View File

@ -1,6 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.

View File

@ -1,13 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
OSLIB = build/o/libopenbsd.rlib
RUSTFLAGS += --extern openbsd=$(OSLIB)
$(OSLIB): src/libopenbsd.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=openbsd \
-o $@ src/libopenbsd.rs

684
src/dj.c
View File

@ -1,6 +1,5 @@
/* /*
* 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
@ -17,422 +16,437 @@
* 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 <ctype.h> /* isupper(3), tolower(3) */
#include <errno.h> /* errno */ #include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */ #include <fcntl.h> /* open(2) */
#include <stdbool.h> /* bool */
#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) */
#include <sysexits.h> /* EX_OK, EX_OSERR, 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),
* pledge(2), unveil(2), optarg, optind, STDIN_FILENO, * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
* STDOUT_FILENO */ extern int errno;
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR */
char *program_name = "dj";
/* dj uses two structures that respectively correspond to the reading and /* dj uses two structures that respectively correspond to the reading and
* 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) */
} ep[2]; /* "engineered pipe"; also "extended play", for the deejay */
/* Additionally, the following global variables are used to store user options.
*/
/* (-a) */ static int align; /* Only the lower 8b are used but align is
* negative if no alignment is being done. */
/* (-c) */ static int count; /* 0 if dj(1) runs until no more reads are
* possible. */
/* ASCII field separator delimited statistics */
static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034";
/* human-readable statistics */
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
/* pointer to chosen formatting */
/* (-H) */ static char *fmt_output; /* fmt_asv (default) or fmt_human (-H) */
/* (-dq) */ static char debug; /*
* -d increments dj -qq | 0 - no diagnostic output whatsoever
* -q decrements dj -q | 1 - typical output without
* | notifications on partial reads or
* | writes
* dj | 2 - typical output (default)
* dj -d | 3 - verbose status messages */
/* (-n) */ static char noerror; /* 0 - exits on partial reads or writes
* (default)
* 1 - retries on partial reads/writes
* (-n) */
/* Non-configurable defaults. */
#define bs_default 1024 /* GNU dd(1) default; twice POSIX but a neat 2^10 */
static char *program_name = "<no argv[0]>";
static char *stdin_name = "<stdin>"; static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>"; static char *stdout_name = "<stdout>";
static int read_flags = O_RDONLY; /* These flags are consistent with Busybox */
static int write_flags = O_WRONLY | O_CREAT; /* dd(1). */
static int creat_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH /* Macro to set defaults for user-configurable options. */
| S_IWOTH; /* Consistent with touch(1p). */ #define setdefaults do{ \
static int read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */ align = -1; \
static int write_flags = O_WRONLY | O_CREAT; count = 0; \
debug = 2; \
fmt_output = fmt_asv; \
noerror = 0; \
ep[0].fl = read_flags; \
Io_setdefaults(&ep[0]); \
ep[1].fl = write_flags; \
Io_setdefaults(&ep[1]); }while(0)
#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)
/* Completes one Io block read */ /* 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{ \
Io_buffree(&(io)[0]); \
Io_buffree(&(io)[1]); \
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)));
}
/* Frees *io's buffer. Returns io. */
static struct Io * static struct Io *
Io_read(struct Io *io) { Io_buffree(struct Io *io){
int t;
assert(io->bs > 0); free(io->buf);
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;
} }
/* Completes one Io block write */ /* Fills the unused portion of io's buffer with padding, updating io->bufuse.
* Returns io. */
static struct Io * static struct Io *
Io_write(struct Io *io) { Io_bufrpad(struct Io *io, int padding){
int t;
assert(io->bufuse > 0); memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
assert(io->bufuse <= io->bs); io->bufuse = io->bs;
if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno;
t = 0;
} else if (t > 0) {
(void)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; 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 static int
oserr(char *e, int n) { /* program_name: [failing component:] error */ Io_fdclose(struct Io *io){
(void)fprintf(stderr, "%s: ", program_name);
if (e != NULL) { (void)fprintf(stderr, "%s: ", e); } return fdisstd(io->fd)
(void)fprintf(stderr, "%s\n", strerror(n)); ? 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, (counter-intuitively)
* returning -1 if successful and a sysexits.h exit code if an unrecoverable
* error occurred. io->buf will be cleared of useful bytes and io->seek will
* be set to zero to indicate the seek occurred. */
static int
Io_fdseek(struct Io *io){
int (*op)(int, void *, size_t);
if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1)
return -1;
/* repeated code to get the condition out of the loop */
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);
}else
return EX_SOFTWARE;
io->bufuse = 0;
return -1;
}
/* 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;
}
/* Sets the variables in a struct *io to the defaults. Identifies the read/
* write ends of the "pipe" by checking io->fl. Returns io. */
static struct Io *
Io_setdefaults(struct Io *io){
io->bs = bs_default;
io->buf = NULL;
io->bytes = 0;
io->fd = (io->fl == read_flags) ? STDIN_FILENO : STDOUT_FILENO;
io->fn = (io->fl == read_flags) ? stdin_name : stdout_name;
io->prec = 0;
io->rec = 0;
io->seek = 0;
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));
io->bytes += t;
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));
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, accessing debug, ep, and fmt_output. */
static int static void
fprintio(FILE *stream, char *fmt, struct Io io[2]) { output(void){
return fprintf(
stream,
fmt,
io[0].rec,
io[0].prec,
io[1].rec,
io[1].prec,
io[0].bytes,
io[1].bytes
);
}
/* To be assigned to main:fmt and used with printio(). */ if(debug >= 1)
static char *fmt_asv = fprintf(stderr, fmt_output,
"%d" /* io[0].rec */ "\037" /* ASCII US */ ep[0].rec, ep[0].prec, ep[1].rec, ep[1].prec,
"%d" /* io[0].prec */ "\036" /* ASCII RS */ ep[0].bytes, ep[1].bytes);
"%d" /* io[1].rec */ "\037" /* ASCII US */
"%d" /* io[1].prec */ "\035" /* ASCII GS */ return;
"%d" /* io[0].bytes */ "\036" /* ASCII RS */ }
"%d" /* io[1].bytes */ "\034" /* ASCII FS */
"\n"
;
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
/* Parses the string s to an integer, returning either the integer or in the /* Parses the string s to an integer, returning either the integer or in the
* case of an error a negative integer. This is used for argument parsing * case of an error a negative integer. This is used for argument parsing
* (e.g. -B [int]) in dj and no negative integer would be valid anyway. */ * (e.g. -B [int]) in dj and no negative integer would be valid anyway. */
static long static long
parse(char *s) { parse(char *s){
long r; long r;
errno = 0; errno = 0;
r = strtol(s, &s, 0); r = strtol(s, &s, 0);
return (*s == '\0' /* no chars left unparsed */ && errno == 0) ? r : -1; return (*s == '\0' /* no chars left unparsed */ && errno == 0)
? r
: -1;
} }
static int static int
usage(char *argv0) { usage(void){
(void)fprintf(
stderr, fprintf(stderr, "Usage: %s (-AdfHqQ) (-a [byte]) (-c [count])\n"
"Usage: %s [-Hn] [-a byte] [-c count]\n" "\t(-i [input file]) (-b [input block size]) (-s [input offset])\n"
"\t[-i file] [-b block_size] [-s offset]\n" "\t(-o [output file]) (-B [output block size]) (-S [output offset])\n",
"\t[-o file] [-B block_size] [-S offset]\n", program_name);
argv0
);
return EX_USAGE; return EX_USAGE;
} }
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 count; /* -1 if dj(1) runs until no more reads are possible */
char *fmt; /* set to fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io (in or out) being modified */
bool retry; /* false if exits on partial reads or writes */
struct Io io[2 /* { in, out } */];
#ifdef __OpenBSD__
if (pledge("cpath rpath stdio unveil wpath", NULL) == -1) {
return oserr(NULL, errno);
}
#endif
/* Set defaults. */
align = -1;
count = -1;
fmt = fmt_asv;
retry = 0;
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;
}
if (argc > 0) {
int c; int c;
int i;
setdefaults;
if(argc > 0){
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:Ab:B:c:di:hHnqs:S:o:")) != -1)
switch (c) { switch(c){
case 'i': case 'o': /* input, output */ case 'i': case 'o':
i = (c == 'o'); i = (c == 'o');
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */
/* optarg == "-" (stdin/stdout) */ ep[i].fd = (i == 0) ? STDIN_FILENO : STDOUT_FILENO;
if (optarg[0] == '-' && optarg[1] == '\0') { ep[i].fn = (i == 0) ? stdin_name : stdout_name;
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
break; break;
} else { }else if(Io_fdopen(&ep[i], optarg) != -1)
int fd;
#ifdef __OpenBSD__
if (unveil(optarg, i == 0 ? "r" : "wc") == -1) {
return oserr(NULL, errno);
}
#endif
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(ep);
} return oserr(optarg);
case 'A': align = '\0'; break;
return oserr(optarg, errno); case 'd': ++debug; break;
/* UNREACHABLE */ case 'n': noerror = 1; break;
case 'n': retry = 1; break; /* retry failed reads once */ case 'H': fmt_output = fmt_human; break;
case 'H': fmt = fmt_human; break; /* human-readable output */ case 'q': --debug; break;
case 'a': /* input buffer padding */ case 'a':
if (optarg[0] == '\0' || optarg[1] == '\0') { if(optarg[0] != '\0' && optarg[1] == '\0'){
align = optarg[0]; align = optarg[0];
break; break;
} }
/* FALLTHROUGH */ /* FALLTHROUGH */
case 'c': /* number of reads */ case 'c': case 'b': case 's': case 'B': case 'S':
case 'b': case 'B': /* input/output block size */ if(c == 'c' && (count = parse(optarg)) >= 0)
case 's': case 'S': /* (s)kip/(S)eek in input/output */ break;
if (c == 'c' && (count = parse(optarg)) >= 0) { break; } i = isupper(c);
c = tolower(c);
i = (c >= 'A' && c <= 'Z'); if((c == 'b' && (ep[i].bs = parse(optarg)) > 0)
c |= 0x20; /* 0b 0010 0000 (ASCII) make lowercase */ || (c == 's' && (ep[i].seek = parse(optarg)) >= 0))
break;
if ( /* if -b or -s is parsed out correctly */
(c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0)
) { break; } /* don't error */
/* FALLTHROUGH */ /* FALLTHROUGH */
default: default:
return usage(program_name); terminate(ep);
return usage();
} }
} }
if(debug >= 3)
fprintf(stderr,
"argv0=%s\n"
"in=%s\tibs=%d\tskip=%ld\talign=%hhx\tcount=%d\n"
"out=%s\tobs=%d\tseek=%ld\tdebug=%2d\tnoerror=%d\n",
program_name,
ep[0].fn, ep[0].bs, ep[0].seek, align, count,
ep[1].fn, ep[1].bs, ep[1].seek, debug, noerror);
if(argc > optind){
terminate(ep);
return usage();
} }
#ifdef __OpenBSD__ for(i = 0; i <= 1; ++i){
if (unveil(NULL, NULL) == -1) { return oserr(NULL, errno); } if(Io_bufalloc(&ep[i]) == NULL){
#endif fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, ep[i].bs);
assert(io->fd != STDIN_FILENO || io->fl == read_flags); terminate(ep);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
if (argc > optind) { return usage(program_name); }
for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
/* buffer allocation */
if ((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL) {
(void)fprintf(
stderr, "%s: Failed to allocate %zd bytes\n",
program_name, io[i].bs
);
return EX_OSERR; return EX_OSERR;
} }else if(ep[i].seek > 0)
switch(Io_fdseek(&ep[i])){
/* easy seeking */ case EX_OK:
if (!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1) { output();
io[i].seek = 0; terminate(ep);
return EX_OK;
} }
} }
assert(io[1].bufuse == 0); /* requirement for hard seeking */ do{ /* read */
Io_read(&ep[0]);
/* hard seeking; t is io[1].bufuse, before Io_write subtracts from it */ if(!noerror && ep[0].bufuse == 0)
for(size_t t; io[1].seek > 0; io[1].seek -= (t - io[1].bufuse)) { Io_read(&ep[0]); /* second chance */
(void)memset( if(ep[0].bufuse == 0) /* that's all she wrote */
io[1].buf, '\0', /* set buf to all nulls */ break;
(t = io[1].bufuse = MIN(io[1].bs, io[1].seek)) /* saturate block */ else if(ep[0].bufuse < ep[0].bs){
); ++ep[0].prec;
if(debug >= 2){
if (Io_write(&io[1])->bufuse == t && !retry && io[1].error == 0) { fprintf(stderr, "%s: Partial read:\n\t", program_name);
(void)Io_write(&io[1]); /* second chance */ output();
} }
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&ep[0], align);
}else
++ep[0].rec;
if (io[1].error != 0) { return oserr(io[1].fn, io[1].error); } /* write */
do{ if(ep[1].bs > ep[0].bs){ /* io[1].bs > io[0].bs */
Io_bufxapp(&ep[1], &ep[0]);
if(ep[0].bs + ep[1].bufuse <= ep[1].bs && count != 1)
continue; /* we could write more */
}else
Io_bufxfer(&ep[1], &ep[0], MIN(ep[0].bufuse, ep[1].bs));
if (io[1].bufuse == t) { break; } /* all writes failed! */ c = ep[1].bufuse;
} Io_write(&ep[1]);
if(!noerror && ep[1].bufuse == c)
io[1].bufuse = 0; /* reset after hard seek */ Io_write(&ep[1]); /* second chance */
if(c == ep[1].bufuse){ /* no more love */
if (io[1].seek > 0) { /* hard seeking failed */
(void)fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno);
}
for ( ;
count == -1 || count > 0;
count -= (count != -1) /* decrement if counting */
) {
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 && !retry && io[0].error == 0) {
(void)Io_read(&io[0]); /* second chance */
}
assert(io[0].bufuse >= t);
if (io[0].bufuse == t) { break; } /* that's all she wrote */
if (/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs) {
(void)fprintf(stderr, "%s: Partial read:\n\t", program_name);
(void)fprintio(stderr, fmt, io);
if (!retry) { count = 1; }
if (align >= 0) {
/* fill the rest of the ibuf with padding */
(void)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 != -1); /* increment if counting */
continue;
}
}
assert(io[0].bufuse > 0);
while (io[0].bufuse > 0) { /* write */
if (io[0].bs <= io[1].bs) {
int n;
(void)memcpy( /* saturate obuf */
io[1].buf, io[0].buf,
(io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))
);
/* permute the copied units out of ibuf */
(void)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 */
(void)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 */
(void)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; /* obuf not saturated - we could write more */
}
}
{ /* writes actually happen, or die */
size_t t;
t = io[1].bufuse;
if (Io_write(&io[1])->bufuse == t
&& !retry
&& io[1].error == 0) {
(void)Io_write(&io[1]); /* second chance */
}
assert(io[1].error == 0 || io[1].bufuse == t);
/* if the Io_writes errored, bufuse wouldn't have changed, and
* the error will be reported at the end of the read/write
* loop */
assert(io[1].bufuse <= t);
if (io[1].bufuse == t) { /* no more love */
count = 1; count = 1;
break; break;
}else if(c > ep[1].bufuse && ep[1].bufuse > 0){
ep[1].prec += 1;
if(debug >= 2){
fprintf(stderr, "%s: Partial write:\n\t", program_name);
output();
} }
} if(!noerror)
count = 1;
}else if(ep[1].bufuse == 0 && c < ep[1].bs)
++ep[1].prec;
else
++ep[1].rec;
}while(ep[0].bufuse > 0);
}while(count == 0 || --count > 0);
if (0 < io[1].bufuse /* && io[1].bufuse < t */) { output();
(void)fprintf(stderr, "%s: Partial write:\n\t", program_name); terminate(ep);
(void)fprintio(stderr, fmt, io);
if(!retry) { count = 1; }
}
}
}
(void)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

@ -1,19 +1,9 @@
/* /*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media> * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: CC0 * SPDX-License-Identifier: CC0
* *
* This work is marked with CC0 1.0. To view a copy of this license, visit * This work is marked with CC0 1.0. To view a copy of this license, visit
* <http://creativecommons.org/publicdomain/zero/1.0>. * <http://creativecommons.org/publicdomain/zero/1.0>.
*/ */
#ifdef __OpenBSD__ int main() { return 1; }
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
#ifdef __OpenBSD__
pledge(NULL, NULL);
#endif
return 1;
}

View File

@ -26,90 +26,55 @@ extern crate getopt;
extern crate strerror; extern crate strerror;
extern crate sysexits; extern crate sysexits;
use getopt::GetOpt; use getopt::{ Opt, Parser };
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
fn main() { fn main() {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut d = '\u{1E}'.to_string(); /* ASCII record separator */ let mut d = 0x1E.to_string();
let mut optind = 1; let mut arg_parser = Parser::new(&argv, "d:");
#[cfg(target_os="openbsd")] { while let Some(opt) = arg_parser.next() {
let promises = Promises::new("stdio proc exec"); match opt {
if let Err(e) = pledge(Some(promises), None) { Ok(Opt('d', Some(arg))) => d = arg,
eprintln!("{}: {}", argv[0], e.strerror()); _ => {},
exit(EX_OSERR);
}
}
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
while let Some(opt) = argv.getopt("d:") {
match opt.opt() {
Ok("d") => {
/* delimiter */
d = opt.arg().unwrap();
optind = opt.ind();
},
_ => {
eprintln!("{}", usage);
exit(EX_USAGE);
}
}; };
} }
/* parse the specified index as a number we can use */ let index_arg = arg_parser.index();
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| { let command_arg = arg_parser.index() + 1;
argv.get(command_arg).unwrap_or_else(|| {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv[0]);
exit(EX_USAGE);
});
let index = argv[index_arg].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[1], e); eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR); exit(EX_DATAERR);
}); });
/* index of the argv[0] for the operator command */
let command_arg = optind as usize + 1;
/* argv[0] of the operator command */
let operator = argv.get(command_arg).unwrap_or_else(|| {
eprintln!("{}", usage);
exit(EX_USAGE);
});
/* read entire standard input into memory */
let mut buf = String::new(); let mut buf = String::new();
if let Err(e) = stdin().read_to_string(&mut buf) { let _ = stdin().read_to_string(&mut buf);
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
};
/* split the buffer by the delimiter (by default, '\u{1E}') */
let mut fields = buf.split(&d).collect::<Vec<&str>>(); let mut fields = buf.split(&d).collect::<Vec<&str>>();
/* collect arguments for the operator command */ let opts = argv
let command_args = argv
.iter() .iter()
.clone() .clone()
.skip(command_arg + 1) /* skip the command name */ .skip(command_arg + 1)
.collect::<Vec<&String>>(); .collect::<Vec<&String>>();
/* spawn the command to operate on the field */ let mut spawned = Command::new(argv.get(command_arg).unwrap())
let mut spawned = Command::new(operator) .args(opts)
.args(command_args) /* spawn with the specified arguments */
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) /* piped stdout to handle output ourselves */ .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap_or_else( |e| { .unwrap_or_else( |e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_UNAVAILABLE); exit(EX_UNAVAILABLE);
}); });
/* get field we want to pipe into spawned program */
let field = fields.get(index).unwrap_or_else(|| { let field = fields.get(index).unwrap_or_else(|| {
eprintln!( eprintln!(
"{}: {}: No such index in input", "{}: {}: No such index in input",
@ -119,10 +84,9 @@ fn main() {
exit(EX_DATAERR); exit(EX_DATAERR);
}); });
/* get the stdin of the newly spawned program and feed it the field val */
if let Some(mut child_stdin) = spawned.stdin.take() { if let Some(mut child_stdin) = spawned.stdin.take() {
let _ = child_stdin.write_all(field.as_bytes()); let _ = child_stdin.write_all(field.as_bytes());
drop(child_stdin); /* stay safe! drop your children! */ drop(child_stdin);
} }
let output = spawned.wait_with_output().unwrap_or_else(|e| { let output = spawned.wait_with_output().unwrap_or_else(|e| {
@ -130,27 +94,17 @@ fn main() {
exit(EX_IOERR); exit(EX_IOERR);
}); });
/* get the output with which the original field will be replaced */
let mut replace = output.stdout.clone(); let mut replace = output.stdout.clone();
/* pop trailing newline out if the input did not contain it */ if replace.pop() != Some(b'\n') { replace = output.stdout; }
if fields[index].chars().last() != Some('\n') /* no newline */
&& replace.pop() != Some(b'\n') { /* pop last char of replacement */
/* restore replacement to original command output if popped char was not
* a newline */
replace = output.stdout;
}
/* convert the output of the program to UTF-8 */
let new_field = String::from_utf8(replace).unwrap_or_else(|e| { let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e); eprintln!("{}: {}: {}", argv[0], argv[command_arg], e);
exit(EX_IOERR); exit(EX_IOERR);
}); });
/* store the new field in the old fields vector */
fields[index] = &new_field; fields[index] = &new_field;
/* fop it */
stdout().write_all( stdout().write_all(
fields.join(&d.to_string()).as_bytes() fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e| { ).unwrap_or_else(|e| {

95
src/getopt-rs/error.rs Normal file
View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use std::{ error, fmt };
use crate::ErrorKind::{ self, * };
/// A basic error type for [`Parser`](struct.Parser.html)
#[derive(Debug, Eq, PartialEq)]
pub struct Error {
culprit: char,
kind: ErrorKind,
}
impl Error {
/// Creates a new error using a known kind and the character that caused the
/// issue.
pub fn new(kind: ErrorKind, culprit: char) -> Self {
Self { culprit, kind }
}
/// Returns the [`ErrorKind`](enum.ErrorKind.html) for this error.
pub fn kind(self) -> ErrorKind {
self.kind
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
MissingArgument => write!(
f,
"option requires an argument -- {:?}",
self.culprit,
),
UnknownOption => write!(f, "unknown option -- {:?}", self.culprit),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/// What kinds of errors [`Parser`](struct.Parser.html) can return.
#[derive(Debug, Eq, PartialEq)]
pub enum ErrorKind {
/// An argument was not found for an option that was expecting one.
MissingArgument,
/// An unknown option character was encountered.
UnknownOption,
}

72
src/getopt-rs/lib.rs Normal file
View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//! # getopt
//!
//! `getopt` provides a minimal, (essentially) POSIX-compliant option parser.
pub use crate::{
error::Error,
errorkind::ErrorKind,
opt::Opt,
parser::Parser,
result::Result
};
mod error;
mod errorkind;
mod opt;
mod parser;
mod result;
#[cfg(test)]
mod tests;

89
src/getopt-rs/opt.rs Normal file
View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use std::fmt;
/// A single option.
///
/// For `Opt(x, y)`:
/// - `x` is the character representing the option.
/// - `y` is `Some` string, or `None` if no argument was expected.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use getopt::Opt;
///
/// // args = ["program", "-abc", "foo"];
/// # let args: Vec<String> = vec!["program", "-abc", "foo"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let optstring = "ab:c";
/// let mut opts = getopt::Parser::new(&args, optstring);
///
/// assert_eq!(Opt('a', None), opts.next().transpose()?.unwrap());
/// assert_eq!(Opt('b', Some("c".to_string())), opts.next().transpose()?.unwrap());
/// assert_eq!(None, opts.next().transpose()?);
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Opt(pub char, pub Option<String>);
impl fmt::Display for Opt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Opt({:?}, {:?})", self.0, self.1)
}
}

382
src/getopt-rs/parser.rs Normal file
View File

@ -0,0 +1,382 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use std::collections::HashMap;
use crate::{ error::Error, errorkind::ErrorKind, opt::Opt, result::Result };
/// The core of the `getopt` crate.
///
/// `Parser` is implemented as an iterator over the options present in the given
/// argument vector.
///
/// The method [`next`](#method.next) does the heavy lifting.
///
/// # Examples
///
/// ## Simplified usage:
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use getopt::Opt;
///
/// // args = ["program", "-abc", "foo"];
/// # let args: Vec<String> = vec!["program", "-abc", "foo"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let mut opts = getopt::Parser::new(&args, "ab:c");
///
/// assert_eq!(Some(Opt('a', None)), opts.next().transpose()?);
/// assert_eq!(1, opts.index());
/// assert_eq!(Some(Opt('b', Some("c".to_string()))), opts.next().transpose()?);
/// assert_eq!(2, opts.index());
/// assert_eq!(None, opts.next());
/// assert_eq!(2, opts.index());
/// assert_eq!("foo", args[opts.index()]);
/// # Ok(())
/// # }
/// ```
///
/// ## A more idiomatic example:
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use getopt::Opt;
///
/// // args = ["program", "-abc", "-d", "foo", "-e", "bar"];
/// # let mut args: Vec<String> = vec!["program", "-abc", "-d", "foo", "-e", "bar"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let mut opts = getopt::Parser::new(&args, "ab:cd:e");
///
/// let mut a_flag = false;
/// let mut b_flag = String::new();
/// let mut c_flag = false;
/// let mut d_flag = String::new();
/// let mut e_flag = false;
///
/// loop {
/// match opts.next().transpose()? {
/// None => break,
/// Some(opt) => match opt {
/// Opt('a', None) => a_flag = true,
/// Opt('b', Some(arg)) => b_flag = arg.clone(),
/// Opt('c', None) => c_flag = true,
/// Opt('d', Some(arg)) => d_flag = arg.clone(),
/// Opt('e', None) => e_flag = true,
/// _ => unreachable!(),
/// },
/// }
/// }
///
/// let new_args = args.split_off(opts.index());
///
/// assert_eq!(true, a_flag);
/// assert_eq!("c", b_flag);
/// assert_eq!(false, c_flag);
/// assert_eq!("foo", d_flag);
/// assert_eq!(true, e_flag);
///
/// assert_eq!(1, new_args.len());
/// assert_eq!("bar", new_args.first().unwrap());
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Eq, PartialEq)]
pub struct Parser {
opts: HashMap<char, bool>,
args: Vec<Vec<char>>,
index: usize,
point: usize,
}
impl Parser {
/// Create a new `Parser`, which will process the arguments in `args`
/// according to the options specified in `optstring`.
///
/// For compatibility with
/// [`std::env::args`](https://doc.rust-lang.org/std/env/fn.args.html),
/// valid options are expected to begin at the second element of `args`, and
/// `index` is
/// initialised to `1`.
/// If `args` is structured differently, be sure to call
/// [`set_index`](#method.set_index) before the first invocation of
/// [`next`](#method.next).
///
/// `optstring` is a string of recognised option characters; if a character
/// is followed by a colon (`:`), that option takes an argument.
///
/// # Note:
/// Transforming the OS-specific argument strings into a vector of `String`s
/// is the sole responsibility of the calling program, as it involves some
/// level of potential information loss (which this crate does not presume
/// to handle unilaterally) and error handling (which would complicate the
/// interface).
pub fn new(args: &[String], optstring: &str) -> Self {
let optstring: Vec<char> = optstring.chars().collect();
let mut opts = HashMap::new();
let mut i = 0;
let len = optstring.len();
while i < len {
let j = i + 1;
if j < len && optstring[j] == ':' {
opts.insert(optstring[i], true);
i += 1;
} else {
opts.insert(optstring[i], false);
}
i += 1;
}
Self {
opts,
// "explode" the args into a vector of character vectors, to allow
// indexing
args: args.iter().map(|e| e.chars().collect()).collect(),
index: 1,
point: 0,
}
}
/// Return the current `index` of the parser.
///
/// `args[index]` will always point to the the next element of `args`; when
/// the parser is
/// finished with an element, it will increment `index`.
///
/// After the last option has been parsed (and [`next`](#method.next) is
/// returning `None`),
/// `index` will point to the first non-option argument.
pub fn index(&self) -> usize {
self.index
}
// `point` must be reset to 0 whenever `index` is changed
/// Modify the current `index` of the parser.
pub fn set_index(&mut self, value: usize) {
self.index = value;
self.point = 0;
}
/// Increment the current `index` of the parser.
///
/// This use case is common enough to warrant its own optimised method.
pub fn incr_index(&mut self) {
self.index += 1;
self.point = 0;
}
}
impl Iterator for Parser {
type Item = Result<Opt>;
/// Returns the next option, if any.
///
/// Returns an [`Error`](struct.Error.html) if an unexpected option is
/// encountered or if an
/// expected argument is not found.
///
/// Parsing stops at the first non-hyphenated argument; or at the first
/// argument matching "-";
/// or after the first argument matching "--".
///
/// When no more options are available, `next` returns `None`.
///
/// # Examples
///
/// ## "-"
/// ```
/// use getopt::Parser;
///
/// // args = ["program", "-", "-a"];
/// # let args: Vec<String> = vec!["program", "-", "-a"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let mut opts = Parser::new(&args, "a");
///
/// assert_eq!(None, opts.next());
/// assert_eq!("-", args[opts.index()]);
/// ```
///
/// ## "--"
/// ```
/// use getopt::Parser;
///
/// // args = ["program", "--", "-a"];
/// # let args: Vec<String> = vec!["program", "--", "-a"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let mut opts = Parser::new(&args, "a");
///
/// assert_eq!(None, opts.next());
/// assert_eq!("-a", args[opts.index()]);
/// ```
///
/// ## Unexpected option:
/// ```
/// use getopt::Parser;
///
/// // args = ["program", "-b"];
/// # let args: Vec<String> = vec!["program", "-b"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let mut opts = Parser::new(&args, "a");
///
/// assert_eq!(
/// "unknown option -- 'b'".to_string(),
/// opts.next().unwrap().unwrap_err().to_string()
/// );
/// ```
///
/// ## Missing argument:
/// ```
/// use getopt::Parser;
///
/// // args = ["program", "-a"];
/// # let args: Vec<String> = vec!["program", "-a"]
/// # .into_iter()
/// # .map(String::from)
/// # .collect();
/// let mut opts = Parser::new(&args, "a:");
///
/// assert_eq!(
/// "option requires an argument -- 'a'".to_string(),
/// opts.next().unwrap().unwrap_err().to_string()
/// );
/// ```
fn next(&mut self) -> Option<Result<Opt>> {
if self.point == 0 {
/*
* Rationale excerpts below taken verbatim from "The Open Group Base
* Specifications Issue 7, 2018 edition", IEEE Std 1003.1-2017
* (Revision of IEEE Std 1003.1-2008).
* Copyright © 2001-2018 IEEE and The Open Group.
*/
/*
* If, when getopt() is called:
* argv[optind] is a null pointer
* *argv[optind] is not the character '-'
* argv[optind] points to the string "-"
* getopt() shall return -1 without changing optind.
*/
if self.index >= self.args.len()
|| self.args[self.index].is_empty()
|| self.args[self.index][0] != '-'
|| self.args[self.index].len() == 1
{
return None;
}
/*
* If:
* argv[optind] points to the string "--"
* getopt() shall return -1 after incrementing index.
*/
if self.args[self.index][1] == '-' && self.args[self.index].len() == 2 {
self.incr_index();
return None;
}
// move past the starting '-'
self.point += 1;
}
let opt = self.args[self.index][self.point];
self.point += 1;
match self.opts.get(&opt) {
None => {
if self.point >= self.args[self.index].len() {
self.incr_index();
}
Some(Err(Error::new(ErrorKind::UnknownOption, opt)))
}
Some(false) => {
if self.point >= self.args[self.index].len() {
self.incr_index();
}
Some(Ok(Opt(opt, None)))
}
Some(true) => {
let arg: String = if self.point >= self.args[self.index].len() {
self.incr_index();
if self.index >= self.args.len() {
return Some(Err(Error::new(
ErrorKind::MissingArgument,
opt,
)));
}
self.args[self.index].iter().collect()
} else {
self.args[self.index]
.clone()
.split_off(self.point)
.iter()
.collect()
};
self.incr_index();
Some(Ok(Opt(opt, Some(arg))))
}
}
}
}

59
src/getopt-rs/result.rs Normal file
View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use std::result;
use crate::error::Error;
/// A specialized `Result` type for use with [`Parser`](struct.Parser.html)
pub type Result<T> = result::Result<T, Error>;

228
src/getopt-rs/tests.rs Normal file
View File

@ -0,0 +1,228 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
* The Clear BSD License
*
* Copyright © 2017-2023 David Wildasin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::{Opt, Parser};
macro_rules! basic_test {
($name:ident, $expect:expr, $next:expr, [$($arg:expr),+], $optstr:expr) => (
#[test]
fn $name() -> Result<(), String> {
let expect: Option<Opt> = $expect;
let args: Vec<String> = vec![$($arg),+]
.into_iter()
.map(String::from)
.collect();
let next: Option<String> = $next;
let mut opts = Parser::new(&args, $optstr);
match opts.next().transpose() {
Err(error) => {
return Err(format!("next() returned {:?}", error))
},
Ok(actual) => if actual != expect {
return Err(
format!("expected {:?}; got {:?}", expect, actual)
)
},
};
match next {
None => if opts.index() < args.len() {
return Err(format!(
"expected end of args; got {:?}", args[opts.index()]
))
},
Some(n) => if args[opts.index()] != n {
return Err(format!(
"next arg: expected {:?}; got {:?}",
n,
args[opts.index()]
))
},
};
Ok(())
}
)
}
#[rustfmt::skip] basic_test!(
blank_arg, None, Some(String::new()), ["x", ""], "a"
);
#[rustfmt::skip] basic_test!(
double_dash, None, Some("-a".to_string()), ["x", "--", "-a", "foo"], "a"
);
#[rustfmt::skip] basic_test!(no_opts_1, None, None, ["x"], "a");
#[rustfmt::skip] basic_test!(
no_opts_2, None, Some("foo".to_string()), ["x", "foo"], "a"
);
#[rustfmt::skip] basic_test!(
no_opts_3, None, Some("foo".to_string()), ["x", "foo", "-a"], "a"
);
#[rustfmt::skip] basic_test!(
single_dash, None, Some("-".to_string()), ["x", "-", "-a", "foo"], "a"
);
#[rustfmt::skip] basic_test!(
single_opt,
Some(Opt('a', None)),
Some("foo".to_string()),
["x", "-a", "foo"],
"a"
);
#[rustfmt::skip] basic_test!(
single_optarg,
Some(Opt('a', Some("foo".to_string()))),
None,
["x", "-a", "foo"],
"a:"
);
macro_rules! error_test {
($name:ident, $expect:expr, [$($arg:expr),+], $optstr:expr) => (
#[test]
fn $name() -> Result<(), String> {
let expect: String = $expect.to_string();
let args: Vec<String> = vec![$($arg),+]
.into_iter()
.map(String::from)
.collect();
let mut opts = Parser::new(&args, $optstr);
match opts.next() {
None => {
return Err(format!(
"unexpected successful response: end of options"
))
},
Some(Err(actual)) => {
let actual = actual.to_string();
if actual != expect {
return Err(
format!("expected {:?}; got {:?}", expect, actual)
);
}
},
Some(Ok(opt)) => {
return Err(
format!("unexpected successful response: {:?}", opt)
)
},
};
Ok(())
}
)
}
#[rustfmt::skip] error_test!(
bad_opt,
"unknown option -- 'b'",
["x", "-b"],
"a"
);
#[rustfmt::skip] error_test!(
missing_optarg,
"option requires an argument -- 'a'",
["x", "-a"],
"a:"
);
#[test]
fn multiple() -> Result<(), String> {
let args: Vec<String> = vec!["x", "-abc", "-d", "foo", "-e", "bar"]
.into_iter()
.map(String::from)
.collect();
let optstring = "ab:d:e".to_string();
let mut opts = Parser::new(&args, &optstring);
macro_rules! check_result {
($expect:expr) => {
let expect: Option<Opt> = $expect;
match opts.next().transpose() {
Err(error) => {
return Err(format!("next() returned {:?}", error));
},
Ok(actual) => {
if actual != expect {
return Err(
format!("expected {:?}; got {:?}", expect, actual)
);
}
}
};
};
}
check_result!(Some(Opt('a', None)));
check_result!(Some(Opt('b', Some("c".to_string()))));
check_result!(Some(Opt('d', Some("foo".to_string()))));
check_result!(Some(Opt('e', None)));
check_result!(None);
Ok(())
}
#[test]
fn continue_after_error() {
let args: Vec<String> = vec!["x", "-z", "-abc"]
.into_iter()
.map(String::from)
.collect();
let optstring = "ab:d:e".to_string();
for _opt in Parser::new(&args, &optstring) {
// do nothing, should not panic
}
}

View File

@ -27,51 +27,42 @@ extern crate strerror;
extern crate sysexits; extern crate sysexits;
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, EX_USAGE }; use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
/* list of SI prefixes */
const LIST: [(u32, &str); 10] = [ const LIST: [(u32, &str); 10] = [
(3, "k"), /* kilo */ (3, "k"),
(6, "M"), /* mega */ (6, "M"),
(9, "G"), /* giga */ (9, "G"),
(12, "T"), /* tera */ (12, "T"),
(15, "P"), /* peta */ (15, "P"),
(18, "E"), /* exa */ (18, "E"),
(21, "Z"), /* zetta */ (21, "Z"),
(24, "Y"), /* yotta */ (24, "Y"),
(27, "R"), /* ronna */ (27, "R"),
(30, "Q"), /* quetta */ (30, "Q")
]; ];
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> { fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
/* preserve decimal places in output by casting to a float */
let mut out = (input as f64, (0_u32, ""));
if input < 1000 { return Ok(out); } /* too low to convert */ let mut out = (input as f64, (0_u32, ""));
if input < 1000 { return Ok(out); }
for (n, p) in LIST { for (n, p) in LIST {
let c = match 10_u128.checked_pow(n) { let c = match 10_u128.checked_pow(n) {
Some(c) => c, Some(c) => c,
None => { /* too big for the laws of computing :( */ None => {
return Err(format!("10^{}: Integer overflow", n.to_string())); return Err(format!("10^{}: Integer overflow", n.to_string()));
}, },
}; };
match c.cmp(&input) { match c.cmp(&input) {
Ordering::Less => { /* c < input */ Ordering::Less => {
/* the program will keep assigning out every loop until either
* the list runs out of higher prefix bases or the input is
* greater than the prefix base */
out = (input as f64 / c as f64, (n, p)); out = (input as f64 / c as f64, (n, p));
}, },
Ordering::Equal => { /* c == input */ Ordering::Equal => {
return Ok((input as f64 / c as f64, (n, p))); return Ok((input as f64 / c as f64, (n, p)));
}, },
Ordering::Greater => {}, /* c > input */ Ordering::Greater => {},
}; };
} }
@ -80,22 +71,7 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
if let Some(_) = argv.get(1) {
eprintln!("Usage: {}", argv[0]);
return ExitCode::from(EX_USAGE as u8);
}
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
let mut buf = String::new(); let mut buf = String::new();
while let Ok(_) = stdin().read_line(&mut buf) { while let Ok(_) = stdin().read_line(&mut buf) {
if buf.is_empty() { return ExitCode::SUCCESS; } if buf.is_empty() { return ExitCode::SUCCESS; }
@ -120,7 +96,6 @@ fn main() -> ExitCode {
let si_prefix = format!("{}B", prefix.1); let si_prefix = format!("{}B", prefix.1);
/* round output number to one decimal place */
let out = ((number * 10.0).round() / 10.0).to_string(); let out = ((number * 10.0).round() / 10.0).to_string();
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())

82
src/intcmp.c Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023 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 <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* strtol(3), size_t, EXIT_FAILURE */
#include <unistd.h> /* getopt(3), optind */
#include <sysexits.h>
/* 0b00? */ /* Equal | -e | 0b001 | 1 */
#define EQUAL 0x01 /* Greater | -g | 0b010 | 2 */
/* 0b0?0 */ /* Greater or Equal | -ge | 0b011 | 3 */
#define GREATER 0x02 /* Less | -l | 0b100 | 4 */
/* 0b?00 */ /* Less or Equal | -le | 0b101 | 5 */
#define LESS 0x04 /* Inequal (Greater or Less) | -gl | 0b110 | 6 */
static char *program_name = "intcmp";
int main(int argc, char *argv[]){
int c;
size_t i;
unsigned char mode;
int r; /* reference integer */
mode = 0;
if(argc < 3)
goto usage;
while((c = getopt(argc, argv, "egl")) != -1)
switch(c){
case 'e': mode |= EQUAL; break;
case 'g': mode |= GREATER; break;
case 'l': mode |= LESS; break;
default: goto usage;
}
if(optind + 2 /* ref cmp */ > argc){
usage: fprintf(stderr,
"Usage: %s (-eghl) [integer] [integer...]\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}
i = optind;
do{ r = c;
c = strtol(argv[i], &argv[i], 10);
if(*argv[i] != '\0' || errno != 0){
fprintf(stderr, "%s: argument #%d: Invalid integer\n",
argv[0], (int)i);
return EX_USAGE;
}
if(i == optind)
continue;
/* rule enforcement; if a mode isn't permitted and the numbers
* correspond to it, return 1 */
if( (!(mode & EQUAL) && r == c)
|| (!(mode & GREATER) && r > c)
|| (!(mode & LESS) && r < c))
return 1;
}while(++i < argc);
return 0;
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* 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,
process::ExitCode
};
extern crate getopt;
extern crate sysexits;
use getopt::GetOpt;
use sysexits::EX_USAGE;
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use strerror::StrError;
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", s);
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
let mut e = false; /* args can be == */
let mut g = false; /* args can be > */
let mut l = false; /* args can be < */
let mut optind = 0;
if argv.len() < 3 { return usage(&argv[0]); }
while let Some(opt) = argv.getopt("egl") {
match opt.opt() {
Ok("e") => e = true,
Ok("g") => g = true,
Ok("l") => l = true,
_ => return usage(&argv[0]),
}
optind = opt.ind();
}
if !e & !g & !l { return usage(&argv[0]); }
if argv.len() - optind < 2 /* see usage */ { return usage(&argv[0]); }
let mut prev: Option<usize> = None; /* no previous operand */
let mut currn: usize;
for arg in argv.iter().skip(optind) { /* iterate operands */
match arg.parse::<usize>() { /* parse current operand */
Ok(n) => currn = n,
Err(e) => {
eprintln!("{}: {}: {}", &argv[0], arg, e);
return ExitCode::from(EX_USAGE as u8);
}
}
if let Some(prevn) = prev { /* if there was a previous opr., test */
if (!e && prevn == currn)
|| (!g && prevn > currn)
|| (!l && prevn < currn)
{ return ExitCode::FAILURE; }
}
prev = Some(currn); /* there is a previous operand */
}
ExitCode::SUCCESS
}

View File

@ -1,202 +0,0 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* 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::ffi::{ c_int, c_char, CString, CStr };
/* binding to getopt(3p) */
extern "C" {
static mut optarg: *mut c_char;
static mut _opterr: c_int;
static mut optind: c_int;
static mut optopt: c_int;
fn getopt(
___argc: c_int,
___argv: *const *mut c_char,
__shortopts: *const c_char,
) -> c_int;
}
#[derive(Clone, Debug)]
pub enum OptError {
MissingArg(String),
UnknownOpt(String),
}
#[derive(Clone, Debug)]
pub struct Opt {
arg: Option<String>, /* option argument */
ind: *mut i32, /* option index */
opt: Result<String, OptError>, /* option option */
}
impl Opt {
pub fn arg(&self) -> Option<String> { self.arg.clone() }
/* sets optarg if default is desired */
pub fn arg_or(&self, default: impl std::fmt::Display) -> String {
default.to_string()
}
/* makes matching the output of this method more bearable */
pub fn opt(&self) -> Result<&str, OptError> {
self.opt.as_ref().map(|o| o.as_str()).map_err(OptError::clone)
}
/* From getopt(3p):
*
* The variable optind is the index of the next element of the argv[]
* vector to be processed. It shall be initialized to 1 by the system, and
* getopt() shall update it when it finishes with each element of argv[].
* If the application sets optind to zero before calling getopt(), the
* behavior is unspecified. When an element of argv[] contains multiple
* option characters, it is unspecified how getopt() determines which
* options have already been processed. */
pub fn ind(&self) -> usize { unsafe { *self.ind as usize } }
/* this is patently terrible and is only happening because Im stubborn */
pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } }
}
/* function signature */
pub trait GetOpt {
fn getopt(&self, optstring: &str) -> Option<Opt>;
}
impl GetOpt for Vec<String> {
fn getopt(&self, optstring: &str) -> Option<Opt> {
let c_strings: Vec<_> = self
.iter()
.cloned()
.map(|x| CString::new(x).unwrap().into_raw())
.collect();
/* god knows what this does */
let boxed = Box::into_raw(c_strings.into_boxed_slice());
let argv = boxed as *const *mut c_char;
/* operations are separated out so that everything lives long enough */
let opts = CString::new(optstring).unwrap().into_raw();
let len = self.len() as c_int;
unsafe {
let ret = match getopt(len, argv, opts) {
/* From getopt(3p):
*
* The getopt() function shall return the next option character
* specified on the command line.
*
* A <colon> (':') shall be returned if getopt() detects a
* missing argument and the first character of optstring was a
* <colon> (':').
*
* A <question-mark> ('?') shall be returned if getopt()
* encounters an option character not in optstring or detects a
* missing argument and the first character of optstring was not
* a <colon> (':').
*
* Otherwise, getopt() shall return -1 when all command line
* options are parsed. */
58 => { /* ASCII ':' */
Some(Opt {
arg: None,
ind: std::ptr::addr_of_mut!(optind),
/* error containing option */
opt: Err(OptError::MissingArg(optopt.to_string())),
})
},
63 => { /* ASCII '?' */
Some(Opt {
arg: None,
ind: std::ptr::addr_of_mut!(optind),
/* error containing option */
opt: Err(OptError::UnknownOpt(optopt.to_string())),
})
},
/* From getopt(3p):
*
* If, when getopt() is called:
*
* argv[optind] is a null pointer
* *argv[optind] is not the character -
* argv[optind] points to the string "-"
*
* getopt() shall return -1 without changing optind. If:
*
* argv[optind] points to the string "--"
*
* getopt() shall return -1 after incrementing optind. */
-1 => return None,
opt => {
let arg: Option<String>;
if optarg.is_null() { arg = None; }
else {
arg = Some(CStr::from_ptr(optarg)
.to_string_lossy()
.into_owned());
}
Some(Opt {
arg,
ind: std::ptr::addr_of_mut!(optind),
/* I didnt need to cast this before; I rewrote the
* pointer logic and now I do
*
* I dont know why this is */
opt: Ok((opt as u8 as char).to_string()),
})
},
};
/* delloc argv (something online said I should do this) */
let _ = Box::from_raw(boxed);
return ret;
}
}
}
/* tests (good) */
#[cfg(test)]
mod tests {
use GetOpt;
#[test]
fn testing() {
let argv: Vec<String> = ["test", "-b", "-f", "arg", "-o", "arg"]
.iter()
.map(|s| s.to_string())
.collect();
while let Some(opt) = argv.getopt(":abf:o:") {
match opt.opt() {
Ok("a") => assert_eq!(opt.ind(), 1),
Ok("b") => assert_eq!(opt.ind(), 2),
Ok("f") | Ok("o") => {
assert_eq!(opt.arg(), Some("arg".into()));
},
_ => assert!(false),
};
}
if let Some(opt) = argv.getopt("abc:") {
opt.clone().set_ind(1);
assert_eq!(opt.ind(), 1);
}
}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* 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::{
ffi::{ CString, c_char },
io::Error,
ptr::null,
};
mod openbsd {
use std::ffi::{ c_char, c_int };
extern "C" {
pub fn pledge(arg1: *const c_char, arg2: *const c_char) -> c_int;
pub fn unveil(arg1: *const c_char, arg2: *const c_char) -> c_int;
pub fn __errno() -> *mut c_int;
}
}
pub struct Promises(*const c_char);
impl Promises {
pub fn new(promises: &str) -> Self {
let p = CString::new(promises).unwrap();
Promises(p.into_raw() as *const c_char)
}
}
pub fn pledge(
promises: Option<Promises>, execpromises: Option<Promises>
) -> Result<(), Error> {
/* From pledge(2):
*
* Passing NULL to promises or execpromises specifies to not change
* the current value. */
let arg1 = promises.unwrap_or(Promises(null())).0;
let arg2 = execpromises.unwrap_or(Promises(null())).0;
unsafe {
match openbsd::pledge(arg1, arg2) {
-1 => Err(Error::from_raw_os_error(*openbsd::__errno())),
0 => Ok(()),
_ => panic!(), /* unreachable */
}
}
}
pub struct UnveilPerms(CString);
impl UnveilPerms {
pub fn new(permissions: Vec<char>) -> Self {
if permissions.is_empty() {
return UnveilPerms(CString::new("").unwrap());
}
UnveilPerms(
CString::new(permissions.iter().collect::<String>()).unwrap()
)
}
}
pub fn unveil(
path: Option<&str>,
permissions: Option<UnveilPerms>,
) -> Result<(), Error> {
let path_c = path.map(CString::new).map(Result::unwrap);
let arg1 = path_c.map(|p| p.into_raw() as *const c_char).unwrap_or(null());
let arg2 = permissions
.map(|p| p.0.into_raw() as *const c_char)
.unwrap_or(null());
unsafe {
match openbsd::unveil(arg1, arg2) {
-1 => Err(Error::from_raw_os_error(*openbsd::__errno())),
0 => Ok(()),
_ => panic!(), /* unreachable */
}
}
}

236
src/mm.c Normal file
View File

@ -0,0 +1,236 @@
/*
* 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;
}

230
src/mm.rs
View File

@ -1,230 +0,0 @@
/*
* 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 };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")]
use openbsd::{
Promises,
UnveilPerms,
pledge,
unveil,
};
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]);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("cpath rpath stdio unveil wpath");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
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()),
};
}
}
#[cfg(target_os="openbsd")] {
for input in &ins {
let perms = UnveilPerms::new(vec!['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
for output in &outs {
let perms = UnveilPerms::new(vec!['c', 'w']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
if let Err(e) = unveil(None, None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
if ins.is_empty() && outs.is_empty() && argv.len() > optind {
eprintln!("Usage: {}", 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,6 +1,5 @@
/* /*
* Copyright (c) 2023 DTB <trinity@trinity.moe> * Copyright (c) 2023 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
@ -17,72 +16,46 @@
* 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 <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin, #include <stdio.h> /* fprintf(3), fputs(3), getc(3), putc(3), stdin, stdout,
* stdout, EOF */ * EOF */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */ #include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <unistd.h> /* pledge(2), getopt(3) */ #include <unistd.h> /* getopt(3) */
#include <sysexits.h>
char *program_name = "npc"; int main(int argc, char *argv[]){
static int
ioerr(char *argv0) {
perror(argv0);
return EX_IOERR;
}
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-et]\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
int c; int c;
char showend = 0; /* print a dollar sign before each newline */ char showend;
char showtab = 0; /* prints tab characters in caret notation */ char showtab;
#ifdef __OpenBSD__ showend = 0;
if (pledge("stdio", NULL) == -1) { showtab = 0;
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
if (argc > 0) { if(argc > 0)
program_name = argv[0]; while((c = getopt(argc, argv, "et")) != -1)
switch(c){
while ((c = getopt(argc, argv, "et")) != -1) {
switch (c){
case 'e': showend = 1; break; case 'e': showend = 1; break;
case 't': showtab = 1; break; case 't': showtab = 1; break;
default: return usage(program_name); default: goto usage;
}
}
} }
if (argc > optind) { return usage(program_name); } if(argc > optind){
usage: fprintf(stderr, "Usage: %s (-eht)\n", argv[0]);
while ((c = getc(stdin)) != EOF) { return EX_USAGE;
if ((c & 0x80) != 0 && fputs("M-", stdout) == EOF) {
return ioerr(argv[0]);
} }
switch (c ^ 0x80 /* 0b 1000 0000 */) { while((c = getc(stdin)) != EOF){
case 0x7f: /* ASCII DEL (127d) */ if((c & 0x80) != 0)
if(fputs("^?", stdout) == EOF) { return ioerr(argv[0]); } fputs("M-", stdout);
switch(c ^ 0x80 /* 0b 1000 0000 */){
case 0x7f: fputs("^?", stdout);
break; break;
case '\n': case '\n': if(showend)
if (showend && fputc('$', stdout) == EOF) { putc('$', stdout);
return ioerr(argv[0]);
}
default: default:
if (c >= ' ' || c == '\n' || (!showtab && c == '\t')) { if(c >= ' ' || c == '\n' || (!showtab && c == '\t'))
if (fputc(c, stdout) == EOF) { return ioerr(argv[0]); } putc(c, stdout);
} else if (fprintf(stdout, "^%c", c + '@') < 0) { else
return ioerr(argv[0]); fprintf(stdout, "^%c", c + '@');
}
} }
} }

View File

@ -56,14 +56,8 @@ extern crate sysexits;
use sysexits::EX_DATAERR; use sysexits::EX_DATAERR;
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use strerror::StrError;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[derive(Clone, PartialEq, PartialOrd, Debug)] #[derive(Clone, PartialEq, PartialOrd, Debug)]
/* enum CalcType is a type containing operations used in the calculator */ // enum CalcType is a type containing operations used in the calculator
enum CalcType { enum CalcType {
Add, Add,
Subtract, Subtract,
@ -123,8 +117,8 @@ struct EvaluationError {
code: i32, code: i32,
} }
/* Im no math nerd but I want the highest possible approximation of 0.9 // Im no math nerd but I want the highest possible approximation of 0.9
* repeating and it seems this can give it to me */ // repeating and it seems this can give it to me
const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0; const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0;
fn eval( fn eval(
@ -139,7 +133,7 @@ fn eval(
return Ok((stack, oper)); return Ok((stack, oper));
} }
/* Split the input into tokens. */ // Split the input into tokens.
let mut toks: VecDeque<CalcType> = input let mut toks: VecDeque<CalcType> = input
.split_whitespace() .split_whitespace()
.rev() .rev()
@ -189,7 +183,7 @@ fn eval(
Ok((stack, oper)) Ok((stack, oper))
} }
/* Round a float to the given precision level */ // Round a float to the given precision level
fn round_precise(value: &f64, precision: usize) -> f64 { fn round_precise(value: &f64, precision: usize) -> f64 {
let multiplier = 10_f64.powi(precision as i32); let multiplier = 10_f64.powi(precision as i32);
(value * multiplier).round() / multiplier (value * multiplier).round() / multiplier
@ -197,22 +191,13 @@ fn round_precise(value: &f64, precision: usize) -> f64 {
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
let mut stack = VecDeque::new(); let mut stack = VecDeque::new();
let mut buf = String::new(); let mut buf = String::new();
/* Set floating-point precision for correcting rounding errors based on // Set floating-point precision for correcting rounding errors based on
* machine epsilon */ // machine epsilon
let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize; let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize;
if argv.get(1).is_none() { /* read from stdin */ if argv.get(1).is_none() {
while let Ok(_) = stdin().read_line(&mut buf) { while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack) { match eval(&buf.trim(), stack) {
Ok(s) => { Ok(s) => {
@ -234,13 +219,12 @@ fn main() -> ExitCode {
}, },
}; };
} }
} else { /* read from argv */ } else {
/* join argv into an owned String joined by spaces minus argv[0] */
let input = argv let input = argv
.iter() .iter()
.skip(1) .skip(1)
.map(|x| x.to_owned()) .map(|x| x.to_owned())
.collect::<Vec<_>>() .collect::<Vec<String>>()
.join(" "); .join(" ");
match eval(&input, stack) { match eval(&input, stack) {
@ -249,7 +233,7 @@ fn main() -> ExitCode {
let val = match stack.iter().last() { let val = match stack.iter().last() {
Some(v) => v, Some(v) => v,
None => return ExitCode::SUCCESS, None => return ExitCode::from(0),
}; };
println!("{}", round_precise(val, precision).to_string()) println!("{}", round_precise(val, precision).to_string())
@ -260,5 +244,5 @@ fn main() -> ExitCode {
}, },
}; };
} }
ExitCode::SUCCESS ExitCode::from(0)
} }

View File

@ -17,100 +17,91 @@
* 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 <stdio.h> /* fprintf(3), stderr, NULL */ #include <stdio.h> /* fprintf(3), stderr, NULL */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */ #include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <string.h> /* memset(3), strchr(3) */ #include <string.h> /* memset(3), strchr(3) */
#include <sysexits.h> /* EX_OSERR, EX_USAGE */ #ifndef EX_USAGE
#include <unistd.h> /* access(3), getopt(3), pledge(2), unveil(2), F_OK, R_OK, # include <sysexits.h>
* W_OK, X_OK */ #endif
#include <unistd.h> /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */
#include <sys/stat.h> /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR, #include <sys/stat.h> /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR,
* S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK, * S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK,
* S_ISUID, S_ISVTX */ * S_ISUID, S_ISVTX */
char *program_name = "scrut"; static char args[] = "bcdefghkprsuwxLS";
#define OPTS "bcdefgkprsuwxLS" static char ops[(sizeof args) / (sizeof *args)];
/* this is an array so main:sel's size can be known at compile time */ static char *program_name = "scrut";
static char opts[] = OPTS;
static int int main(int argc, char *argv[]){
usage(char *argv0) { struct stat buf;
(void)fprintf(stderr, "Usage: %s [-" OPTS "] file...\n", argv0); int c;
size_t i;
return EX_USAGE;
}
int main(int argc, char *argv[]) {
char sel[(sizeof opts) / (sizeof *opts)];
program_name = argv[0] == NULL ? program_name : argv[0];
#ifdef __OpenBSD__
if (pledge("rpath stdio unveil", NULL) == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
if (argc < 2) { return usage(program_name); }
{ /* option parsing */
char *p; char *p;
memset(sel, '\0', sizeof sel); if(argc < 2)
for (int c; (c = getopt(argc, argv, opts)) != -1;) { goto usage;
if ((p = strchr(opts, c)) == NULL) { return usage(argv[0]); }
else { memset(ops, '\0', sizeof ops);
assert(p - opts < sizeof sel / sizeof *sel); /* bounds check */ while((c = getopt(argc, argv, args)) != -1)
sel[p - opts] = c; if((p = strchr(args, c)) == NULL)
} goto usage;
else
ops[p - args] = c;
/* straighten out ops */
for(i = 0, p = ops; i < (sizeof ops) / (sizeof *ops); ++i)
if(ops[i] != '\0'){
*p = ops[i];
if(&ops[i] != p++)
ops[i] = '\0';
} }
/* straighten out selections; permute out nulls */ if(optind == argc)
p = sel; goto usage;
for (size_t i = 0; i < (sizeof sel) / (sizeof *sel); ++i) {
if (sel[i] != '\0') {
*p = sel[i];
if (&sel[i] != p++) { sel[i] = '\0'; }
}
}
}
if (optind == argc) { return usage(argv[0]); } argv += optind;
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
for (argv += optind ; *argv != NULL; argv = &argv[1]) {
struct stat buf;
#ifdef __OpenBSD__
if (unveil(*argv, "r") == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) {
return EXIT_FAILURE; /* doesn't exist or isn't stattable */ return EXIT_FAILURE; /* doesn't exist or isn't stattable */
}
for (size_t i = 0; sel[i] != '\0'; ++i) { for(i = 0; ops[i] != '\0'; ++i)
if ( if(ops[i] == 'e')
(sel[i] == 'b' && !S_ISBLK(buf.st_mode)) continue;
|| (sel[i] == 'c' && !S_ISCHR(buf.st_mode)) else if(ops[i] == 'h'){
|| (sel[i] == 'd' && !S_ISDIR(buf.st_mode)) usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n",
|| (sel[i] == 'e' && 0) argv[0] == NULL
|| (sel[i] == 'f' && !S_ISREG(buf.st_mode)) ? program_name
|| (sel[i] == 'g' && !(buf.st_mode & S_ISGID)) : argv[0],
|| (sel[i] == 'k' && !(buf.st_mode & S_ISVTX)) args);
|| (sel[i] == 'p' && !S_ISFIFO(buf.st_mode))
|| (sel[i] == 'r' && access(*argv, R_OK) != 0) return EX_USAGE;
|| (sel[i] == 'u' && !(buf.st_mode & S_ISUID)) }else if(
|| (sel[i] == 'w' && access(*argv, W_OK) != 0) (ops[i] == 'b'
|| (sel[i] == 'x' && access(*argv, X_OK) != 0) && !S_ISBLK(buf.st_mode))
|| (sel[i] == 'L' && !S_ISLNK(buf.st_mode)) || (ops[i] == 'c'
|| (sel[i] == 'S' && !S_ISSOCK(buf.st_mode)) && !S_ISCHR(buf.st_mode))
) { return EXIT_FAILURE; } || (ops[i] == 'd'
} && !S_ISDIR(buf.st_mode))
} || (ops[i] == 'f'
&& !S_ISREG(buf.st_mode))
|| (ops[i] == 'g'
&& !(buf.st_mode & S_ISGID))
|| (ops[i] == 'k'
&& !(buf.st_mode & S_ISVTX))
|| (ops[i] == 'p'
&& !S_ISFIFO(buf.st_mode))
|| (ops[i] == 'r'
&& access(*argv, R_OK) != 0)
|| (ops[i] == 'u'
&& !(buf.st_mode & S_ISUID))
|| (ops[i] == 'w'
&& access(*argv, W_OK) != 0)
|| (ops[i] == 'x'
&& access(*argv, X_OK) != 0)
|| (ops[i] == 'L'
&& !S_ISLNK(buf.st_mode))
|| (ops[i] == 'S'
&& !S_ISSOCK(buf.st_mode)))
return EXIT_FAILURE;
}while(*++argv != NULL);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -19,21 +19,17 @@
#include <ctype.h> #include <ctype.h>
#include <stddef.h> /* NULL */ #include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3), perror(3) */ #include <stdio.h> /* fprintf(3) */
#include <stdlib.h> /* size_t, EXIT_FAILURE */ #include <stdlib.h> /* EXIT_FAILURE */
#include <string.h> /* strcmp(3) */ #include <string.h> /* strcmp(3) */
#include <sysexits.h> /* EX_OSERR, EX_USAGE */ #include <sysexits.h>
#ifdef __OpenBSD__ static char *program_name = "str";
# include <unistd.h> /* pledge(2) */
#endif
char *program_name = "str";
static struct { static struct {
char *name; char *name;
int (*f)(int); int (*f)(int);
} ctypes[] = { }ctypes[] = {
{ "isalnum", isalnum }, { "isalnum", isalnum },
{ "isalpha", isalpha }, { "isalpha", isalpha },
{ "isblank", isblank }, { "isblank", isblank },
@ -45,52 +41,35 @@ static struct {
{ "isprint", isprint }, { "isprint", isprint },
{ "ispunct", ispunct }, { "ispunct", ispunct },
{ "isspace", isspace }, { "isspace", isspace },
{ "isupper", isupper }, { "isupper", isupper }
{ NULL, NULL } /* marks end */
}; };
static int int main(int argc, char *argv[]){
usage(char *argv0) { int ctype;
(void)fprintf(stderr, "Usage: %s type string...\n", argv0); int i;
int r;
if(argc >= 3){
for(ctype = 0; ctype < (sizeof ctypes) / (sizeof *ctypes);
++ctype)
if(strcmp(argv[1], ctypes[ctype].name) == 0)
goto pass;
}
fprintf(stderr, "Usage: %s [type] [string...]\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE; return EX_USAGE;
}
pass: for(argv += 2, r = 1; *argv != NULL; ++argv)
int main(int argc, char *argv[]) { for(i = 0; argv[0][i] != '\0'; ++i)
size_t ctype; // selected from ctypes.h; index of ctype /* First checks if argv[0][i] is valid ASCII; ctypes(3)
int retval; // initially fail but becomes success on the first valid char * don't handle non-ASCII.
program_name = argv[0] == NULL ? program_name : argv[0]; * This is bad. */
if((unsigned char)argv[0][i] < 0x80 && !ctypes[ctype].f(argv[0][i]))
#ifdef __OpenBSD__ return 1;
if (pledge("stdio", NULL) == -1) { else
perror(program_name); r = 0;
return EX_OSERR;
} return r;
#endif
if (argc < 3) { return usage(program_name); }
for ( /* iterate ctypes */
ctype = 0;
ctypes[ctype].f != NULL /* break at the end of ctypes */
&& strcmp(argv[1], ctypes[ctype].name) != 0; /* break at match */
++ctype
);
if (ctypes[ctype].f == NULL) { return usage(argv[0]); }
/* iterate args */
for (argv += 2, retval = EXIT_FAILURE; *argv != NULL; ++argv) {
for (size_t i = 0; argv[0][i] != '\0'; ++i) { /* iterate arg bytes */
/* First checks if argv[0][i] is valid ASCII; ctypes(3) don't
* handle non-ASCII. This is bad. */
if(
(unsigned char)argv[0][i] < 0x80 // argv[0][i] is ASCII,
&& !ctypes[ctype].f(argv[0][i]) // so use ctypes(3)
) { return EXIT_FAILURE; }
else { retval = EXIT_SUCCESS; }
}
}
return retval;
} }

View File

@ -1,62 +1,24 @@
/* #include <stdio.h> /* fprintf(3), stderr */
* Copyright (c) 2023 DTB <trinity@trinity.moe> #include <stdlib.h> /* EXIT_FAILURE */
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media> #include <sysexits.h>
* 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 <stdio.h> /* fprintf(3), perror(3), stderr */
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__ static char *program_name = "strcmp";
# include <unistd.h> /* pledge(2) */
#endif
char *program_name = "strcmp"; int main(int argc, char *argv[]){
int i;
int main(int argc, char *argv[]) {
unsigned int i;
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
if (argc < 3) {
(void)fprintf(
stderr,
"Usage: %s string string...\n",
argv[0] == NULL ? program_name : argv[0]
);
if(argc < 3){
fprintf(stderr, "Usage: %s [string] [string...]\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE; return EX_USAGE;
} }
for (; *argv[1] != '\0'; ++argv[1]) { for(; *argv[1] != '\0'; ++argv[1])
for (i = 2; i < argc; ++i) { for(i = 2; i < argc; ++i)
/* a former string has a greater byte value */ if(*argv[i-1] > *argv[i])
if (*argv[i-1] > *argv[i]) {
return 1; return 1;
/* a latter string has a greater byte value */ else if(*argv[i-1] < *argv[i]++)
} else if (*argv[i-1] < *argv[i]++) {
return -1; /* actually 255 */ return -1; /* actually 255 */
}
}
}
return EX_OK; return 0;
} }

View File

@ -1,6 +1,5 @@
/* /*
* 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
@ -19,90 +18,72 @@
use std::{ use std::{
env::args, env::args,
io::{ stdin, stdout, Error, Read, Write }, io::{ stdin, stdout, Error, ErrorKind, Read, Write },
process::ExitCode, process::ExitCode,
vec::Vec vec::Vec
}; };
extern crate getopt; extern crate getopt;
use getopt::{ Opt, Parser };
extern crate sysexits; extern crate sysexits;
extern crate strerror; use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
use getopt::GetOpt; fn oserr(s: &str, e: Error) -> ExitCode {
use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE }; eprintln!("{}: {}", s, e);
use strerror::StrError;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
fn oserr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_OSERR as u8) ExitCode::from(EX_OSERR as u8)
} }
fn ioerr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_IOERR as u8)
}
fn usage(s: &str) -> ExitCode { fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-w word_size]", s); eprintln!("Usage: {} (-f) (-w [wordsize])", s);
ExitCode::from(EX_USAGE as u8) ExitCode::from(EX_USAGE as u8)
} }
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut buf: Vec<u8> = Vec::new();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
return oserr(&argv[0], e);
}
}
let mut buf: Vec<u8> = Vec::new(); // holds the sequence getting swabbed
let mut input = stdin(); let mut input = stdin();
let mut output = stdout().lock(); let mut output = stdout().lock();
let mut optind: usize = 1; // argv[0]
let mut wordsize: usize = 2; // default; mimics dd(1p) conv=swab
while let Some(opt) = argv.getopt("w:") { let mut opts = Parser::new(&argv, "fw:");
match opt.opt() { let mut force = false;
Ok("w") => { let mut wordsize: usize = 2;
match opt.arg().unwrap().parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; }, loop {
match opts.next() {
None => break,
Some(opt) =>
match opt {
Ok(Opt('f', None)) => force = true,
Ok(Opt('w', Some(arg))) => {
match arg.parse::<usize>() {
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 {
match input.read(&mut buf) { match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8), // read nothing; bye Ok(0) => break ExitCode::from(EX_OK as u8),
Ok(v) if v == wordsize => { // read full block; swab Ok(v) if v == wordsize => {
let (left, right) = buf.split_at(v/2); let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right) if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) { .and_then(|_| output.write(&left)) {
break ioerr(&argv[0], e); break oserr(&argv[0], e)
} }
}, },
Ok(v) => { // partial read; partially write Ok(v) => {
if let Err(e) = output.write(&buf[..v]) { if let Err(e) = output.write(&buf[..v]) {
break ioerr(&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)
} }
} }

View File

@ -1,18 +1,9 @@
/* /*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media> * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: CC0 * SPDX-License-Identifier: CC0
* *
* This work is marked with CC0 1.0. To view a copy of this license, visit * This work is marked with CC0 1.0. To view a copy of this license, visit
* <http://creativecommons.org/publicdomain/zero/1.0>. * <http://creativecommons.org/publicdomain/zero/1.0>.
*/ */
#ifdef __OpenBSD__ int main() {}
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
#ifdef __OpenBSD__
pledge(NULL, NULL);
#endif
}

View File

@ -1,36 +0,0 @@
The testing suite contains two trees: the Bonsai tree and the POSIX tree:
.
├── README
├── bonsai/
│   ├── dj.mk
│   ├── false.mk
│   ├── fop.mk
│   └── ...
├── posix/
└── tests.mk
The Bonsai tree tests the functionality of Harakit utilities for regressions and
other issues relating to compliance to our standards of practice.
The POSIX tests are currently a work-in-progress. Their status in this
repository is uncertain.
Both sets of tests also inherit the environment set by the top-level Makefile,
which sets the BIN variable to the build/bin directory at the root of the
project; therefore, each binary is located at $(BIN)/tool for idiomatic access.
Each test contains a set of PHONY targets which are prefixed with the name of
the tool being tested and an underscore. The first target is tests, which
depends on all the other targets in the test file. These test files are each
included in the top Makefile, so they can be called from the root of the
repository. This also means that BIN can be set manually so that tests can be
run using make(1) inside of the tests directory:
$ make -f tests.mk BIN=../build/bin dj_tests
--
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.

View File

@ -1,48 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
/dev/full:
/dev/null:
.PHONY: dj_tests
dj_tests: dj_help dj_full dj_null # dj_skip_stdin
.PHONY: dj_full
# Linux has a /dev/full pseudodevice useful for testing errors.
dj_full: $(BIN)/dj /dev/full
case "$$(uname)" in \
Linux) \
! $(BIN)/dj -Hi /dev/zero -o /dev/full 2>&1 \
| tee /dev/stderr \
| xargs -I out test '1+0 > 0+0; 1024 > 0' = out \
;; \
esac
.PHONY: dj_help
dj_help: $(BIN)/dj
! $(BIN)/dj -h
.PHONY: dj_null
# Read nothing from /dev/null, write nothing to /dev/null.
dj_null: $(BIN)/dj /dev/null
$(BIN)/dj -Hi /dev/null -o /dev/null 2>&1 \
| tee /dev/stderr \
| xargs -I out test '0+0 > 0+0; 0 > 0' = out
# This test currently fails. This is probably due to dj(1) being stale relative
# to the main harakit branch. TODO: Reassess once the testing branch is merged.
# .PHONY: dj_skip_stdin
# # Test skipping stdin.
# dj_skip_stdin: $(BIN)/dj
# # Pipe 1024B of '\0' into dj(1); skip the first 24B; expect 1000B written.
# dd count=1 bs=1024 </dev/zero 2>/dev/null \
# | $(BIN)/dj -H -s 24 -o /dev/null 2>&1 \
# | tee /dev/stderr \
# | xargs -I out test '1+0 > 1+0; 1024 > 1000' = out

View File

@ -1,18 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: false_tests
false_tests: false_test false_help
.PHONY: false
false_test: $(BIN)/false
! $(BIN)/false
.PHONY: false_help
false_help: $(BIN)/false
! $(BIN)/false -h

View File

@ -1,31 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: fop_tests
fop_tests: fop_functionality fop_delimiter fop_help fop_fail
.PHONY: fop_help
fop_help: $(BIN)/fop
! $(BIN)/fop -h
.PHONY: fop_delimiter
fop_delimiter: $(BIN)/fop
test "$$(printf 'test1 test1 test1\n' | $(BIN)/fop -d' ' 2 sed 's/1/4/g')" \
= 'test1 test1 test4'
test "$$(printf 'meowsetwoofsetribbit\n' \
| $(BIN)/fop -d 'set' 1 sed 's/woof/meow/g')" = 'meowsetmeowsetribbit'
.PHONY: fop_fail
fop_fail: $(BIN)/fop
! printf 'test\n' | $(BIN)/fop 1 cat
! printf 'test\n' | $(BIN)/fop 'test' cat
! printf 'test\n' | $(BIN)/fop -d'test' cat
.PHONY: fop_functionality
fop_functionality: $(BIN)/fop
test "$$(printf 'test1\036test1\036test1\n' | $(BIN)/fop 1 sed 's/1/4/g')" \
= "$$(printf 'test1\036test4\036test1\n')"

View File

@ -1,32 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: hru_tests
hru_tests: hru_help hru_functionality hru_negative hru_regressions
.PHONY: hru_help
hru_help: $(BIN)/hru
! $(BIN)/hru -h
.PHONY: hru_functionality
hru_functionality: $(BIN)/hru
test "$$(printf '1234\n' | $(BIN)/hru)" = '1.2 kB'
test "$$(printf '0\n' | $(BIN)/hru)" = '0 B'
.PHONY: hru_negative
hru_negative: $(BIN)/hru
! printf '%s\n' '-1' | $(BIN)/hru
.PHONY: hru_regressions
hru_regressions: $(BIN)/hru
n=1; \
while true; \
do \
printf '%s\n' "$$n" | $(BIN)/hru || break; \
n="$$(($$n * 10))"; \
done; \
printf 'Max float: %s\n' "$$n"

View File

@ -1,67 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: intcmp_tests
intcmp_tests: intcmp_help intcmp_none intcmp_e intcmp_g intcmp_l intcmp_combined
.PHONY: intcmp_help
intcmp_help: $(BIN)/intcmp
! $(BIN)/intcmp -h
# These test that integer comparisons are working as they should. For the sake
# of readability (to facilitate faster skimming) these recipes follow a
# columned format:
# $binary -flags d d d d # op
# For flag meanings reference intcmp(1) (though they are somewhat self
# explanatory). d here refers to a decimal number; a mixture of 1s, 2s, and 3s
# (a particularly lovely number) arranged to demonstrate easily the operation
# under scrutiny. The commented op is the operation that is true for the given
# numbers. For example:
# $(BIN)/intcmp -e 3 3 3 3 # ==
# op here is ==; 3 == 3 == 3 == 3. The flag being used is -e, to test for
# equality, so this test should succeed.
# ! $(BIN)/intcmp -l 3 2 1 # >
# op here is >; 3 > 2 > 1. The flag being used is -l, to test for each integer
# being less than the next, so intcmp should fail - hence the ! at the start of
# the invocation. If this test failed, intcmp(1) would be confusing -l for -g,
# so that would be a good place to start looking for bugs.
.PHONY: intcmp_none
intcmp_none: $(BIN)/intcmp
! $(BIN)/intcmp 1 2
.PHONY: intcmp_e
intcmp_e: $(BIN)/intcmp
$(BIN)/intcmp -e 3 3 3 # ==
! $(BIN)/intcmp -e 1 2 3 # <
! $(BIN)/intcmp -e 3 2 1 # >
.PHONY: intcmp_g
intcmp_g: $(BIN)/intcmp
$(BIN)/intcmp -g 3 2 1 # >
! $(BIN)/intcmp -g 3 3 3 # ==
! $(BIN)/intcmp -g 1 2 3 # <
$(BIN)/intcmp -ge 3 3 1 # >=
! $(BIN)/intcmp -ge 1 2 3 # <
.PHONY: intcmp_l
intcmp_l: $(BIN)/intcmp
$(BIN)/intcmp -l 1 2 3 # <
! $(BIN)/intcmp -l 3 3 3 # ==
! $(BIN)/intcmp -l 3 2 1 # >
$(BIN)/intcmp -le 1 3 3 # <=
! $(BIN)/intcmp -le 3 2 1 # >
.PHONY: intcmp_combined
intcmp_combined: $(BIN)/intcmp
$(BIN)/intcmp -gl 1 2 3 # <
$(BIN)/intcmp -gl 3 2 1 # >
$(BIN)/intcmp -gl 1 3 1 # !=
! $(BIN)/intcmp -gl 3 3 3 # ==
$(BIN)/intcmp -egl 3 1 1 3 # >, ==, <
! $(BIN)/intcmp -egl foo # huh?

View File

@ -1,34 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: mm_tests
mm_tests: mm_args mm_help mm_stderr mm_remaining
.PHONY: mm_none
mm_none: $(BIN)/mm
test "$$(printf 'meow\n' | $(BIN)/mm)" = meow
.PHONY: mm_args
# mm(1) will error if positional arguments are given without -i or -o
mm_args: $(BIN)/mm
! $(BIN)/mm argument
.PHONY: mm_help
mm_help: $(BIN)/mm
! $(BIN)/mm -h
.PHONY: mm_stderr
# check if stderr is empty upon specifying -e
mm_stderr: $(BIN)/mm
test "$$(printf 'test\n' | $(BIN)/mm -e 2>&1 >/dev/null )" = "test"
.PHONY: mm_remaining
# check to make sure remaining arguments are used
mm_remaining: $(BIN)/mm
test "$$($(BIN)/mm -i README COPYING)" = "$$(cat README COPYING)"
$(BIN)/mm -i README -o /tmp/mm_test0 /tmp/mm_test1
diff /tmp/mm_test0 /tmp/mm_test1

View File

@ -1,90 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: npc_tests
npc_tests: npc_help npc_args npc_ascii
.PHONY: npc_help
npc_help: $(BIN)/npc
! $(BIN)/npc -h
.PHONY: npc_args
# arg parsing
npc_args:
$(BIN)/npc -e </dev/null
$(BIN)/npc -t </dev/null
$(BIN)/npc -et </dev/null
! $(BIN)/npc -et 5 </dev/null
.PHONY: npc_ascii
# Test 0x00 to 0x7f in input; in other words, the full 7b ASCII range.
npc_ascii: npc_ascii_controls npc_ascii_symbols npc_ascii_uppers # \
# npc_ascii_lowers
.PHONY: npc_ascii_controls
# (control characters)
npc_ascii_controls:
# The following test prints the bytes 0x00 (inclusive) through 0x20
# (exclusive) and pipes them through npc(1). npc(1) should then replace all
# non-printing, non-space (in the isspace(3p) sense) characters with their
# graphical carat-char counterparts (see the npc(1) man page). The head(1p)
# invocation then strips off everything past the first line (or past the
# first newline byte, 0x0A) and xargs(1p) is used to test(1p) the output
# against the known good answer.
# Immediately before that newline, 0x09 is printed - in ASCII, the
# horizontal tab. If xargs' -I option is used, tr(1p) should used to delete
# that tab. If the tab is left as part of input, OpenBSD's xargs(1)
# implementation has been observed to strip it along with the other
# trailing whitespace (the newline), but Busybox's and GNU's xargs(1)
# implementations have been observed to leave the tab in. All three
# implementations strip off the trailing tab if `-I` is not used. The POSIX
# specification for `-I` is ambiguous as to which behavior is correct.
# This comment is the result of much bewilderment and debugging.
# ASCII 0x00 to 0x0a (before the newline, due to xargs(1p) issues)
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); }' \
| $(BIN)/npc \
| head -n 1 \
| xargs test "^@^A^B^C^D^E^F^G^H" =
# ASCII 0x0a (otherwise the head|tail sequence won't work) to 0x1f
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| head -n 2 \
| tail -n 1 \
| xargs -I out test "^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_"
.PHONY: npc_ascii_symbols
# ASCII 0x1f to 0x3f (^_ and symbols)
npc_ascii_symbols:
# shell quoting olympics
awk 'BEGIN{ for (i = 31; i < 64; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| sed -e s"/\'/\\\'/g" -e 's/"/\\"/g' \
| xargs -I out test "^_ !\"#$$%&'()*+,-./0123456789:;<=>?" = out
.PHONY: npc_ascii_uppers
# ASCII 0x40 to 0x5f (uppercases)
npc_ascii_uppers:
awk 'BEGIN{ for (i = 64; i < 96; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| sed 's/\\/\\\\/' \
| xargs -I out test @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ = out
# This test is broken and will need closer inspection along with the npc(1)
# source.
# .PHONY: npc_ascii_lowers
# # ASCII 0x60 to 0x7f (lowercases)
# npc_ascii_lowers:
# awk 'BEGIN{ for (i = 96; i < 128; ++i) printf("%c", i); print }' \
# | $(BIN)/npc \
# | xargs -I out test "\`abcdefghijklmnopqrstuvwxyz{|}~^?" = out

View File

@ -1,43 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: rpn_tests
rpn_tests: rpn_help rpn_add rpn_sub rpn_mul rpn_div rpn_mod rpn_flr
.PHONY: rpn_help
rpn_help: $(BIN)/rpn
! $(BIN)/rpn -h
.PHONY: rpn_add
rpn_add: $(BIN)/rpn
test "$$($(BIN)/rpn 1 2 +)" -eq 3
test "$$($(BIN)/rpn 0.2 0.1 +)" = 0.3
.PHONY: rpn_sub
rpn_sub: $(BIN)/rpn
test "$$($(BIN)/rpn 23 5 -)" -eq 18
test "$$($(BIN)/rpn 0.3 0.1 -)" = 0.2
.PHONY: rpn_mul
rpn_mul: $(BIN)/rpn
test "$$($(BIN)/rpn 1.2 3 '*')" = 3.6
test "$$($(BIN)/rpn 0 3 '*')" -eq 0
.PHONY: rpn_div
rpn_div: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 /)" = 2.4
test "$$($(BIN)/rpn 3 0 /)" = inf
.PHONY: rpn_mod
rpn_mod: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 %)" -eq 2
test "$$($(BIN)/rpn 9 4 %)" -eq 1
.PHONY: rpn_flr
rpn_flr: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 //)" -eq 2
test "$$($(BIN)/rpn 9 4 //)" -eq 2

View File

@ -1,46 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: scrut_tests
scrut_tests: scrut_help scrut_options
.PHONY: scrut_help
scrut_help: $(BIN)/scrut
! $(BIN)/scrut -h
.PHONY: scrut_options
# scrut tests file attributes, but files of a certain attribute aren't
# guaranteed to be present on a system. This test checks all of the files in
# harakit and, if test(1p) says a file matches a certain attribute, then checks
# scrut.
# opts are space-delimited (for command splitting), sel is not
scrut_options: $(BIN)/scrut
set -e; \
opts="b c d e f g k p r s u w x L S"; \
sel=; \
find . -name .git -prune -o -print \
| while read -r f; do \
for opt in $$opts; \
do if ! printf "%s\n" $$sel | grep $$opt >/dev/null; then \
if test -$$opt "$$f"; then \
if ! $(BIN)/scrut -$$opt "$$f"; \
then printf "[!!] scrut -%s failed on %s.\n" \
$$opt "$$f"; \
fi; \
sel="$$sel$$opt"; \
printf "[OK] Tested scrut -%s using %s\n" \
$$opt "$$f"; \
fi; \
fi; \
done; \
if printf "%s\n" "$$opts" | sed 's/ //g' | xargs test "$$sel" =; \
then break; \
fi; \
done

View File

@ -1,20 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: str_tests
str_tests: str_help str_isalpha
.PHONY: str_help
str_help: $(BIN)/str
! $(BIN)/str -h
.PHONY: str_isalpha
str_isalpha: $(BIN)/str
$(BIN)/str isalpha c
! $(BIN)/str isalpha 3

View File

@ -1,31 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
NAME = strcmp
TARGET = $(NAME)_tests
BINARY = $(BIN)/$(NAME)
.PHONY: strcmp_tests
strcmp_tests: strcmp_equals strcmp_help strcmp_nocmp strcmp_unequals
.PHONY: strcmp_equals
strcmp_equals: $(BIN)/strcmp
$(BIN)/strcmp equals equals
$(BIN)/strcmp - -
.PHONY: strcmp_help
strcmp_help: $(BIN)/strcmp
! $(BIN)/strcmp -h
.PHONY: strcmp_nocmp
strcmp_nocmp: $(BIN)/strcmp
! $(BIN)/strcmp nocmp
.PHONY: strcmp_unequals
strcmp_unequals: $(BIN)/strcmp
! $(BIN)/strcmp unequals equals

View File

@ -1,22 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: swab_tests
swab_tests: swab_help swab_examples
.PHONY: swab_help
swab_help: $(BIN)/swab
! $(BIN)/swab -h
.PHONY: swab_examples
# These are the examples present in the man page.
swab_examples: $(BIN)/swab
printf 'hello world!\n' \
| $(BIN)/swab \
| xargs -I out test 'ehll oowlr!d' = out

View File

@ -1,19 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: true_tests
true_tests: true_test
.PHONY: true_help
true_help: $(BIN)/true
$(BIN)/true -h
.PHONY: true_test
true_test: $(BIN)/true
$(BIN)/true

24
tests/posix-compat.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
set -e
if ! ls Makefile >/dev/null 2>&1
then
printf '%s: Run this script in the root of the project.\n' "$0" 1>&2
exit 1
fi
printf "Starting POSIX compatibility testing.\n"
for utility in tests/posix/*; do
printf '%s: %s: Testing utility.\n' "$0" "$utility"
"$utility"
printf '\n'
done

View File

@ -1,22 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
# Strictly POSIX-compliant cat(1) implementation. See cat(1p)
for arg in "$@"; do
case "$arg" in
-u) args="$(printf '%s %s\n' "$args" "$arg")" ;;
*) args="$(printf -- '%s -i %s\n' "$args" "$arg")" ;;
esac
done
# See IEEE Std 1003.1-2017 3.282
# https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282
IFS=' '
mm $args

View File

@ -1,12 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
# Strictly POSIX-compliant false(1) implementation. See false(1p)
false "$@"

View File

@ -1,11 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
# Strictly POSIX-compliant true(1) implementation. See true(1p)
true "$@"

View File

@ -1,5 +0,0 @@
#!/bin/sh
set -ex
PATH="$PWD/bin:$PATH"

View File

@ -1,15 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
#TESTFILES != for file in tests/bonsai/*.mk tests/posix/*.mk; do printf '%s ' "$$file"; done;
TESTFILES != for file in tests/bonsai/*.mk; do printf '%s ' "$$file"; done;
TESTS != printf '%s\n' "$(TESTFILES)" | xargs -n1 basename \
| sed 's/\.mk/_tests/g'
include $(TESTFILES)