Compare commits

...

70 Commits

Author SHA1 Message Date
8eece4cf84
Makefile: removes deprecated variable 2025-03-28 19:38:51 -06:00
0e9127d417
tests: removed POSIX testing (fixes #163) 2025-02-24 23:28:52 -07:00
e6f747d635
Merge branch 'npc-broken-tests' 2025-02-24 23:19:47 -07:00
9b699d7298
Makefile, tests/bonsai/scrut.mk: cleanup 2025-02-24 23:17:10 -07:00
941f931f8b
Merge branch libfileis 2025-02-24 23:13:13 -07:00
f0eac562b3
tests: bonsai/npc.mk: fixed copyright header 2025-02-24 23:08:28 -07:00
138044e52f
Merge branch 'optimizations' 2025-02-24 23:04:10 -07:00
bd8bc0094d
rpn(1): fixes erroneous OpenBSD-gated import 2025-02-24 23:03:31 -07:00
99a99cdcb5
Merge branch 'style-c' 2025-02-24 22:59:50 -07:00
008c0624f6
tests: bonsai/npc.mk: removed test that is broken with GNU and BusyBox 2025-02-24 22:57:42 -07:00
982c67df13
intcmp(1), swab(1): consistent type annotations 2024-10-01 22:08:56 -06:00
c4cd2563f9
scrut(1): fixes failing write test 2024-09-11 03:26:00 -06:00
8bdb72ece8
false(1): fixes pledge(2) invocation 2024-09-11 03:19:56 -06:00
ba73f50527
true(1): removes NULL from commented imports 2024-09-11 03:19:23 -06:00
579bb98874
true(1): fixes pledge(2) invocation 2024-09-11 03:18:01 -06:00
8f1e570b50
strcmp(1): fixes pledge(2) invocation 2024-09-11 03:15:58 -06:00
a43daf2cf2
str(1): fixes pledge(2) invocation 2024-09-11 03:15:13 -06:00
f25a499ca0
scrut(1): fixes pledge(2) invocation 2024-09-11 03:14:36 -06:00
e0b5467fb6
npc(1): fixes pledge invocation 2024-09-11 03:13:38 -06:00
7498b283ce
mm(1): updates to use new unveil api 2024-09-11 02:32:32 -06:00
0b3ed37c38
libopenbsd.rs(3): makes using statically-allocated arrays possible 2024-09-11 02:32:03 -06:00
800a097903
swab(1): uses default for promises 2024-09-10 17:14:02 -06:00
1ccdc65d30
rpn(1): uses default for promises 2024-09-10 17:13:20 -06:00
df0a236f81
mm(1): uses default for Promises 2024-09-10 17:12:14 -06:00
e385d873ec
intcmp(1): uses default value for Promises 2024-09-10 17:11:22 -06:00
73a75a32df
hru(1): uses default Promises value for child processes 2024-09-10 17:10:26 -06:00
b8e5901d97
libopenbsd.rs(3): adds default value for Promises 2024-09-10 17:09:46 -06:00
f4bd4de2e4
fileis(1): various changes to make the code more efficient and idiomatic 2024-09-10 02:42:55 -06:00
f66fdef9c3
STYLE: avoid unbounded loops 2024-09-10 02:05:01 -06:00
b56a66f980
STYLE: fixes default case in example 2024-09-10 01:59:59 -06:00
f6559b464a
STYLE: fix minor issues 2024-09-07 12:51:18 -06:00
790b12fb40
STYLE: changes numbering scheme to transcend headers, fixes minor errors 2024-09-05 15:55:32 -06:00
4cb5ea78d7
STYLE: improves clarity, focus, distribution of items, adds more gripes, includes usage text style 2024-09-05 12:44:14 -06:00
62ce288524
rpn(1): makes rounding more efficient 2024-09-04 22:12:44 -06:00
731e62ee7e
rpn(1): made evaluation function more readable 2024-08-28 18:24:49 -06:00
f50bfeea92
fop(1): fixes panic on no arguments 2024-08-28 18:05:56 -06:00
8d00fa0afd
fop(1): minor formatting change 2024-08-28 00:46:32 -06:00
9f520df82b
strcmp(1): adds null unveil 2024-08-28 00:31:02 -06:00
41982c2f73
str(1): adds null unveil 2024-08-28 00:30:45 -06:00
6166a3ca36
npc(1): adds null unveil 2024-08-28 00:26:26 -06:00
efedcd3ae4
true(1), false(1): adds null unveils 2024-08-28 00:24:35 -06:00
8fd5bdf5a6
swab(1): adds null unveil 2024-08-28 00:15:15 -06:00
b946469da5
rpn(1): adds null unveil 2024-08-28 00:13:21 -06:00
06384c72fb
intcmp(1): adds null unveil 2024-08-28 00:09:40 -06:00
9aeadd8f8f
hru(1): adds null unveil 2024-08-27 23:57:12 -06:00
5b666d6858
fop(1): adds null unveil 2024-08-27 23:54:39 -06:00
b875aa1058
rpn(1): more improvements 2024-08-24 19:00:01 -06:00
cc53dab035
rpn(1): handles errors when writing to stdout 2024-08-24 18:18:27 -06:00
232629f4d3
hru(1): makes more efficient 2024-08-24 17:55:00 -06:00
ff4ff825bd
swab(1): changes disparate error handling functions to one function 2024-08-24 17:26:26 -06:00
5eb71e90a3
tests/bonsai: rpn.mk: tests the standard input 2024-08-24 17:22:57 -06:00
a0138be79e
rpn(1): refactor to make code more efficient, readable, and maintainable 2024-08-24 17:22:02 -06:00
150fa22f35
fop(1): changes casts to calls to .into() 2024-08-24 15:57:55 -06:00
f104598164
mm(1): updates error-handling functions and uncovers issue with error reporting 2024-08-24 15:53:58 -06:00
722679247a
hru(1): uses new sysexits 2024-08-23 14:31:02 -06:00
4db4160019
swab(1): uses new sysexits 2024-08-23 14:23:11 -06:00
928dce0234
fop(1): uses new sysexits 2024-08-23 14:22:11 -06:00
1fffd3df52
Makefile: sets bindgen to output exit codes as u8 2024-08-23 14:21:43 -06:00
a14b8fb9d7
fop(1): adds functions for error handling 2024-08-23 13:35:30 -06:00
dd39aeff02
mm(1): adds ioerr(), usage(), and oser() functions 2024-08-22 00:22:05 -06:00
DTB
8646d5c4ee
STYLE: make rules more granular and consistent, add examples 2024-08-08 02:31:54 -06:00
DTB
6d7173e438
libfileis(3): finish removal 2024-07-18 20:50:12 -06:00
DTB
958f08bd9e
fileis.1: rename from scrut.1 2024-07-18 20:45:14 -06:00
DTB
02b5edae05
fileis(1): feature parity with C scrut(1) 2024-07-18 20:43:39 -06:00
DTB
0819eeb75d
fileis(1): scrap libfileis(3), work on rewriting scrut(1) in Rust 2024-07-18 19:11:22 -06:00
DTB
0f12dcc552
scrut(1): fix ugly opt parsing 2024-07-15 14:28:00 -06:00
DTB
b9c4b49603
scrut(1): use libfileis 2024-07-15 14:13:53 -06:00
DTB
2c4349872c
scrut(1): banish gotos 2024-07-15 13:55:01 -06:00
DTB
59fa3ed3d2
scrut(1): get rid of -h 2024-07-15 13:47:55 -06:00
DTB
71655a8559
libfileis(3): a library for scrutinizing files 2024-07-15 13:47:04 -06:00
37 changed files with 629 additions and 408 deletions

View File

@ -1,4 +1,4 @@
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media> # Copyright (c) 20232025 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 DTB <trinity@trinity.moe> # Copyright (c) 20232024 DTB <trinity@trinity.moe>
# Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media> # Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
# Copyright (c) 2024 Aaditya Aryal <aryalaadi123@gmail.com> # Copyright (c) 2024 Aaditya Aryal <aryalaadi123@gmail.com>
@ -42,7 +42,7 @@ BIN = build/bin
default: all test default: all test
.PHONY: all .PHONY: all
all: dj false fop hru intcmp mm npc peek rpn scrut str strcmp swab true all: dj false fileis fop hru intcmp mm npc peek rpn str strcmp swab true
# keep build/include until bindgen(1) has stdin support # keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703 # https://github.com/rust-lang/rust-bindgen/issues/2703
@ -62,7 +62,12 @@ dist: all docs
install: dist install: dist
cp -r $(DESTDIR)/* / cp -r $(DESTDIR)/* /
include tests/tests.mk TESTFILES != for file in tests/*.mk; do printf '%s ' "$$file"; done
TESTS != printf '%s\n' "$(TESTFILES)" | xargs -n1 basename \
| sed 's/\.mk/_tests/g'
include $(TESTFILES)
.PHONY: test .PHONY: test
test: all $(TESTS) /tmp/getopt test: all $(TESTS) /tmp/getopt
@ -95,7 +100,7 @@ build/o/libstrerror.rlib: build src/libstrerror.rs
src/libstrerror.rs src/libstrerror.rs
build/o/libsysexits.rlib: build/include/sysexits.h build/o/libsysexits.rlib: build/include/sysexits.h
bindgen --default-macro-constant-type signed --use-core --formatter=none \ bindgen --fit-macro-constant-types --default-macro-constant-type unsigned --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 # bandage solution until bindgen(1) gets stdin support
@ -112,6 +117,11 @@ false: build/bin/false
build/bin/false: src/false.c build build/bin/false: src/false.c build
$(CC) $(CFLAGS) -o $@ src/false.c $(CC) $(CFLAGS) -o $@ src/false.c
.PHONY: fileis
fileis: build/bin/fileis
build/bin/fileis: src/fileis.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/fileis.rs
.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
@ -147,11 +157,6 @@ 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) -o $@ src/rpn.rs
.PHONY: scrut
scrut: build/bin/scrut
build/bin/scrut: src/scrut.c build
$(CC) $(CFLAGS) -o $@ src/scrut.c
.PHONY: str .PHONY: str
str: build/bin/str str: build/bin/str
build/bin/str: src/str.c build build/bin/str: src/str.c build

254
STYLE
View File

@ -1,11 +1,51 @@
“Everyone knows that debugging is twice as hard as writing a program in the
first place. So if youre as clever as you can be when you write it, how
will you ever debug it?”
Brian Kernighan, The Elements of Programming Style
The following guidelines are conducive to clear and readable code that is The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System. 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. Use
===
2. Empty lines should be placed between different kinds of statements: 0. A single line for control flow statements short enough to be easily
understood at a glance:
if !(argc < 0) { usage(program_name); }
This applies to C switch statements and cases and Rust match statements, as
well:
switch (value) { /* aligning stuff to make it easier to read is fine */
case possibility: variable = foo; break;
default: variable = NULL; break;
}
1. Switch cases in C and match arms in Rust should start another level of
indentation:
switch (value) {
case possibility:
statement;
break;
default:
statement;
break;
}
match result {
Ok(n) => variable = n,
Err(e) => error = e,
}
2. Braces in control flow where their inclusion is left optional in C:
if (condition) { statement; }
3. Empty lines between different kinds of statements:
int t; int t;
@ -25,56 +65,57 @@ consistent with the style of the rest of the Bonsai Computer System.
return io; return io;
3. Each block of code should be indented once more than the keyword which 4. Compiler options that yield the most useful warnings, such as -Wpedantic in
initiated the block: a lot of C compilers. Fix the warnings, too [0].
switch (c) { 5. One more level of indentation and one argument per line when a function
case 'e': mode |= EQUAL; break; call or statement header is too long to fit on one line:
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!( let usage = format!(
"Usage: {} [-d delimiter] index command [args...]", "Usage: {} [-d delimiter] index command [args...]",
argv[0], argv[0],
); );
6. If Rust function arguments or fields are on their own lines, they should 6. One more level of indentation than the keyword that initiated a multi-line
always have a trailing comma: block.
if (condition) {
statement;
statement;
}
7. The return value of all non-void functions, or explicitly ignore them (like
casting to void in C) [0]:
if ((a = malloc(sizeof char)) == NULL) { /* handle this error */
(void)fprintf(stderr, "oh noes!"); /* explicitly ignore this one */
return EX_OSERR; /* ...because the program is exiting anyway */
}
8. The smallest possible scope for data [0].
9. Comments noting all the symbols and macros used from a C header file, next
to its include macro:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
(space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. Spaces in control flow statements, after the keyword and before the
opening brace:
for (i = 2; i < argc; ++i) {
11. In Rust, a trailing comma on all arguments or fields that are on their own
lines:
return Err(EvaluationError { return Err(EvaluationError {
message: format!("{}: Invalid token", i), message: format!("{}: Invalid token", i),
code: EX_DATAERR, code: EX_DATAERR,
}) })
7. If text is on the same line as a brace, spaces should be placed after an 12. In Rust, place extern statements after use statements that include standard
opening curly brace and before a closing one: library crates. Group like statements:
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; use std::fs::Path;
@ -84,40 +125,129 @@ library crates. Group alike statements:
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE }; use sysexits::{ EX_OSERR, EX_USAGE };
11. Do not use do while loops in C. 13. If text is on the same line as a brace, spaces after an opening brace and
before a closing one:
12. Adhere to the following rules from the paper The Power of 10: Rules for use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
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. 14. Alphabetic sorting, where applicable:
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 use std::io::{ BufWriter, Read, Write, stderr, stdin, stdout }
Kernighan:
Everyone knows that debugging is twice as hard as writing a program in the 15. In Rust, use the to_owned() method on string types (str, OsStr, CStr, etc.)
first place. So if you're as clever as you can be when you write it, how and the to_string() method on other types.
will you ever debug it?
Avoid
=====
16. Unbounded loops [0].
17. Function pointers [0].
18. Heap memory allocation [0].
19. Using too much nested logic (within reason).
20. Too many levels of dereferences [0]:
/* do not do this */
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) {
if (a[i].id == MATCH) { a[i].val = 0; }
}
/* do this */
for (struct MadeUp *s = &a[0]; *s != NULL; s = &s[1]) {
if (s->id == MATCH) { s->val = 0; }
}
21. Using C preprocessor macros; the fewer, the better [0].
22. The exit(3p) and std::process::exit() functions; returning from the main
function skips a system call.
Do Not Use
==========
23. More than the length of one printed page for a function [0].
24. Recursion, as its complex and can unexpectedly overflow the stack [0].
25. Any functionality not in the POSIX C specification and language features not
in C99.
26. Do-while loops, as theyre unique to C and confusing for casual programmers.
27. Labels and goto statements; use sensible flow control [0].
28. Pointer arithmetic, as it tends to be confusing and unnecessary; use
index-reference patterns like &p[1] instead of p + 1. &p[n] is the address at
p + sizeof p * n, not p + n, like pointer arithmetic suggests.
29. C struct bitfields in unions, to access certain bits of bigger data types,
as its poorly defined in the C standards; use bit arithmetic.
30. C trigraphs.
31. Inclusions in C header files, to prevent multiple file inclusions.
32. C preprocessor variables to prevent multiple inclusions of the same file,
such as:
#ifdef _FILE
#define _FILE
/* file body */
#endif /* ifdef _FILE */
Instead, take the time to ensure other files arent including any files twice.
33. The gets(3p) function from <stdio.h>, as its impossible to prevent buffer
overflows when it's used; use fgets(3p) from <stdio.h>.
34. The scanf(3p) function from <stdio.h> [1].
35. Any functionality not described in the latest POSIX make(1) specification.
36. Macros which panic on failure in Rust (such as the print!() and println!()
macros). Use a function and handle any errors. However, do use the eprintln!()
macro for error messages. Handling an error for writing an error message is
redundant.
Usage Text
==========
This section is adapted from the NetBSD style guide [2].
When programs are invoked incorrectly and in the synopsis of manual pages, uasge
text should be provided to the user. The following is the format used by this
project for this purpose:
All optional arguments are to be placed in square brackets (U+005B, U+005D).
Mutually exclusive arguments can be separated by a vertical line (U+007C).
Groups of arguments should be specified in alphabetical order in most cases. The
order of arguments and an example of these rules follows:
0. Options with no option arguments.
1. Options with option arguments. Arguments should be specified inside the same
square brackets as the options.
3. Non-option arguments.
"usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n"
"usage: f [-a | -b] [-c [-de] [-n number]]\n"
References References
========== ==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf> [0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
[1] <http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html>
[2] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
-- --
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media> Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>
Copyright © Wikipedia contributors Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit

View File

@ -1,15 +1,15 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe> .\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media> .\" Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
.\" .\"
.\" 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 FILEIS 1 2025-02-24 "Harakit X.X.X"
.SH NAME .SH NAME
scrut \(en scrutinize file properties fileis \(en scrutinize file properties
.SH SYNOPSIS .SH SYNOPSIS
scrut fileis
.RB [ -LSbcdefgkprsuwx ] .RB [ -LSbcdefgkprsuwx ]
.B file... .B file...
.\" .\"
@ -82,5 +82,6 @@ Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
.\" .\"
.SH SEE ALSO .SH SEE ALSO
.BR access (3p), .BR access (3p),
.BR chown (1p),
.BR lstat (3p), .BR lstat (3p),
.BR test (1p) .BR test (1p)

View File

@ -13,7 +13,7 @@
int main(void) { int main(void) {
#ifdef __OpenBSD__ #ifdef __OpenBSD__
pledge(NULL, NULL); (void)pledge("stdio", "");
#endif #endif
return 1; return 1;
} }

91
src/fileis.rs Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2024 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,
fs::metadata,
os::unix::fs::{ FileTypeExt, MetadataExt },
process::ExitCode,
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::GetOpt;
use strerror::StrError;
use sysexits::EX_USAGE;
const OPTS: &str = "bcdefgkprsuwxLS";
fn usage(argv0: &str) -> ExitCode {
eprintln!("Usage: {} [-{}] file...", argv0, OPTS);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let mut sel = String::with_capacity(OPTS.len()); // selected options
let mut optind: usize = 1; // argv[0]
while let Some(opt) = argv.getopt(OPTS) {
if let Ok(optchr) = opt.opt() { sel.push_str(optchr); }
else { return usage(&argv[0]); }
optind = opt.ind();
}
if optind == argv.len() { return usage(&argv[0]); }
for arg in argv.iter().skip(optind) {
let fmeta = match metadata(arg) {
Ok(m) => m,
Err(e) => { // no perms or nonexistent
eprintln!("{}: {}: {}", argv[0], arg, e.strerror());
return ExitCode::FAILURE;
},
};
let fmode = fmeta.mode();
let ftype = fmeta.file_type();
for selection in sel.chars() { // run all selected tests
match selection {
'b' if ftype.is_block_device() => (),
'c' if ftype.is_char_device() => (),
'e' => (), // exists or metadata would have errored
'd' if fmeta.is_dir() => (),
'f' if fmeta.is_file() => (),
'g' if fmode & 0o2000 /* S_ISGID */ != 0 => (), // setgid
'k' if fmode & 0o1000 /* S_ISVTX */ != 0 => (), // setvtx
'p' if ftype.is_fifo() => (),
'r' if fmode & 0o0400 /* S_IRUSR */ != 0 => (), // read access
'u' if fmode & 0o4000 /* S_ISUID */ != 0 => (), // setuid
'w' if fmode & 0o0200 /* S_IWUSR */ != 0 => (), // write access
'x' if fmode & 0o0100 /* S_IXUSR */ != 0 => (), // exec access
'L' if fmeta.is_symlink() => (),
'S' if ftype.is_socket() => (),
_ => { return ExitCode::FAILURE; }
}
}
}
ExitCode::SUCCESS
}

View File

@ -18,8 +18,8 @@
use std::{ use std::{
env::args, env::args,
io::{ Read, stdin, stdout, Write }, io::{ Error, Read, Write, stdin, stdout },
process::{ Command, exit, Stdio }, process::{ Command, ExitCode, Stdio, exit },
}; };
extern crate getopt; extern crate getopt;
@ -32,25 +32,36 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR; #[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd; #[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge }; #[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn main() { fn err(argv0: &String, e: Error) {
eprintln!("{}: {}", argv0, e.strerror());
}
fn usage(argv0: &String) -> u8 {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv0);
EX_USAGE
}
fn main() -> ExitCode {
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 = '\u{1E}'.to_string(); /* ASCII record separator */
let mut optind = 1; let mut optind = 1;
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio proc exec"); let promises = Promises::new("exec proc stdio unveil");
if let Err(e) = pledge(Some(promises), None) { if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror()); err(&argv[0], e);
exit(EX_OSERR); return ExitCode::from(EX_OSERR);
}
if let Err(e) = unveil(None, None) {
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
} }
} }
let usage = format!( if argv.len() == 1 { return ExitCode::from(usage(&argv[0])); }
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
while let Some(opt) = argv.getopt("d:") { while let Some(opt) = argv.getopt("d:") {
match opt.opt() { match opt.opt() {
@ -60,8 +71,7 @@ fn main() {
optind = opt.ind(); optind = opt.ind();
}, },
_ => { _ => {
eprintln!("{}", usage); return ExitCode::from(usage(&argv[0]));
exit(EX_USAGE);
} }
}; };
} }
@ -69,7 +79,7 @@ fn main() {
/* parse the specified index as a number we can use */ /* parse the specified index as a number we can use */
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| { let index = argv[optind].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[1], e); eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR); exit(EX_DATAERR.into());
}); });
/* index of the argv[0] for the operator command */ /* index of the argv[0] for the operator command */
@ -77,15 +87,14 @@ fn main() {
/* argv[0] of the operator command */ /* argv[0] of the operator command */
let operator = argv.get(command_arg).unwrap_or_else(|| { let operator = argv.get(command_arg).unwrap_or_else(|| {
eprintln!("{}", usage); exit(usage(&argv[0]).into());
exit(EX_USAGE);
}); });
/* read entire standard input into memory */ /* 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) { if let Err(e) = stdin().read_to_string(&mut buf) {
eprintln!("{}: {}", argv[0], e.strerror()); err(&argv[0], e);
exit(EX_IOERR); exit(EX_IOERR.into());
}; };
/* split the buffer by the delimiter (by default, '\u{1E}') */ /* split the buffer by the delimiter (by default, '\u{1E}') */
@ -105,18 +114,14 @@ fn main() {
.stdout(Stdio::piped()) /* piped stdout to handle output ourselves */ .stdout(Stdio::piped()) /* piped stdout to handle output ourselves */
.spawn() .spawn()
.unwrap_or_else( |e| { .unwrap_or_else( |e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); err(&argv[0], e);
exit(EX_UNAVAILABLE); exit(EX_UNAVAILABLE.into());
}); });
/* get field we want to pipe into spawned program */ /* 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", argv[0], index);
"{}: {}: No such index in input", exit(EX_DATAERR.into());
argv[0],
index.to_string(),
);
exit(EX_DATAERR);
}); });
/* get the stdin of the newly spawned program and feed it the field val */ /* get the stdin of the newly spawned program and feed it the field val */
@ -126,8 +131,8 @@ fn main() {
} }
let output = spawned.wait_with_output().unwrap_or_else(|e| { let output = spawned.wait_with_output().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); err(&argv[0], e);
exit(EX_IOERR); exit(EX_IOERR.into());
}); });
/* get the output with which the original field will be replaced */ /* get the output with which the original field will be replaced */
@ -143,8 +148,8 @@ fn main() {
/* convert the output of the program to UTF-8 */ /* 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], e);
exit(EX_IOERR); exit(EX_IOERR.into());
}); });
/* store the new field in the old fields vector */ /* store the new field in the old fields vector */
@ -154,7 +159,9 @@ fn main() {
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| {
eprintln!("{}: {}", argv[0], e.strerror()); err(&argv[0], e);
exit(EX_IOERR); exit(EX_IOERR.into());
}); });
ExitCode::SUCCESS
} }

View File

@ -19,7 +19,7 @@
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
env::args, env::args,
io::{ stdin, stdout, Write }, io::{ Write, stdin, stdout },
process::{ ExitCode, exit }, process::{ ExitCode, exit },
}; };
@ -31,7 +31,7 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR; #[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd; #[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge }; #[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
/* list of SI prefixes */ /* list of SI prefixes */
const LIST: [(u32, &str); 10] = [ const LIST: [(u32, &str); 10] = [
@ -47,6 +47,16 @@ const LIST: [(u32, &str); 10] = [
(30, "Q"), /* quetta */ (30, "Q"), /* quetta */
]; ];
fn err(argv0: &String, message: String, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, message);
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {}", argv0);
ExitCode::from(EX_USAGE)
}
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 */ /* preserve decimal places in output by casting to a float */
let mut out = (input as f64, (0_u32, "")); let mut out = (input as f64, (0_u32, ""));
@ -81,22 +91,25 @@ 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) { if let Some(_) = argv.get(1) { return usage(&argv[0]); }
eprintln!("Usage: {}", argv[0]);
return ExitCode::from(EX_USAGE as u8);
}
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio"); let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), None) { if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e.strerror(), Some(EX_OSERR));
return ExitCode::from(EX_OSERR as u8); }
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
} }
} }
let mut buf = String::new(); let mut buf = String::new();
while let Ok(_) = stdin().read_line(&mut buf) { if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
}
if buf.is_empty() { return ExitCode::SUCCESS; } if buf.is_empty() { return ExitCode::SUCCESS; }
let n: u128 = match buf.trim().parse() { let n: u128 = match buf.trim().parse() {
@ -104,30 +117,22 @@ fn main() -> ExitCode {
buf.clear(); buf.clear();
f f
}, },
Err(err) => { Err(e) => return err(&argv[0], e.to_string(), Some(EX_DATAERR)),
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_DATAERR as u8);
},
}; };
let (number, prefix) = match convert(n) { let (number, prefix) = convert(n).unwrap_or_else(|e| {
Ok(x) => x, let _ = err(&argv[0], e.to_string(), None);
Err(err) => { exit(EX_SOFTWARE.into());
eprintln!("{}: {}", argv[0], err); });
return ExitCode::from(EX_SOFTWARE as u8);
},
};
let si_prefix = format!("{}B", prefix.1); let si_prefix = prefix.1.to_owned() + "B";
/* round output number to one decimal place */ /* round output number to one decimal place */
let out = ((number * 10.0).round() / 10.0).to_string(); let rounded = (number * 10.0).round() / 10.0;
let out = rounded.to_string() + " " + &si_prefix + &'\n'.to_string();
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) if let Err(e) = stdout().write_all(out.as_bytes()) {
.unwrap_or_else(|e| { return err(&argv[0], e.strerror(), Some(EX_IOERR));
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
} }
ExitCode::SUCCESS ExitCode::SUCCESS

View File

@ -26,27 +26,35 @@ extern crate getopt;
extern crate sysexits; extern crate sysexits;
use getopt::GetOpt; use getopt::GetOpt;
use sysexits::EX_USAGE; use sysexits::{ EX_DATAERR, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR; #[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd; #[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] extern crate strerror; #[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge }; #[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
#[cfg(target_os="openbsd")] use strerror::StrError; #[cfg(target_os="openbsd")] use strerror::StrError;
fn usage(s: &str) -> ExitCode { fn err(argv0: &String, e: String, code: u8) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", s); eprintln!("{}: {}", argv0, e);
ExitCode::from(EX_USAGE as u8) ExitCode::from(code)
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", argv0);
ExitCode::from(EX_USAGE)
} }
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio"); let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), None) { if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e.strerror(), EX_OSERR);
return ExitCode::from(EX_OSERR as u8); }
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), EX_OSERR);
} }
} }
@ -78,8 +86,8 @@ fn main() -> ExitCode {
match arg.parse::<usize>() { /* parse current operand */ match arg.parse::<usize>() { /* parse current operand */
Ok(n) => currn = n, Ok(n) => currn = n,
Err(e) => { Err(e) => {
eprintln!("{}: {}: {}", &argv[0], arg, e); let error = arg.to_owned() + ": " + &e.to_string();
return ExitCode::from(EX_USAGE as u8); return err(&argv[0], error, EX_DATAERR);
} }
} }

View File

@ -43,6 +43,10 @@ impl Promises {
} }
} }
impl Default for Promises {
fn default() -> Self { Promises::new("") }
}
pub fn pledge( pub fn pledge(
promises: Option<Promises>, execpromises: Option<Promises> promises: Option<Promises>, execpromises: Option<Promises>
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -65,14 +69,12 @@ pub fn pledge(
pub struct UnveilPerms(CString); pub struct UnveilPerms(CString);
impl UnveilPerms { impl UnveilPerms {
pub fn new(permissions: Vec<char>) -> Self { pub fn new<T: IntoIterator<Item = char>>(permissions: T) -> Self {
if permissions.is_empty() { let perms = CString::new(
return UnveilPerms(CString::new("").unwrap()); permissions.into_iter().collect::<String>()
} ).unwrap();
UnveilPerms( UnveilPerms(perms)
CString::new(permissions.iter().collect::<String>()).unwrap()
)
} }
} }
@ -83,9 +85,9 @@ pub fn unveil(
let path_c = path.map(CString::new).map(Result::unwrap); 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 arg1 = path_c.map(|p| p.into_raw() as *const c_char).unwrap_or(null());
let arg2 = permissions let arg2 = permissions.map(|p| {
.map(|p| p.0.into_raw() as *const c_char) p.0.into_raw() as *const c_char
.unwrap_or(null()); }).unwrap_or(null());
unsafe { unsafe {
match openbsd::unveil(arg1, arg2) { match openbsd::unveil(arg1, arg2) {

View File

@ -20,9 +20,9 @@
use std::{ use std::{
env::args, env::args,
fs::File, fs::File,
io::{ stdin, stdout, stderr, BufWriter, Read, Write }, io::{ Error, BufWriter, Read, Write, stderr, stdin, stdout },
os::fd::{ AsRawFd, FromRawFd }, os::fd::{ AsRawFd, FromRawFd },
process::{ exit, ExitCode }, process::{ ExitCode, exit},
}; };
extern crate getopt; extern crate getopt;
@ -47,15 +47,23 @@ use ArgMode::*;
enum ArgMode { In, Out } enum ArgMode { In, Out }
fn err(argv0: &String, e: Error, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-aetu] [-i input] [-o output]", argv0);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>(); let argv = args().collect::<Vec<_>>();
let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
let promises = Promises::new("cpath rpath stdio unveil wpath"); let promises = Promises::new("cpath rpath stdio unveil wpath");
if let Err(e) = pledge(Some(promises), None) { if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e, Some(EX_OSERR));
return ExitCode::from(EX_OSERR as u8);
} }
} }
@ -85,8 +93,7 @@ fn main() -> ExitCode {
mode = Some(Out); /* latest argument == -o */ mode = Some(Out); /* latest argument == -o */
}, },
Err(_) | Ok(_) => { Err(_) | Ok(_) => {
eprintln!("{}", usage); return usage(&argv[0]);
return ExitCode::from(EX_USAGE as u8);
}, },
}; };
@ -108,32 +115,28 @@ fn main() -> ExitCode {
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
for input in &ins { for input in &ins {
let perms = UnveilPerms::new(vec!['r']); let perms = UnveilPerms::new(['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) { if let Err(e) = unveil(Some(&input), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e, Some(EX_OSERR));
return ExitCode::from(EX_OSERR as u8);
} }
} }
for output in &outs { for output in &outs {
let perms = UnveilPerms::new(vec!['c', 'w']); let perms = UnveilPerms::new(['c', 'w']);
if let Err(e) = unveil(Some(&output), Some(perms)) { if let Err(e) = unveil(Some(&output), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e, Some(EX_OSERR));
return ExitCode::from(EX_OSERR as u8);
} }
} }
if let Err(e) = unveil(None, None) { if let Err(e) = unveil(None, None) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e, Some(EX_OSERR));
return ExitCode::from(EX_OSERR as u8);
} }
} }
if ins.is_empty() && outs.is_empty() && argv.len() > optind { if ins.is_empty() && outs.is_empty() && argv.len() > optind {
eprintln!("Usage: {}", usage); return usage(&argv[0]);
return ExitCode::from(EX_USAGE as u8);
} }
/* use stdin if no inputs are specified */ /* use stdin if no inputs are specified */
@ -153,8 +156,8 @@ fn main() -> ExitCode {
match File::open(file) { match File::open(file) {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror()); let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR); exit(EX_IOERR.into());
}, },
} }
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
@ -180,8 +183,8 @@ fn main() -> ExitCode {
match options { match options {
Ok(f) => return f, Ok(f) => return f,
Err(e) => { Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror()); let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR); exit(EX_IOERR.into());
}, },
}; };
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
@ -205,21 +208,19 @@ fn main() -> ExitCode {
for file in inputs { for file in inputs {
for byte in file.bytes().map(|b| { for byte in file.bytes().map(|b| {
b.unwrap_or_else(|e| { b.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror()); let _ = err(&argv[0], e, None);
exit(EX_IOERR); exit(EX_IOERR.into());
}) })
}) { }) {
for out in &mut outputs { for out in &mut outputs {
if let Err(e) = out.write(&[byte]) { if let Err(e) = out.write(&[byte]) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e, Some(EX_IOERR));
return ExitCode::from(EX_IOERR as u8);
} }
if u { if u {
/* immediately flush the output for -u */ /* immediately flush the output for -u */
if let Err(e) = out.flush() { if let Err(e) = out.flush() {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], e, Some(EX_IOERR));
return ExitCode::from(EX_IOERR as u8);
} }
} }
} }

View File

@ -20,7 +20,7 @@
#include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin, #include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin,
* stdout, EOF */ * stdout, EOF */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */ #include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* pledge(2), getopt(3) */ #include <unistd.h> /* NULL, getopt(3), pledge(2), unveil(2) */
char *program_name = "npc"; char *program_name = "npc";
@ -44,7 +44,7 @@ int main(int argc, char *argv[]) {
char showtab = 0; /* prints tab characters in caret notation */ char showtab = 0; /* prints tab characters in caret notation */
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1) { if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL)) {
perror(argv[0] == NULL ? program_name : argv[0]); perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR; return EX_OSERR;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media> * Copyright (c) 20242025 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
@ -46,21 +46,21 @@ use std::{
collections::VecDeque, collections::VecDeque,
env::args, env::args,
fmt::{ self, Display, Formatter }, fmt::{ self, Display, Formatter },
io::stdin, io::{ Error, Write, stdin, stdout },
process::ExitCode, process::ExitCode,
}; };
use CalcType::*; use CalcType::*;
extern crate strerror;
extern crate sysexits; extern crate sysexits;
use sysexits::EX_DATAERR; use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR; #[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")] extern crate openbsd;
#[cfg(target_os="openbsd")] use strerror::StrError; #[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
#[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 */
@ -120,12 +120,46 @@ impl Display for CalcType {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct EvaluationError { struct EvaluationError {
message: String, message: String,
code: i32, code: u8,
} }
/* Im no math nerd but I want the highest possible approximation of 0.9 impl StrError for EvaluationError {
* repeating and it seems this can give it to me */ fn strerror(&self) -> String {
const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0; self.message.clone()
}
}
fn err<T: StrError>(argv0: &String, e: &T, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn operate(
mut stack: VecDeque<f64>,
op: CalcType,
) -> Result<VecDeque<f64>, EvaluationError> {
let vals = (stack.pop_back(), stack.pop_back());
if let (Some(x), Some(y)) = vals {
match op {
Add => stack.push_back(y + x),
Subtract => stack.push_back(y - x),
Multiply => stack.push_back(y * x),
Divide => stack.push_back(y / x),
Power => stack.push_back(y.powf(x)),
Floor => stack.push_back((y / x).floor()),
Modulo => stack.push_back(y % x),
_ => {},
};
} else {
return Err(EvaluationError {
message: format!("{}: unexpected operation", op),
code: EX_DATAERR,
})
}
Ok(stack)
}
fn eval( fn eval(
input: &str, input: &str,
@ -145,6 +179,7 @@ fn eval(
.rev() .rev()
.map(|t| CalcType::from(t)) .map(|t| CalcType::from(t))
.collect(); .collect();
let mut ops: VecDeque<CalcType> = VecDeque::new(); let mut ops: VecDeque<CalcType> = VecDeque::new();
while let Some(n) = toks.pop_back() { while let Some(n) = toks.pop_back() {
@ -152,36 +187,15 @@ fn eval(
Val(v) => stack.push_back(v), Val(v) => stack.push_back(v),
Invalid(i) => { Invalid(i) => {
return Err(EvaluationError { return Err(EvaluationError {
message: format!("{}: Invalid token", i), message: format!("{}: invalid token", i),
code: EX_DATAERR, code: EX_DATAERR,
}) })
}, },
op => { op => {
ops.push_back(op.clone()); ops.push_back(op.clone());
oper = true;
let vals = ( oper = true; /* this is an operation */
stack.pop_back(), return operate(stack, op).map(|s| (s, oper));
stack.pop_back(),
);
if let (Some(x), Some(y)) = vals {
match op {
Add => stack.push_back(y + x),
Subtract => stack.push_back(y - x),
Multiply => stack.push_back(y * x),
Divide => stack.push_back(y / x),
Power => stack.push_back(y.powf(x)),
Floor => stack.push_back((y / x).floor()),
Modulo => stack.push_back(y % x),
_ => {},
};
} else {
return Err(EvaluationError {
message: format!("{}: Unexpected operation", op),
code: EX_DATAERR,
})
}
}, },
}; };
} }
@ -190,51 +204,46 @@ fn eval(
} }
/* 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) -> f64 {
let multiplier = 10_f64.powi(precision as i32); /* Set floating-point precision for correcting rounding errors based on
* machine epsilon */
let precision = (-f64::EPSILON.log10()).floor() as i32;
let multiplier = 10_f64.powi(precision);
(value * multiplier).round() / multiplier (value * multiplier).round() / multiplier
} }
/* print the stack and let the caller know if evaluation should continue */
fn unstack(stack: VecDeque<f64>, op: bool) -> Result<bool, Error> {
if let Some(val) = stack.iter().last() {
if !op { return Ok(true); }
let out = round_precise(val).to_string() + &'\n'.to_string();
return stdout().write_all(out.as_bytes()).map(|_| true);
} else {
return Ok(false);
}
}
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio"); let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), None) { if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
eprintln!("{}: {}", argv[0], e.strerror()); return err(&argv[0], &e, Some(EX_OSERR));
return ExitCode::from(EX_OSERR as u8); }
if let Err(e) = unveil(None, None) {
return err(&argv[0], &e, Some(EX_OSERR));
} }
} }
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
* machine epsilon */
let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize;
if argv.get(1).is_none() { /* read from stdin */ if argv.get(1).is_some() { /* read expressions from argv */
while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack) {
Ok(s) => {
buf.clear();
stack = s.0.clone();
let val = match stack.iter().last() {
Some(v) => v,
None => break,
};
if s.1 == false { continue; }
println!("{}", round_precise(val, precision).to_string());
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(err.code as u8);
},
};
}
} else { /* read from argv */
/* join argv into an owned String joined by spaces minus argv[0] */ /* join argv into an owned String joined by spaces minus argv[0] */
let input = argv let input = argv
.iter() .iter()
@ -245,20 +254,44 @@ fn main() -> ExitCode {
match eval(&input, stack) { match eval(&input, stack) {
Ok(s) => { Ok(s) => {
stack = s.0.clone(); /* we can ignore the return value of unstack() because we are
* not continually evaluating from stdin */
if let Err(e) = unstack(s.0.clone(), s.1.clone()) {
return err(&argv[0], &e, Some(EX_IOERR));
}
let val = match stack.iter().last() { return ExitCode::SUCCESS;
Some(v) => v,
None => return ExitCode::SUCCESS,
};
println!("{}", round_precise(val, precision).to_string())
}, },
Err(err) => { Err(e) => {
eprintln!("{}: {}", argv[0], err.message); return err(&argv[0], &e, Some(e.code));
return ExitCode::from(err.code as u8);
}, },
}; };
} }
/* else, read from stdin */
loop { /* take input until EOF */
if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], &e, Some(EX_IOERR));
}
match eval(&buf.trim(), stack) {
Ok(s) => {
buf.clear();
stack = s.0.clone(); /* out with the old, in with the new */
match unstack(s.0, s.1) {
Ok(b) if b => continue,
Ok(_) => break,
Err(e) => {
return err(&argv[0], &e, Some(EX_IOERR))
},
};
},
Err(e) => {
return err(&argv[0], &e, Some(e.code));
},
};
}
ExitCode::SUCCESS ExitCode::SUCCESS
} }

View File

@ -46,7 +46,7 @@ int main(int argc, char *argv[]) {
program_name = argv[0] == NULL ? program_name : argv[0]; program_name = argv[0] == NULL ? program_name : argv[0];
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge("rpath stdio unveil", NULL) == -1) { if (pledge("rpath stdio unveil", "") == -1) {
perror(program_name); perror(program_name);
return EX_OSERR; return EX_OSERR;
} }
@ -82,7 +82,7 @@ int main(int argc, char *argv[]) {
struct stat buf; struct stat buf;
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (unveil(*argv, "r") == -1) { if (unveil(*argv, "rw") == -1) {
perror(program_name); perror(program_name);
return EX_OSERR; return EX_OSERR;
} }

View File

@ -25,7 +25,7 @@
#include <sysexits.h> /* EX_OSERR, EX_USAGE */ #include <sysexits.h> /* EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__ #ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */ # include <unistd.h> /* pledge(2), unveil(2) */
#endif #endif
char *program_name = "str"; char *program_name = "str";
@ -62,7 +62,7 @@ int main(int argc, char *argv[]) {
program_name = argv[0] == NULL ? program_name : argv[0]; program_name = argv[0] == NULL ? program_name : argv[0];
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1) { if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL) == -1) {
perror(program_name); perror(program_name);
return EX_OSERR; return EX_OSERR;
} }

View File

@ -20,7 +20,7 @@
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */ #include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__ #ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */ # include <unistd.h> /* pledge(2), unveil(2) */
#endif #endif
char *program_name = "strcmp"; char *program_name = "strcmp";
@ -29,7 +29,7 @@ int main(int argc, char *argv[]) {
unsigned int i; unsigned int i;
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1) { if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL) == -1) {
perror(argv[0] == NULL ? program_name : argv[0]); perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR; return EX_OSERR;

View File

@ -33,31 +33,29 @@ use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
use strerror::StrError; use strerror::StrError;
#[cfg(target_os="openbsd")] extern crate openbsd; #[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge }; #[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn err(argv0: &String, e: Error, code: u8) -> ExitCode {
fn oserr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror()); eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_OSERR as u8) ExitCode::from(code)
} }
fn ioerr(argv0: &str, e: Error) -> ExitCode { fn usage(s: &String) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_IOERR as u8)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-w word_size]", s); eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE as u8) ExitCode::from(EX_USAGE)
} }
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] { #[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio"); let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), None) { if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return oserr(&argv[0], e); return err(&argv[0], e, EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e, EX_OSERR);
} }
} }
@ -88,22 +86,22 @@ fn main() -> ExitCode {
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), // read nothing; bye
Ok(v) if v == wordsize => { // read full block; swab Ok(v) if v == wordsize => { // read full block; swab
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 err(&argv[0], e, EX_IOERR);
} }
}, },
Ok(v) => { // partial read; partially write Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) { if let Err(e) = output.write(&buf[..v]) {
break ioerr(&argv[0], e); break err(&argv[0], e, EX_IOERR);
} }
}, },
Err(e) => break oserr(&argv[0], e) Err(e) => break err(&argv[0], e, EX_OSERR)
} }
} }
} }

View File

@ -13,6 +13,6 @@
int main(void) { int main(void) {
#ifdef __OpenBSD__ #ifdef __OpenBSD__
pledge(NULL, NULL); (void)pledge("stdio", "");
#endif #endif
} }

View File

@ -1,24 +1,13 @@
The testing suite contains two trees: the Bonsai tree and the POSIX tree: Tests
=====
. The Harakit testing suite verifies the functionality of Harakit utilities
├── README and checks for regressions and other issues relating to compliance to our
├── bonsai/ standards of practice.
│   ├── dj.mk
│   ├── false.mk
│   ├── fop.mk
│   └── ...
├── posix/
└── tests.mk
The Bonsai tree tests the functionality of Harakit utilities for regressions and Tests inherit the environment set by the top-level Makefile, which sets the BIN
other issues relating to compliance to our standards of practice. variable to the build/bin directory at the root of the project; therefore, each
binary is located at $(BIN)/tool for idiomatic access.
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 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 the tool being tested and an underscore. The first target is tests, which
@ -27,10 +16,10 @@ 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 repository. This also means that BIN can be set manually so that tests can be
run using make(1) inside of the tests directory: run using make(1) inside of the tests directory:
$ make -f tests.mk BIN=../build/bin dj_tests $ make -f dj.mk BIN=../build/bin dj_tests
-- --
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media> Copyright © 20242025 Emma Tebibyte <emma@tebibyte.media>
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit 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/>. <http://creativecommons.org/licenses/by-sa/4.0/>.

View File

@ -8,20 +8,20 @@
.PRAGMA: command_comment .PRAGMA: command_comment
.PHONY: scrut_tests .PHONY: fileis_tests
scrut_tests: scrut_help scrut_options fileis_tests: fileis_help fileis_options
.PHONY: scrut_help .PHONY: fileis_help
scrut_help: $(BIN)/scrut fileis_help: $(BIN)/fileis
! $(BIN)/scrut -h ! $(BIN)/fileis -h
.PHONY: scrut_options .PHONY: fileis_options
# scrut tests file attributes, but files of a certain attribute aren't # fileis 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 # 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 # harakit and, if test(1p) says a file matches a certain attribute, then checks
# scrut. # fileis.
# opts are space-delimited (for command splitting), sel is not # opts are space-delimited (for command splitting), sel is not
scrut_options: $(BIN)/scrut fileis_options: $(BIN)/fileis
set -e; \ set -e; \
opts="b c d e f g k p r s u w x L S"; \ opts="b c d e f g k p r s u w x L S"; \
sel=; \ sel=; \
@ -30,12 +30,12 @@ scrut_options: $(BIN)/scrut
for opt in $$opts; \ for opt in $$opts; \
do if ! printf "%s\n" $$sel | grep $$opt >/dev/null; then \ do if ! printf "%s\n" $$sel | grep $$opt >/dev/null; then \
if test -$$opt "$$f"; then \ if test -$$opt "$$f"; then \
if ! $(BIN)/scrut -$$opt "$$f"; \ if ! $(BIN)/fileis -$$opt "$$f"; \
then printf "[!!] scrut -%s failed on %s.\n" \ then printf "[!!] fileis -%s failed on %s.\n" \
$$opt "$$f"; \ $$opt "$$f"; \
fi; \ fi; \
sel="$$sel$$opt"; \ sel="$$sel$$opt"; \
printf "[OK] Tested scrut -%s using %s\n" \ printf "[OK] Tested fileis -%s using %s\n" \
$$opt "$$f"; \ $$opt "$$f"; \
fi; \ fi; \
fi; \ fi; \

View File

@ -24,6 +24,7 @@ fop_fail: $(BIN)/fop
! printf 'test\n' | $(BIN)/fop 1 cat ! printf 'test\n' | $(BIN)/fop 1 cat
! printf 'test\n' | $(BIN)/fop 'test' cat ! printf 'test\n' | $(BIN)/fop 'test' cat
! printf 'test\n' | $(BIN)/fop -d'test' cat ! printf 'test\n' | $(BIN)/fop -d'test' cat
! $(BIN)/fop
.PHONY: fop_functionality .PHONY: fop_functionality
fop_functionality: $(BIN)/fop fop_functionality: $(BIN)/fop

View File

@ -6,7 +6,7 @@
# notice are preserved. This file is offered as-is, without any warranty. # notice are preserved. This file is offered as-is, without any warranty.
.PHONY: mm_tests .PHONY: mm_tests
mm_tests: mm_args mm_help mm_stderr mm_remaining mm_tests: mm_args mm_help mm_stderr mm_remaining mm_remaining_options
.PHONY: mm_none .PHONY: mm_none
mm_none: $(BIN)/mm mm_none: $(BIN)/mm
@ -32,3 +32,9 @@ mm_remaining: $(BIN)/mm
test "$$($(BIN)/mm -i README COPYING)" = "$$(cat README COPYING)" test "$$($(BIN)/mm -i README COPYING)" = "$$(cat README COPYING)"
$(BIN)/mm -i README -o /tmp/mm_test0 /tmp/mm_test1 $(BIN)/mm -i README -o /tmp/mm_test0 /tmp/mm_test1
diff /tmp/mm_test0 /tmp/mm_test1 diff /tmp/mm_test0 /tmp/mm_test1
.PHONY: mm_remaining_options
# check to make sure mm -i with trailing arguments interprets -o as one
mm_remaining_options:
! $(BIN)/mm -i README COPYING -o - 2>&1 | cut -d: -f2 \
| xargs test " -o" =

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe> # Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media> # Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP # SPDX-License-Identifier: FSFAP
# #
# Copying and distribution of this file, with or without modification, are # Copying and distribution of this file, with or without modification, are
@ -26,7 +26,7 @@ npc_args:
.PHONY: npc_ascii .PHONY: npc_ascii
# Test 0x00 to 0x7f in input; in other words, the full 7b ASCII range. # 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: npc_ascii_controls npc_ascii_uppers # npc_ascii_symbols \
# npc_ascii_lowers # npc_ascii_lowers
.PHONY: npc_ascii_controls .PHONY: npc_ascii_controls
@ -63,14 +63,18 @@ npc_ascii_controls:
| tail -n 1 \ | tail -n 1 \
| xargs -I out test "^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_" | xargs -I out test "^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_"
.PHONY: npc_ascii_symbols # This test is broken on Linux and will need closer inspection along with GNU
# ASCII 0x1f to 0x3f (^_ and symbols) # xargs(1).
npc_ascii_symbols: # .PHONY: npc_ascii_symbols
# shell quoting olympics # # ASCII 0x1f to 0x3f (^_ and symbols)
awk 'BEGIN{ for (i = 31; i < 64; ++i) printf("%c", i); print }' \ # npc_ascii_symbols:
| $(BIN)/npc \ # # shell quoting olympics
| sed -e s"/\'/\\\'/g" -e 's/"/\\"/g' \ # c="$(awk 'BEGIN{ for (i = 31; i < 64; ++i) printf("%c", i); print }')"
| xargs -I out test "^_ !\"#$$%&'()*+,-./0123456789:;<=>?" = out #
# printf '%s\n' "$c" | $(BIN)/npc \
# | sed -e s"/\'/\\\'/g" -e 's/"/\\"/g' \
# | tr -d '\n' \
# | xargs -I out test "^_ !\"#$$%&\'()*+,-./0123456789:;<=>?" = out
.PHONY: npc_ascii_uppers .PHONY: npc_ascii_uppers
# ASCII 0x40 to 0x5f (uppercases) # ASCII 0x40 to 0x5f (uppercases)

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

@ -6,7 +6,7 @@
# notice are preserved. This file is offered as-is, without any warranty. # notice are preserved. This file is offered as-is, without any warranty.
.PHONY: rpn_tests .PHONY: rpn_tests
rpn_tests: rpn_help rpn_add rpn_sub rpn_mul rpn_div rpn_mod rpn_flr rpn_tests: rpn_help rpn_add rpn_sub rpn_mul rpn_div rpn_mod rpn_flr rpn_stdin
.PHONY: rpn_help .PHONY: rpn_help
rpn_help: $(BIN)/rpn rpn_help: $(BIN)/rpn
@ -41,3 +41,8 @@ rpn_mod: $(BIN)/rpn
rpn_flr: $(BIN)/rpn rpn_flr: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 //)" -eq 2 test "$$($(BIN)/rpn 12 5 //)" -eq 2
test "$$($(BIN)/rpn 9 4 //)" -eq 2 test "$$($(BIN)/rpn 9 4 //)" -eq 2
# done last because all operations have been tested
.PHONY: rpn_stdin
rpn_stdin: $(BIN)/rpn
test "$$(printf '1\n2\n+\n3\n-\n' | $(BIN)/rpn | tail -n1)" -eq 0

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)