247 Commits

Author SHA1 Message Date
fbacfecce8 Merge branch 'linux-fix' (closes #158) 2024-08-21 22:43:29 -06:00
DTB
579ff65b67 tests: bonsai/npc.mk: drop redundant tab removal 2024-08-21 21:59:22 -06:00
DTB
334433536b tests: bonsai/npc.mk: fix harrowing ordeal of a Linux error 2024-08-21 21:57:10 -06:00
e9058803d3 mm(1): fixes to pledge(2) now; tests: bonsai/mm.mk: adds test for regression 2024-08-17 14:58:56 -06:00
71d4d6ba05 tests: bonsai/dj.mk, bonsai/rpn.mk: fixes testing on linux 2024-08-17 01:57:16 -06:00
821f5d09e9 fop(1), hru(1), intcmp(1), mm(1), rpn(1), swab(1): fixes conditional compilation 2024-08-17 01:51:25 -06:00
a4a556a5b6 mm(1): fixes extra file arguments not being unveil(2)ed 2024-08-16 18:43:51 -06:00
DTB
e2c03842a3 intcmp(1): remove extra newline 2024-08-13 12:13:47 -06:00
DTB
0fd66bff38 false(1), true(1): complete main prototype 2024-08-13 12:04:42 -06:00
DTB
0c4923016e Makefile: replace include/None.mk with /dev/null 2024-08-13 11:56:09 -06:00
e0c985f7ff libopenbsd.rs(3): uses c_char instead of i8 for portability 2024-08-14 00:32:28 -06:00
DTB
4c81516742 strcmp(1): further error-proofing 2024-08-10 22:30:08 -06:00
DTB
98c4d94f6d scrut(1): further error-proofing 2024-08-10 22:24:32 -06:00
DTB
da190f713c npc(1): tweak OpenBSD functions 2024-08-10 22:18:15 -06:00
DTB
10b7f7706b dj(1): tweak OpenBSD functions 2024-08-10 22:16:06 -06:00
DTB
b1a4a1a2b9 dj(1): use entirely-stdio error messages for OpenBSD functions 2024-08-10 22:03:59 -06:00
a693ced9d9 strcmp(1): fixes typo 2024-08-10 19:18:52 -06:00
1003c82d23 swab(1): uses pledge(2) 2024-08-10 19:16:26 -06:00
baa75a2619 strcmp(1): implements use of pledge(2) 2024-08-10 19:09:50 -06:00
d6d9c2088e npc(1): uses perror(3) 2024-08-10 19:09:26 -06:00
ea2efdf5b9 str(1): perror(2) -> perror(3) 2024-08-10 19:08:15 -06:00
6c558654f3 str(1): adds reference to perror(3) in include 2024-08-10 19:01:44 -06:00
8699d04ccc str(1): updates to use pledge(2) 2024-08-10 19:00:32 -06:00
18dfd20937 scrut(1): fixes buffer overflow on i386 systems 2024-08-10 18:57:36 -06:00
42010596de scrut(1): adds support for pledge(2) and unveil(2) 2024-08-10 17:17:42 -06:00
0ddfa6e474 tests: bonsai/rpn.mk: fixes rpn_test target deps 2024-08-10 15:34:41 -06:00
326c8f77d1 rpn(1): adds support for pledge(2) 2024-08-10 15:34:14 -06:00
72ef8d00bc npc(1): adds pledge(2) support 2024-08-10 15:22:07 -06:00
851f729ebd tests: bonsai/mm.mk, bonsai/intcmp.mk: updates for regression testing 2024-08-10 14:24:33 -06:00
e253cdf79c intcmp(1): fixes intcmp without options falling through to integer parsing 2024-08-10 14:23:53 -06:00
d89707a47c mm(1): fixes mm without arguments not working 2024-08-10 14:20:38 -06:00
2a75b8f820 mm(1): removes leftover debug print 2024-08-10 14:09:33 -06:00
bda7c074b0 hru(1): adds support for pledge(2) 2024-08-10 13:34:44 -06:00
2f805cc942 mm(1): adds support for pledge(2) and unveil(2) 2024-08-10 13:29:27 -06:00
70bec49127 libopenbsd.rs(3): fixes API for unveil(2) 2024-08-10 13:25:56 -06:00
eae0b0352b dj(1): fixes small rebase mistake 2024-08-10 13:07:46 -06:00
25eb08eb84 Makefile, include: makes conditional compilation more robust 2024-08-10 13:04:41 -06:00
6c882f54cb fop(1): adds pledge support 2024-08-10 12:51:25 -06:00
cf96a13419 true(1), false(1): adds pledge(2) and unveil(2) support 2024-08-10 12:50:51 -06:00
1f59a9806e dj(1): adds pledge(2) and unveil(2) support 2024-08-10 12:50:15 -06:00
c7c71c725b libopenbsd(3): adds pledge(2) and unveil(2) support for Rust; Makefile, include: adds conditional compilation 2024-08-10 12:49:35 -06:00
b76ff8fd90 tests, Makefile: cleaning up 2024-08-09 23:50:31 -06:00
66f809162b Merge branch 'testing' 2024-08-09 23:28:21 -06:00
7278a8fc41 Makefile: fixes testing import 2024-08-09 18:00:25 -06:00
8e5090d13d tests: README: updates README 2024-08-07 22:03:32 -06:00
eb821715f7 tests: bonsai/hru.mk: fixes hru.mk more 2024-08-07 20:45:26 -06:00
0bc0ffa0a5 tests: bonsai/hru.mk: updates to not use old variables 2024-08-07 20:38:03 -06:00
DTB
a7f16b5a7e swab.1: Add note to keep example in tests 2024-08-07 08:35:24 -06:00
DTB
a94884cc2a tests: bonsai/swab.mk: add example from tha man page 2024-08-07 08:34:29 -06:00
DTB
9412f95cb1 tests: bonsai/str.mk: add str_isalpha test 2024-08-05 21:16:18 -06:00
DTB
74d6ec16ac Merge branch 'testing' of git.tebibyte.media:bonsai/harakit into testing 2024-08-04 13:45:10 -06:00
3880abaa4f tests: bonsai/rpn.mk: added rpn(1) test 2024-08-05 13:47:12 -06:00
DTB
e93d218b87 tests/bonsai: str.mk: -h test 2024-08-04 09:14:19 -06:00
DTB
cd5983b10b tests/bonsai: intcmp.mk: add a comment 2024-08-04 09:05:20 -06:00
DTB
acdbecf178 tests: bonsai/scrut.mk: add tests 2024-08-03 21:36:53 -06:00
DTB
0f2d357476 tests/bonsai: dj.mk: tee diagnostics to stderr instead of tty 2024-08-03 07:33:46 -06:00
DTB
588680406a tests: bonsai/npc.mk: full ASCII test coverage 2024-08-03 07:29:04 -06:00
ee7b7e89b2 tests: bonsai/fop.mk: fixes record separators 2024-08-02 19:37:36 -06:00
DTB
91de98cea3 tests: bonsai/intcmp.mk: make tests more relevant to failure cases 2024-08-02 18:28:24 -06:00
DTB
bd09d16949 tests: bonsai/dj.mk: comment more 2024-08-02 18:22:45 -06:00
ce8a0a5be3 tests: converted to Makefiles 2024-08-02 17:29:30 -06:00
0b0bd9bd76 Makefile: fixes test and removes references to the RUSTLIBS variable 2024-08-01 13:12:54 -06:00
18aac06113 Makefile: changes test usage 2024-08-01 00:06:39 -06:00
4aeba9d13f Makefile: uses dirname(1) to get normalized prefix, adds libraries to RUSTFLAGS, fixes typo 2024-07-29 23:04:08 -06:00
6a5739ea9d Merge branch 'dj-formatting' 2024-07-29 22:40:18 -06:00
DTB
6bbccb3776 fop(1): fix minor optind bug, add some comments 2024-07-29 21:14:45 -06:00
DTB
fd13a7f189 swab(1): fix imports to be consistent 2024-07-29 14:43:21 -06:00
6c2b7b68fd dj(1): more better comments 2024-07-29 14:38:33 -06:00
DTB
8d23c7fbac dj(1): better ASV formatting and comments 2024-07-29 13:43:52 -06:00
DTB
7e347bebdf mm(1): interpret retvals 2024-07-29 11:30:41 -06:00
DTB
a7b27f0d6a strcmp(1): interpret all retvals 2024-07-29 11:14:48 -06:00
DTB
c554b96722 swab(1): fix type errors 2024-07-29 11:13:58 -06:00
DTB
7b930363bf npc(1): fix syntax errors 2024-07-29 11:12:02 -06:00
DTB
5545846c92 dj(1): fix fprintio signature 2024-07-29 11:10:17 -06:00
DTB
162c6411b3 mm(1): remove terminate macro 2024-07-29 11:08:30 -06:00
DTB
2e172d93e8 dj(1): interpret all retvals 2024-07-29 11:04:00 -06:00
DTB
549fa98bdb npc(1): interpret all retvals 2024-07-29 10:53:58 -06:00
361b34c50f npc(1): improves program_name resolution 2024-07-29 02:35:59 -06:00
DTB
8bb57bf2e4 Merge branch 'dj-formatting' of git.tebibyte.media:bonsai/harakit into dj-formatting 2024-07-28 21:26:08 -06:00
DTB
ca6148e11f intcmp(1): tweak comment 2024-07-28 21:06:53 -06:00
44d461fb16 scrut(1): return program_name ternary to former position 2024-07-28 18:33:49 -06:00
338a3e7155 intcmp(1): move program_name ternary 2024-07-28 18:31:01 -06:00
6b9d13b8a0 fop(1): better opt matching 2024-07-28 18:27:22 -06:00
68e31058a8 intcmp(1): move program_name ternary 2024-07-28 17:49:39 -06:00
d2ae35eac9 fop(1): fixes glaring issue with newline handling 2024-07-28 01:15:08 -06:00
0f121cbac7 fop(1): more descriptive command arguments variable name 2024-07-28 00:40:16 -06:00
09193a14d5 fop(1): fixes unhandled i/o error 2024-07-28 00:35:55 -06:00
d5d13b84a7 STYLE: repition & Kernighan quote 2024-07-28 00:34:42 -06:00
aed64840ea STYLE: fixes some concerns 2024-07-28 00:12:34 -06:00
d45fa19d5c dj(1): fixes extraneous ternary for program_name 2024-07-27 23:59:32 -06:00
DTB
970b25dee2 str(1): fix brackets 2024-07-27 18:35:02 -06:00
DTB
5d48114083 dj(1), intcmp(1), mm(1), npc(1), scrut(1), str(1): make usage function consistent 2024-07-27 18:32:04 -06:00
DTB
429d064209 mm(1): fix argv use 2024-07-27 18:22:43 -06:00
DTB
c5c0e543e4 mm(1): remove useless extern errno 2024-07-27 18:21:17 -06:00
DTB
cba8394d95 str(1): fix argv parsing and add comments 2024-07-27 18:18:29 -06:00
DTB
199d48d85b swab(1): comment code 2024-07-27 17:59:47 -06:00
c0a5e11eef mm(1): revert changes to program_name 2024-07-25 21:28:42 -06:00
0641a487d7 intcmp(1): fixes pointless condition; formatting 2024-07-25 21:17:30 -06:00
2fe3aa894c dj(1), intcmp(1), mm(1), npc(1), scrut(1), strcmp(1): changes to use and modify program_name 2024-07-24 19:37:07 -06:00
0282b60e65 dj(1), mm(1), npc(1), scrut(1), str(1): consistent argv[0] handling 2024-07-20 07:18:59 -06:00
DTB
f96ed9c1f3 scrut(1): fix syntax error 2024-07-19 19:34:37 -06:00
DTB
19eee6b4e5 scrut(1): replace do/while loop 2024-07-19 19:31:34 -06:00
DTB
9086bf0d08 dj(1): remove do/while statement in read loop 2024-07-19 19:18:04 -06:00
DTB
c8b4f7a8b3 str(1): edit out goto 2024-07-19 19:03:23 -06:00
DTB
71f4a411b6 dj(1): replace do/while in write loop 2024-07-19 18:40:24 -06:00
DTB
a01cea572d dj(1): replace do/while on hard seeking 2024-07-19 18:26:59 -06:00
f7ebe7cf57 STYLE: added 10 rules reference 2024-07-19 17:31:51 -06:00
22bb26b9cb STYLE: added introduction statement 2024-07-19 17:04:49 -06:00
3ba6682ab3 STYLE: extern and use statements rules 2024-07-19 16:41:02 -06:00
DTB
e90d25e30f Merge branch 'strcmp' 2024-07-15 19:52:26 -06:00
DTB
71f372d2c2 Merge branch 'intcmp' 2024-07-15 15:21:31 -06:00
DTB
27ff64dffa intcmp(1): add comments 2024-07-15 14:51:54 -06:00
DTB
becb3bac4e strcmp(1): code clarification 2024-07-15 14:40:26 -06:00
9f2447ce94 STYLE: code block indentation edits and example 2024-07-15 13:38:26 -06:00
fbdf4f9c45 intcmp(1): switch formatting 2024-07-15 13:38:08 -06:00
806ddac8da dj(1): whitespace formatting 2024-07-15 13:31:38 -06:00
a0ed14a089 STYLE: example for trailing comma & add includes guideline 2024-07-15 13:29:12 -06:00
dc2a4a39ba swab(1): note what sysexits are being used 2024-07-15 13:17:00 -06:00
7ff14214c3 npc(1): move bit comment to be next to hex 2024-07-15 13:16:41 -06:00
a3ceb845e3 dj(1): revert some formatting changes 2024-07-15 13:16:05 -06:00
c2e6744e2b dj(1): revert changes to function return type formatting 2024-07-15 13:09:09 -06:00
71e98dbde7 dj.1: fixes more ambiguity 2024-07-15 13:06:34 -06:00
e9496cb4a5 dj.1: fixes ambiguity and false information 2024-07-15 13:04:23 -06:00
789046f694 STYLE: removes do while constraint & reword indentation rule 2024-07-15 13:03:53 -06:00
e7a6632b41 mm(1): improves comments 2024-07-15 12:52:02 -06:00
48dbea0228 mm(1): imports full enum 2024-07-15 12:52:02 -06:00
64f3f73d96 mm(1): formatting 2024-07-15 12:52:02 -06:00
eda5385058 mm.1: small correction 2024-07-15 12:52:02 -06:00
7a0ad78000 mm.1: reflects new changes 2024-07-15 12:52:02 -06:00
DTB
8dc763f05e mm(1): treats trailing arguments as optargs for the last opt 2024-07-15 12:52:01 -06:00
20692d581a mm(1): makes -e block inferring stdout as an output, mm.1: reflects changes to -e 2024-07-15 12:52:01 -06:00
8d693b6664 mm(1): removes debug print 2024-07-15 12:52:01 -06:00
37804aab6b mm(1): fixes comment about flags -a & -t 2024-07-15 12:52:01 -06:00
3b76254599 mm(1): fixes creating files 2024-07-15 12:52:01 -06:00
e972ff468a mm(1): added -t for disabling truncation; mm.1: updated docs 2024-07-15 12:52:01 -06:00
1b3b03cae0 mm(1): rewritten in Rust 2024-07-15 12:51:48 -06:00
DTB
efb3ce626d strcmp(1): fix program_name type 2024-07-15 04:29:43 -06:00
DTB
16f23e11c0 strcmp.1: update docs to match utility 2024-07-15 04:26:57 -06:00
DTB
d87c278be5 strcmp(1): re-style, tweak exits 2024-07-15 04:21:50 -06:00
DTB
5caefbb465 strcmp(1): note used sysexit 2024-07-15 03:45:36 -06:00
DTB
8d743dab7a strcmp(1): add copyright header
I could trace strcmp(1) as far back as
<293436c5ad/src/streq.c>
in my repo.
2024-07-15 03:43:25 -06:00
DTB
2f2270322a intcmp(1): rewrite in rust 2024-07-15 03:14:57 -06:00
DTB
699893af89 intcmp(1): initial rust impl 2024-07-15 03:09:00 -06:00
DTB
9addfc9284 Merge branch 'dj' 2024-07-15 02:36:07 -06:00
ab003f7d4a strcmp(1): commenting 2024-07-14 03:25:52 -06:00
3c243e4a09 str(1): formatting 2024-07-14 03:20:15 -06:00
49031102f2 npc(1): commenting 2024-07-14 03:17:21 -06:00
fd1ed79329 dj(1): return top-of-scope variable 2024-07-14 02:43:01 -06:00
fe0c631d42 dj.1: reverts change to hex literals 2024-07-14 02:41:59 -06:00
aa074ad9b6 dj.1: fixes ambiguity in block size options 2024-07-14 02:37:37 -06:00
b22ded9e98 STYLE: adds do while rule 2024-07-14 02:15:07 -06:00
579bf3b622 STYLE: initial commit 2024-07-14 02:10:18 -06:00
b0602388e7 rpn(1): better comments 2024-07-13 18:29:27 -06:00
a9b388fe4b hru(1): adds more descriptive comments 2024-07-13 18:18:32 -06:00
e4e823a309 fop(1): adds more comments 2024-07-13 18:03:49 -06:00
c7c6ca2c60 mm(1): formatting 2024-07-13 17:23:31 -06:00
26b0c93f4d strcmp(1): returns -1, specifies sysexits imports 2024-07-13 17:04:38 -06:00
35a20dca79 npc(1): specifies sysexits imports, formatting 2024-07-13 17:03:41 -06:00
8421f8be87 mm(1): specifies sysexits imports 2024-07-13 17:02:23 -06:00
0c530dffbf intcmp(1): formatting, fixes argv[0] ternary and usage function 2024-07-13 17:01:53 -06:00
d9dd4e6057 intcmp(1): formatting, lists sysexits imports, allows no args 2024-07-13 16:57:52 -06:00
1dfad87e87 dj(1): lists sysexits imports, fixes negation 2024-07-13 16:56:56 -06:00
99f2b2963a dj(1): more formatting 2024-07-12 16:39:11 -06:00
666c621a02 strcmp(1): more formatting 2024-07-12 16:38:50 -06:00
34cd715e37 mm(1): removes unnecessary macros 2024-07-12 16:22:56 -06:00
958bfa52ed scrut(1): more formatting 2024-07-12 16:18:48 -06:00
59de0262bd strcmp(1): adds copyright header, formatting, removes unused #include 2024-07-12 16:15:41 -06:00
5d2872d050 scrut(1): formatting, removes gotos 2024-07-12 16:04:07 -06:00
6e1e3db6c8 npc(1): forgot to update copyright 2024-07-12 15:55:36 -06:00
9cfc48c960 intcmp(1): formatting, removed gotos 2024-07-12 15:54:30 -06:00
acc3cf3e90 swab(1): formatting, remove gotos 2024-07-12 15:43:00 -06:00
1ad4114882 dj(1): remove unnecessary macros 2024-07-12 15:42:24 -06:00
db0dd02d55 dj(1): more formatting 2024-07-12 15:32:30 -06:00
6cf7fd9794 dj(1): reformatting 2024-07-12 15:23:57 -06:00
DTB
8f8de5de2b dj(1): fix io[0].bufuse underflow 2024-07-08 22:53:44 -06:00
DTB
0df2c9f566 dj(1): fix infiniskipping 2024-07-08 22:48:16 -06:00
DTB
94873a2ddc tests: bonsai/dj.sh: iron out some existing tests 2024-07-08 22:25:18 -06:00
DTB
aa819cabc2 tests: bonsai/npc.sh: initial arg parsing tests 2024-07-08 22:24:18 -06:00
DTB
7939985c98 Merge branch 'main' into testing 2024-07-08 14:53:47 -06:00
DTB
b7bc1f16ad swab(1): use the getopt error message 2024-07-08 14:34:42 -06:00
ca6865688a swab(1): updates getopt usage 2024-07-08 13:23:23 -06:00
DTB
1fd768057c swab(1): don't accept positional arguments 2024-07-08 11:45:01 -06:00
DTB
35d54d84b0 swab(1): don't use the getopt error message 2024-07-08 11:31:35 -06:00
DTB
a141b95293 swab(1): remove -f 2024-07-08 11:30:21 -06:00
DTB
bf06e91be5 Merge branch 'main' into dj 2024-07-08 11:20:52 -06:00
DTB
5d5a6d2172 dj(1): fix retvals 2024-07-07 21:13:44 -06:00
DTB
691e94c0c1 dj(1): error reporting more of the time 2024-07-07 20:33:54 -06:00
45329ccb8c tests/README: initial commit; tests/posix_env, tests/test.sh: updated to match README 2024-07-07 19:19:38 -06:00
cf744efc1b swab(1): fixes not using strerror(3) 2024-07-07 18:21:48 -06:00
DTB
bab3cdd90e dj(1): Io_write: don't add to bufuse 2024-07-07 18:14:48 -06:00
DTB
abfe7046e7 dj(1): fix some type issues 2024-07-05 08:02:09 -06:00
DTB
6ed7089b25 dj(1): statistics now track hard seeks 2024-07-04 21:32:05 -06:00
DTB
571796fe0d dj.1: update man page to match behavior 2024-07-04 21:05:15 -06:00
DTB
9e8b82c4bb dj(1): fix inaccurate statistics after Io_read and Io_write 2024-07-04 20:47:30 -06:00
DTB
906eb92f5a dj(1): (broken) move hard skipping to the main loop 2024-07-04 20:27:31 -06:00
DTB
9f420131ee dj(1): more work adapting hard skipping to the main loop 2024-07-04 20:16:54 -06:00
DTB
1fab60d779 dj(1): no more pointer arithmetic 2024-07-04 20:05:18 -06:00
DTB
fe175cab19 dj(1): add a variable for skipping in the main loop 2024-07-04 20:00:40 -06:00
DTB
8c33f0116c dj(1): move open(2) flags, remove unnecessary comments 2024-07-04 19:45:53 -06:00
DTB
f8c0e0570c dj(1): make Io_write handle prec and rec 2024-07-04 19:36:32 -06:00
DTB
fc0d9e374b dj(1): make printio fprintio 2024-07-04 19:23:09 -06:00
DTB
f49a2d2eb8 dj(1): move prec and rec adjustment into Io_read 2024-07-04 19:21:40 -06:00
DTB
4004a4a006 dj(1): use the retvals of Io_read and Io_write 2024-07-04 18:41:20 -06:00
DTB
cc64561388 dj(1): only include sysexits if they aren't defined 2024-07-03 20:52:41 -06:00
DTB
5b1d4fef88 dj(1): remove Io_fdopen 2024-07-03 19:22:34 -06:00
DTB
2b593559af dj(1): remove Io_bufrpad 2024-07-03 19:06:59 -06:00
DTB
b74160fa4e dj(1): remove Io_bufxfer 2024-07-03 19:04:01 -06:00
DTB
3e1735f778 dj(1): clean up some stray ends 2024-07-03 18:44:42 -06:00
DTB
adda0d9580 dj.1: elaborate on skip/seek behavior, provide another example 2024-07-03 18:30:54 -06:00
DTB
2167f35f58 dj(1): fix segfault when bses are mismatched 2024-07-03 17:59:21 -06:00
DTB
f4b97be1f1 dj(1): iron out Io_bufxapp 2024-07-03 17:50:04 -06:00
DTB
7fe122ac3b dj.1: clarify skip/seek behavior with regards to statistics output 2024-07-03 16:13:20 -06:00
DTB
944feef434 dj(1): Refactor out Io_fdseek entirely 2024-07-03 16:07:02 -06:00
DTB
66ca4b9a12 dj(1): remove unnecessary stderr checks 2024-07-03 15:47:48 -06:00
DTB
3897f44cf8 dj(1): prefix getopt optstring with : 2024-07-03 15:46:11 -06:00
DTB
6548a448c7 dj(1): fix potential skip/seek bug in non-std io 2024-07-03 15:44:42 -06:00
DTB
aff658d611 dj(1): remove debugging vestige, reflow output into printio 2024-07-03 14:50:50 -06:00
DTB
76252305f9 dj(1): remove Io_bufalloc 2024-07-03 14:46:56 -06:00
DTB
1cf67af281 dj(1): add a ton of assertions, fix if statement, fix io[i] mixups 2024-07-03 14:22:23 -06:00
DTB
064abb82a6 dj(1): fix option parsing regression 2024-07-03 13:50:24 -06:00
d1eefcb37e Merge branch 'makefile-improved' 2024-06-30 22:17:39 -06:00
984c1c1f9a Makefile: fixes portability issue 2024-06-30 21:21:02 -06:00
e38ea5b35d Makefile: fixes dist 2024-06-29 08:36:12 -06:00
261c98ad14 Makefile: docs no longer builds every invocation, normalize PREFIX for setting man dir 2024-06-29 08:28:49 -06:00
014485d3c5 Merge branch 'main' into testing 2024-06-29 08:05:13 -06:00
5cfccf75af tests: bonsai/test_env: set -e; tests: strcmp: -h causes error 2024-06-07 23:30:13 -06:00
c88c41b213 tests: adds toki pona locale 2024-06-02 20:14:06 -06:00
406feb3dc7 tests: test.sh: fixes locales when run from root of project 2024-05-27 22:37:15 -06:00
787f0dc6e2 tests: test.sh: actually includes localization variables 2024-05-27 22:23:55 -06:00
94ada03ce4 tests: adds easy localization 2024-05-27 22:17:30 -06:00
9bfc587623 tests: posix: add environment script for sourcing in tests 2024-05-27 22:12:54 -06:00
8508479a5b tests: test.sh: remove superfluous printing of test category 2024-05-27 22:07:17 -06:00
e028844825 tests: test.sh: utilize $BIN 2024-04-27 15:18:09 -06:00
f022121436 tests: posix: cat(1p): added POSIX-compliant cat 2024-04-27 15:16:52 -06:00
e278307daf tests: bonsai: removed redundant code 2024-04-26 20:55:16 -06:00
4ad9e0da92 tests: bonsai: rename aliases sourced script to test_env 2024-04-25 18:57:05 -06:00
0113cf793d tests: bonsai: hru.sh: adds test 2024-04-25 17:20:55 -06:00
1aa2b596d0 tests: aliases: added set -x 2024-04-24 19:28:33 -06:00
c6f30c4195 tests: bonsai: fop.sh: added test 2024-04-24 19:25:55 -06:00
3398fc372c tests: bonsai: mm.sh: fixed copyright 2024-04-24 15:33:17 -06:00
28f2d44e2f CONTRIBUTING: updated to include docs and tests requirement 2024-04-24 15:24:35 -06:00
aefa87d9e5 tests: fixed aliasing and created mm.sh 2024-04-24 15:22:07 -06:00
057f5571d6 moved tests into test directory 2024-04-24 14:58:35 -06:00
DTB
e7021e127c Testfile: make non-executable 2024-03-13 18:20:30 -06:00
DTB
417d7ca405 Testfile: fix syntax 2024-03-11 21:03:53 -06:00
DTB
f7a74dc430 Testfile 2024-03-11 21:01:29 -06:00
DTB
cabe08bca4 TESTING: start testing document/script 2024-02-29 20:33:09 -07:00
45 changed files with 2097 additions and 921 deletions

View File

@@ -99,9 +99,10 @@ 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 [1]. style guide for the usage texts output format [0].
[1] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style> If committing a new utility, please include tests and documentation (see
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:
@@ -130,6 +131,7 @@ $ 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/>

View File

@@ -16,20 +16,33 @@
DESTDIR ?= dist DESTDIR ?= dist
PREFIX ?= /usr/local PREFIX ?= /usr/local
MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \ # for conditionally compiling OS features
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 - | sed 's/ /\n/g' \ SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | tr ' ' '\n' \
| sed -n 's/sysexits\.h//p' || printf 'include\n' | sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc CC ?= cc
RUSTC ?= rustc RUSTC ?= rustc
RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ RUSTFLAGS += --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \ --extern strerror=build/o/libstrerror.rlib \
--extern strerror=build/o/libstrerror.rlib --extern sysexits=build/o/libsysexits.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: docs dj false fop hru intcmp mm npc rpn scrut str strcmp swab true all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
# keep build/include until bindgen(1) has stdin support # keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703 # https://github.com/rust-lang/rust-bindgen/issues/2703
@@ -40,8 +53,8 @@ build:
clean: clean:
rm -rf build dist rm -rf build dist
dist: all dist: all docs
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
@@ -49,10 +62,12 @@ dist: all
install: dist install: dist
cp -r $(DESTDIR)/* / cp -r $(DESTDIR)/* /
include tests/tests.mk
.PHONY: test .PHONY: test
test: build /tmp/getopt test: all $(TESTS) /tmp/getopt
@echo $(TESTS)
/tmp/getopt /tmp/getopt
tests/posix-compat.sh
/tmp/getopt: src/libgetopt.rs /tmp/getopt: src/libgetopt.rs
$(RUSTC) --test -o /tmp/getopt src/libgetopt.rs $(RUSTC) --test -o /tmp/getopt src/libgetopt.rs
@@ -64,9 +79,12 @@ docs: docs/ build
"s/X\.X\.X/$$(git describe --tags --long | cut -d'-' -f1)/g")"; \ "s/X\.X\.X/$$(git describe --tags --long | cut -d'-' -f1)/g")"; \
sed "s/$$original/$$title/g" <"$$file" >"build/$$file"; done 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/libsysexits.rlib build/o/libgetopt.rlib \ rustlibs: build/o/libgetopt.rlib build/o/libstrerror.rlib \
build/o/libstrerror.rlib build/o/libsysexits.rlib $(OSLIB)
build/o/libgetopt.rlib: build src/libgetopt.rs build/o/libgetopt.rlib: build src/libgetopt.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \ $(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
@@ -97,32 +115,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) $(RUSTLIBS) -o $@ src/fop.rs $(RUSTC) $(RUSTFLAGS) -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) $(RUSTLIBS) -o $@ src/hru.rs $(RUSTC) $(RUSTFLAGS) -o $@ src/hru.rs
.PHONY: intcmp .PHONY: intcmp
intcmp: build/bin/intcmp intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.c build build/bin/intcmp: src/intcmp.rs build rustlibs
$(CC) $(CFLAGS) -o $@ src/intcmp.c $(RUSTC) $(RUSTFLAGS) -o $@ src/intcmp.rs
.PHONY: mm .PHONY: mm
mm: build/bin/mm mm: build/bin/mm
build/bin/mm: src/mm.c build build/bin/mm: src/mm.rs build rustlibs
$(CC) $(CFLAGS) -o $@ src/mm.c $(RUSTC) $(RUSTFLAGS) -o $@ src/mm.rs
.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) $(CFLAGAS) -o $@ src/npc.c $(CC) $(CFLAGS) -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) $(RUSTLIBS) -o $@ src/rpn.rs $(RUSTC) $(RUSTFLAGS) -o $@ src/rpn.rs
.PHONY: scrut .PHONY: scrut
scrut: build/bin/scrut scrut: build/bin/scrut
@@ -142,7 +160,7 @@ 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 rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/swab.rs $(RUSTC) $(RUSTFLAGS) -o $@ src/swab.rs
.PHONY: true .PHONY: true
true: build/bin/true true: build/bin/true

124
STYLE Normal file
View File

@@ -0,0 +1,124 @@
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/>.

View File

@@ -4,7 +4,7 @@
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\" .\"
.TH DJ 1 2024-06-29 "Harakit X.X.X" .TH DJ 1 2024-07-14 "Harakit X.X.X"
.SH NAME .SH NAME
dj \(en disk jockey dj \(en disk jockey
.\" .\"
@@ -34,8 +34,13 @@ respectively. This language is inherited from the
.BR dd (1p) .BR dd (1p)
utility and used here to decrease ambiguity. utility and used here to decrease ambiguity.
When seeking or skipping to a byte, writing or reading starts at the byte The offset used when skipping or seeking refers to how many bytes are skipped
immediately subsequent to the specified byte. or sought. Running
.BR dj (1)
with a skip offset of 1 skips one byte into the input and reads from the second
byte onwards. A programmer may think of a file as a zero-indexed array of
bytes; in this analogy, the offset given is the index of the byte at which to
start reading or writing.
.\" .\"
.SH OPTIONS .SH OPTIONS
@@ -51,9 +56,9 @@ bytes read to this point 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\ \fIblock_size\fP
Does the same as Takes a numeric argument as the size in bytes of the output buffer, the default
.B -b being 1024. Note that this option only affects the size of output writes and not
but for the output buffer. the amount of output data itself. See the CAVEATS section.
.IP \fB-S\fP .IP \fB-S\fP
Takes a numeric argument as the index of the byte at which writing will Takes a numeric argument as the index of the byte at which writing will
commence; \(lqseeks\(rq that number of bytes. If the standard output is used, commence; \(lqseeks\(rq that number of bytes. If the standard output is used,
@@ -63,8 +68,8 @@ Accepts a single literal byte with which the 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. If the option argument is empty, the
null byte is used. null byte is used.
.IP \fB-c\fP .IP \fB-c\fP
Specifies a number of reads to make. The default is 0, in which case the Specifies a number of blocks to read. The default is 0, in which case the input
input is read until a partial or empty read is made. is read until a partial or empty read is made.
.IP \fB-H\fP .IP \fB-H\fP
Prints diagnostic messages in a human-readable manner as described in the Prints diagnostic messages in a human-readable manner as described in the
DIAGNOSTICS section. DIAGNOSTICS section.
@@ -80,6 +85,54 @@ input file is \(lq-\(rq.
The standard output shall be used as an output if no inputs are specified or if The standard output shall be used as an output if no inputs are specified or if
the output file is \(lq-\(rq. the output file is \(lq-\(rq.
.\" .\"
.SH EXAMPLES
The following
.BR sh (1p)
line:
.RS
printf 'Hello, world!\(rsn' | dj -c 1 -b 7 -s 7 2>/dev/null
.RE
Produces the following output:
.RS
world!
.RE
The following
.BR sh (1p)
lines run sequentially:
.RS
tr '\(rs0' 0 </dev/zero | dj -c 1 -b 6 -o hello.txt
tr '\(rs0' H </dev/zero | dj -c 1 -b 1 -o hello.txt
tr '\(rs0' e </dev/zero | dj -c 1 -b 1 -o hello.txt -S 1
tr '\(rs0' l </dev/zero | dj -c 1 -b 2 -o hello.txt -S 2
tr '\(rs0' o </dev/zero | dj -c 1 -b 1 -o hello.txt -S 4
tr '\(rs0' '\(rsn' </dev/zero | dj -c 1 -b 1 -o hello.txt -S 5
dj -i hello.txt
.RE
Produce the following output:
.RS
Hello
.RE
It may be particularly illuminating to print the contents of the example
.B hello.txt
after each
.BR dj (1)
invocation.
.\"
.SH DIAGNOSTICS .SH DIAGNOSTICS
On a partial or empty read, a diagnostic message is printed. Then, the program On a partial or empty read, a diagnostic message is printed. Then, the program
@@ -128,9 +181,27 @@ option is specified, this could make written data nonsensical.
Existing files are not truncated on ouput and are instead overwritten. Existing files are not truncated on ouput and are instead overwritten.
Many lowercase options have capitalized variants and vice-versa which can be Option variants that have lowercase and uppercase forms could be confused for
confusing. Capitalized options tend to affect output or are more intense each other. The former affects input and the latter affects output.
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
@@ -151,3 +222,5 @@ 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

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

View File

@@ -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 STRCMP 1 2024-06-17 "Harakit X.X.X" .TH STRCMP 1 2024-07-15 "Harakit X.X.X"
.SH NAME .SH NAME
strcmp \(en compare strings strcmp \(en compare strings
.\" .\"
@@ -20,15 +20,15 @@ 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 of 1 if a string passed has a lesser byte value will exit with an error code less than 128 if a string passed has a lesser byte
than one of the prior strings: value than one of the prior strings:
.RS .RS
strcmp b a strcmp b a
.RE .RE
or with an error code of 255 if it has a greater byte value than one of the or with an error code greater than 128 if it has a greater byte value than one
prior strings: of the prior strings:
.RS .RS
strcmp a b strcmp a b

View File

@@ -11,7 +11,6 @@ swab \(en swap bytes
.SH SYNOPSIS .SH SYNOPSIS
swab swab
.RB [ -f ]
.RB [ -w\ word_size ] .RB [ -w\ word_size ]
.\" .\"
.SH DESCRIPTION .SH DESCRIPTION
@@ -20,8 +19,6 @@ Swap the latter and former halves of a block of bytes.
.\" .\"
.SH OPTIONS .SH OPTIONS
.IP \fB-f\fP
Ignore SIGINT signal.
.IP \fB-w\fP\ \fIword_size\fP .IP \fB-w\fP\ \fIword_size\fP
Configures the word size; that is, the size in bytes of the block size on which Configures the word size; that is, the size in bytes of the block size on which
to operate. The default word size is 2. The word size must be cleanly divisible to operate. The default word size is 2. The word size must be cleanly divisible
@@ -36,6 +33,7 @@ line:
.RS .RS
printf 'hello world!\(rsn' | swab 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:

6
include/FreeBSD.mk Normal file
View File

@@ -0,0 +1,6 @@
# 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.

13
include/OpenBSD.mk Normal file
View File

@@ -0,0 +1,13 @@
# 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

635
src/dj.c
View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024 DTB <trinity@trinity.moe> * Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
* *
* This program is free software: you can redistribute it and/or modify it under * This program is free software: you can redistribute it and/or modify it under
@@ -16,378 +17,422 @@
* along with this program. If not, see https://www.gnu.org/licenses/. * along with this program. If not, see https://www.gnu.org/licenses/.
*/ */
#include <assert.h> /* assert(3) */
#include <errno.h> /* errno */ #include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */ #include <fcntl.h> /* open(2) */
#include <stdbool.h> /* bool */
#include <stdio.h> /* fprintf(3), stderr */ #include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* free(3), malloc(3), strtol(3), size_t */ #include <stdlib.h> /* malloc(3), strtol(3), size_t */
#include <string.h> /* memcpy(3), memmove(3), memset(3) */ #include <string.h> /* memcpy(3), memmove(3), memset(3) */
#include <sysexits.h> /* EX_OK, EX_USAGE */ #include <sysexits.h> /* EX_OK, EX_OSERR, 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),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */ * pledge(2), unveil(2), optarg, optind, STDIN_FILENO,
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, * STDOUT_FILENO */
S_IWUSR */ #include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR */
extern int errno;
char *program_name = "dj"; 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 {
int bs; /* buffer size (-bB) */ char *buf; /* buffer */
char *fn; /* file name (-io) */
size_t bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */ size_t bufuse; /* buffer usage */
char *buf; /* buffer */ size_t bytes; /* bytes processed */
int bytes; /* bytes processed */ size_t prec; /* partial records processed */
int fd; /* file descriptor */ size_t rec; /* records processed */
int fl; /* file opening flags */ long seek; /* remaining bytes to seek/skip (-sS) */
char *fn; /* file name (may be stdin_name or stdout_name) (-io) */ int error; /* errno */
int prec; /* partial records processed */ int fd; /* file descriptor */
int rec; /* records processed */ int fl; /* file opening flags */
long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */ };
};
/* To be assigned to main:fmt_output and used with output(). */
static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034";
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
static char *stdin_name = "<stdin>"; static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>"; static char *stdout_name = "<stdout>";
static int creat_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
| S_IWOTH; /* Consistent with touch(1p). */
static int read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */ static int read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */
static int write_flags = O_WRONLY | O_CREAT; static int write_flags = O_WRONLY | O_CREAT;
#define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b))
/* Macro to check if fd is a std* file, e.g. stdin. */ /* Macro to check if fd is stdin or stdout */
#define fdisstd(fd) \ #define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO)
((fd) == STDIN_FILENO \
|| (fd) == STDOUT_FILENO \
|| (fd) == STDERR_FILENO)
/* Macro to call the cleanup functions that operate on struct io on the /* Completes one Io block read */
* particular io[2] used in main. Error conditions are not checked because this
* is only used when the program is about to terminate (hence its name). */
#define terminate(io) do{ \
free((io[0]).buf); \
free((io[1]).buf); \
Io_fdclose(&(io)[0]); \
Io_fdclose(&(io)[1]); }while(0)
/* Allocates *io's buffer. Returns NULL if unsuccessful. */
static void *
Io_bufalloc(struct Io *io){
return (io->buf = malloc(io->bs * (sizeof *io->buf)));
}
/* Fills the unused portion of io's buffer with padding, updating io->bufuse.
* Returns io. */
static struct Io * static struct Io *
Io_bufrpad(struct Io *io, int padding){ Io_read(struct Io *io) {
memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
io->bufuse = io->bs;
return io;
}
/* Copies from the buffer in src as much as possible to the free space in the
* dest buffer, removing the copied units from src and permuting the remaining
* units in the src buffer to the start of the buffer, modifying both the src
* and dest bufuse and returning dest. */
static struct Io*
Io_bufxapp(struct Io *dest, struct Io *src){
int n;
n = MIN(src->bufuse, dest->bs - dest->bufuse);
memcpy(dest->buf + dest->bufuse, src->buf, n);
dest->bufuse += n;
memmove(src->buf, src->buf + n, src->bs - n);
src->bufuse -= n;
return dest;
}
/* Copies from the buffer in src to the buffer in dest no more than n units,
* removing the copied units from src and permuting the remaining units in the
* src buffer to the start of the buffer, modifying both the src and dest
* bufuse and returning dest. */
static struct Io*
Io_bufxfer(struct Io *dest, struct Io *src, int n){
memcpy(dest->buf, src->buf, (dest->bufuse = n));
memmove(src->buf, src->buf + n, (src->bufuse -= n));
return dest;
}
/* Closes io->fn and returns -1 on error, otherwise io->fd. */
static int
Io_fdclose(struct Io *io){
return fdisstd(io->fd)
? 0
: close(io->fd);
}
/* Opens io->fn and saves the file descriptor into io->fd. Returns io->fd,
* which will be -1 if an error occured. */
static int
Io_fdopen(struct Io *io, char *fn){
int fd;
if((fd = open(fn, io->fl,
/* these are the flags used by touch(1p) */
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH))
!= -1
&& Io_fdclose(io) == 0){
io->fd = fd;
io->fn = fn;
}
return fd;
}
/* Seeks io->seek bytes through *io's file descriptor, subtracting the number
* of sought bytes from io->seek. This procedure leaves garbage in io->buf. */
static void
Io_fdseek(struct Io *io){
if(io->seek != 0
|| (!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1))
return;
if(io->fl == write_flags)
memset(io->buf, '\0', io->bs);
if(io->fl == write_flags){
memset(io->buf, '\0', io->bs);
/* We're going to cheat and use bufuse as the retval for write(2),
* which is fine because it'll be zeroed as this function returns
* anyway. */
do{
if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else if(io->fl == read_flags){
do{
if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}
io->bufuse = 0;
return;
}
/* Reads io->bs bytes from *io's file descriptor into io->buf, storing the
* number of read bytes in io->bufuse and updating io->bytes. If io->bufuse is
* 0, errno will probably be set. Returns io. */
static struct Io *
Io_read(struct Io *io){
io->bytes += (io->bufuse = read(io->fd, io->buf, io->bs));
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; int t;
if((t = write(io->fd, io->buf, io->bufuse)) > 0) assert(io->bs > 0);
memmove(io->buf, io->buf + t, (io->bufuse -= t)); assert(io->bufuse < io->bs);
io->bytes += t;
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;
} }
/* Prints an error message suitable for the event of an operating system error, /* Completes one Io block write */
* with the error itself to be described in the string s. */ static struct Io *
static int Io_write(struct Io *io) {
oserr(char *s){ int t;
fprintf(stderr, "%s: %s: %s\n", program_name, s, strerror(errno)); 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) {
(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;
}
static int
oserr(char *e, int n) { /* program_name: [failing component:] error */
(void)fprintf(stderr, "%s: ", program_name);
if (e != NULL) { (void)fprintf(stderr, "%s: ", e); }
(void)fprintf(stderr, "%s\n", strerror(n));
return EX_OSERR; return EX_OSERR;
} }
/* Prints statistics regarding the use of dj, particularly partially and /* Prints statistics regarding the use of dj, particularly partially and
* completely read and written records. */ * completely read and written records. */
static void static int
output(struct Io io[2], char *fmt){ fprintio(FILE *stream, char *fmt, struct Io io[2]) {
return fprintf(
fprintf(stderr, fmt, stream,
io[0].rec, io[0].prec, io[1].rec, io[1].prec, fmt,
io[0].bytes, io[1].bytes); io[0].rec,
io[0].prec,
return; io[1].rec,
io[1].prec,
io[0].bytes,
io[1].bytes
);
} }
/* To be assigned to main:fmt and used with printio(). */
static char *fmt_asv =
"%d" /* io[0].rec */ "\037" /* ASCII US */
"%d" /* io[0].prec */ "\036" /* ASCII RS */
"%d" /* io[1].rec */ "\037" /* ASCII US */
"%d" /* io[1].prec */ "\035" /* ASCII GS */
"%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) return (*s == '\0' /* no chars left unparsed */ && errno == 0) ? r : -1;
? r
: -1;
} }
static int static int
usage(char *s){ usage(char *argv0) {
(void)fprintf(
fprintf(stderr, "Usage: %s [-Hn] [-a byte] [-c count]\n" stderr,
"Usage: %s [-Hn] [-a byte] [-c count]\n"
"\t[-i file] [-b block_size] [-s offset]\n" "\t[-i file] [-b block_size] [-s offset]\n"
"\t[-o file] [-B block_size] [-S 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 align; /* low 8b used, negative if no alignment is being done */
int count; /* 0 if dj(1) runs until no more reads are possible */ int count; /* -1 if dj(1) runs until no more reads are possible */
char *fmt_output; /* == fmt_asv (default) or fmt_human (-H) */ char *fmt; /* set to fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */ size_t i; /* side of io (in or out) being modified */
struct Io io[2]; bool retry; /* false if exits on partial reads or writes */
char noerror; /* 0=exits (default) 1=retries 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. */ /* Set defaults. */
align = -1; align = -1;
count = 0; count = -1;
fmt_output = fmt_asv; fmt = fmt_asv;
noerror = 0; retry = 0;
for(i = 0; i < 2; ++i){ for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */ io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bufuse = 0;
io[i].bytes = 0; io[i].bytes = 0;
io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO; io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name; io[i].fn = i == 0 ? stdin_name : stdout_name;
io[i].fl = i ? read_flags : write_flags; io[i].fl = i == 0 ? read_flags : write_flags;
io[i].error = 0;
io[i].prec = 0; io[i].prec = 0;
io[i].rec = 0; io[i].rec = 0;
io[i].seek = 0; io[i].seek = 0;
} }
if(argc > 0){ if (argc > 0) {
int c; int c;
program_name = argv[0]; program_name = argv[0];
while((c = getopt(argc, argv, "a:b:B:c:i:hHns:S:o:")) != -1) while ((c = getopt(argc, argv, "a:b:B:c:i:hHns:S:o:")) != -1) {
switch(c){ switch (c) {
case 'i': case 'o': i = (c == 'o'); case 'i': case 'o': /* input, output */
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */ i = (c == 'o');
io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name; /* optarg == "-" (stdin/stdout) */
break; if (optarg[0] == '-' && optarg[1] == '\0') {
}else if(Io_fdopen(&io[i], optarg) != -1) io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
break; io[i].fn = i == 0 ? stdin_name : stdout_name;
terminate(io); break;
return oserr(optarg); } else {
case 'n': noerror = 1; break; int fd;
case 'H': fmt_output = fmt_human; break;
case 'a': #ifdef __OpenBSD__
if(optarg[0] == '\0' || optarg[1] == '\0'){ if (unveil(optarg, i == 0 ? "r" : "wc") == -1) {
align = optarg[0]; 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;
}
}
return oserr(optarg, errno);
/* UNREACHABLE */
case 'n': retry = 1; break; /* retry failed reads once */
case 'H': fmt = fmt_human; break; /* human-readable output */
case 'a': /* input buffer padding */
if (optarg[0] == '\0' || optarg[1] == '\0') {
align = optarg[0];
break;
}
/* FALLTHROUGH */
case 'c': /* number of reads */
case 'b': case 'B': /* input/output block size */
case 's': case 'S': /* (s)kip/(S)eek in input/output */
if (c == 'c' && (count = parse(optarg)) >= 0) { break; }
i = (c >= 'A' && c <= 'Z');
c |= 0x20; /* 0b 0010 0000 (ASCII) make lowercase */
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 */
default:
return usage(program_name);
}
}
}
#ifdef __OpenBSD__
if (unveil(NULL, NULL) == -1) { return oserr(NULL, errno); }
#endif
assert(io->fd != STDIN_FILENO || io->fl == read_flags);
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;
}
/* easy seeking */
if (!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1) {
io[i].seek = 0;
}
}
assert(io[1].bufuse == 0); /* requirement for hard seeking */
/* hard seeking; t is io[1].bufuse, before Io_write subtracts from it */
for(size_t t; io[1].seek > 0; io[1].seek -= (t - io[1].bufuse)) {
(void)memset(
io[1].buf, '\0', /* set buf to all nulls */
(t = io[1].bufuse = MIN(io[1].bs, io[1].seek)) /* saturate block */
);
if (Io_write(&io[1])->bufuse == t && !retry && io[1].error == 0) {
(void)Io_write(&io[1]); /* second chance */
}
if (io[1].error != 0) { return oserr(io[1].fn, io[1].error); }
if (io[1].bufuse == t) { break; } /* all writes failed! */
}
io[1].bufuse = 0; /* reset after hard seek */
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;
break; break;
} }
/* FALLTHROUGH */
case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */
if(c == 'c' && (count = parse(optarg)) >= 0)
break;
i = (c >= 'A' && c <= 'Z'); /* uppercase changes output */
c &= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */
if((c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0))
break;
/* FALLTHROUGH */
default:
terminate(io);
return usage(program_name);
} }
}
if(argc > optind){ if (0 < io[1].bufuse /* && io[1].bufuse < t */) {
terminate(io); (void)fprintf(stderr, "%s: Partial write:\n\t", program_name);
return usage(program_name); (void)fprintio(stderr, fmt, io);
}
for(i = 0; i < 2; ++i){ if(!retry) { count = 1; }
if(Io_bufalloc(&io[i]) == NULL){
fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, io[i].bs);
terminate(io);
return EX_OSERR;
}else if(io[i].seek > 0)
Io_fdseek(&io[i]);
if(io[i].seek > 0){
terminate(io);
return oserr(io[i].fn);
} }
}
} }
do{ /* read */ (void)fprintio(stderr, fmt, io);
Io_read(&io[0]);
if(!noerror && io[0].bufuse == 0)
Io_read(&io[0]); /* second chance */
if(io[0].bufuse == 0) /* that's all she wrote */
break;
else if(io[0].bufuse < io[0].bs){
++io[0].prec;
fprintf(stderr, "%s: Partial read:\n\t", program_name);
output(io, fmt_output);
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&io[0], align);
}else
++io[0].rec;
/* write */ for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
do{ if (io[i].error) { return oserr(io[i].fn, io[i].error); }
int t; }
if(io[1].bs > io[0].bs){
Io_bufxapp(&io[1], &io[0]);
if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1)
continue; /* we could write more */
}else
Io_bufxfer(&io[1], &io[0], MIN(io[0].bufuse, io[1].bs));
t = io[1].bufuse;
Io_write(&io[1]);
if(!noerror && io[1].bufuse == t)
Io_write(&io[1]); /* second chance */
if(t == io[1].bufuse){ /* no more love */
count = 1;
break;
}else if(t > io[1].bufuse && io[1].bufuse > 0){
io[1].prec += 1;
fprintf(stderr, "%s: Partial write:\n\t", program_name);
output(io, fmt_output);
if(!noerror)
count = 1;
}else if(io[1].bufuse == 0 && t < io[1].bs)
++io[1].prec;
else
++io[1].rec;
}while(io[0].bufuse > 0);
}while(count == 0 || --count > 0);
output(io, fmt_output);
terminate(io);
return EX_OK; return EX_OK;
} }

View File

@@ -1,9 +1,19 @@
/* /*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> * Copyright (c) 20232024 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>.
*/ */
int main() { return 1; } #ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
#ifdef __OpenBSD__
pledge(NULL, NULL);
#endif
return 1;
}

View File

@@ -30,10 +30,22 @@ use getopt::GetOpt;
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(); let mut d = '\u{1E}'.to_string(); /* ASCII record separator */
let mut index_arg = 0; let mut optind = 1;
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio proc exec");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_OSERR);
}
}
let usage = format!( let usage = format!(
"Usage: {} [-d delimiter] index command [args...]", "Usage: {} [-d delimiter] index command [args...]",
@@ -42,51 +54,62 @@ fn main() {
while let Some(opt) = argv.getopt("d:") { while let Some(opt) = argv.getopt("d:") {
match opt.opt() { match opt.opt() {
Ok(_) => { Ok("d") => {
/* unwrap because Err(OptError::MissingArg) will be returned if /* delimiter */
* opt.arg() is None */
d = opt.arg().unwrap(); d = opt.arg().unwrap();
index_arg = opt.ind(); optind = opt.ind();
}, },
Err(_) => { _ => {
eprintln!("{}", usage); eprintln!("{}", usage);
exit(EX_USAGE); exit(EX_USAGE);
} }
}; };
} }
let command_arg = index_arg as usize + 1; /* parse the specified index as a number we can use */
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| {
argv.get(command_arg).unwrap_or_else(|| {
eprintln!("{}", usage);
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();
let _ = stdin().read_to_string(&mut buf); if let Err(e) = 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>>();
let opts = argv /* collect arguments for the operator command */
let command_args = argv
.iter() .iter()
.clone() .clone()
.skip(command_arg + 1) .skip(command_arg + 1) /* skip the command name */
.collect::<Vec<&String>>(); .collect::<Vec<&String>>();
let mut spawned = Command::new(argv.get(command_arg).unwrap()) /* spawn the command to operate on the field */
.args(opts) let mut spawned = Command::new(operator)
.args(command_args) /* spawn with the specified arguments */
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .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()); 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",
@@ -96,9 +119,10 @@ 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); drop(child_stdin); /* stay safe! drop your children! */
} }
let output = spawned.wait_with_output().unwrap_or_else(|e| { let output = spawned.wait_with_output().unwrap_or_else(|e| {
@@ -106,17 +130,27 @@ 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();
if replace.pop() != Some(b'\n') { replace = output.stdout; } /* pop trailing newline out if the input did not contain it */
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| {

View File

@@ -27,42 +27,51 @@ extern crate strerror;
extern crate sysexits; extern crate sysexits;
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE }; use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, 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 };
/* list of SI prefixes */
const LIST: [(u32, &str); 10] = [ const LIST: [(u32, &str); 10] = [
(3, "k"), (3, "k"), /* kilo */
(6, "M"), (6, "M"), /* mega */
(9, "G"), (9, "G"), /* giga */
(12, "T"), (12, "T"), /* tera */
(15, "P"), (15, "P"), /* peta */
(18, "E"), (18, "E"), /* exa */
(21, "Z"), (21, "Z"), /* zetta */
(24, "Y"), (24, "Y"), /* yotta */
(27, "R"), (27, "R"), /* ronna */
(30, "Q") (30, "Q"), /* quetta */
]; ];
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, ""));
let mut out = (input as f64, (0_u32, "")); if input < 1000 { return Ok(out); } /* too low to convert */
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 => { None => { /* too big for the laws of computing :( */
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 => { Ordering::Less => { /* c < input */
/* 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 => { Ordering::Equal => { /* c == input */
return Ok((input as f64 / c as f64, (n, p))); return Ok((input as f64 / c as f64, (n, p)));
}, },
Ordering::Greater => {}, Ordering::Greater => {}, /* c > input */
}; };
} }
@@ -71,7 +80,22 @@ 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; }
@@ -96,6 +120,7 @@ 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())

View File

@@ -1,82 +0,0 @@
/*
* 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 [-egl] 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;
}

97
src/intcmp.rs Normal file
View File

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

97
src/libopenbsd.rs Normal file
View File

@@ -0,0 +1,97 @@
/*
* 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
View File

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

230
src/mm.rs Normal file
View File

@@ -0,0 +1,230 @@
/*
* 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,5 +1,6 @@
/* /*
* 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
@@ -16,46 +17,72 @@
* 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), putc(3), stdin, stdout, #include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin,
* EOF */ * stdout, EOF */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */ #include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* getopt(3) */ #include <unistd.h> /* pledge(2), getopt(3) */
#include <sysexits.h>
int main(int argc, char *argv[]){ char *program_name = "npc";
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; char showend = 0; /* print a dollar sign before each newline */
char showtab; char showtab = 0; /* prints tab characters in caret notation */
showend = 0; #ifdef __OpenBSD__
showtab = 0; if (pledge("stdio", NULL) == -1) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
if(argc > 0) if (argc > 0) {
while((c = getopt(argc, argv, "et")) != -1) program_name = argv[0];
switch(c){
case 'e': showend = 1; break; while ((c = getopt(argc, argv, "et")) != -1) {
case 't': showtab = 1; break; switch (c){
default: goto usage; case 'e': showend = 1; break;
case 't': showtab = 1; break;
default: return usage(program_name);
} }
}
if(argc > optind){
usage: fprintf(stderr, "Usage: %s [-et]\n", argv[0]);
return EX_USAGE;
} }
while((c = getc(stdin)) != EOF){ if (argc > optind) { return usage(program_name); }
if((c & 0x80) != 0)
fputs("M-", stdout); while ((c = getc(stdin)) != EOF) {
switch(c ^ 0x80 /* 0b 1000 0000 */){ if ((c & 0x80) != 0 && fputs("M-", stdout) == EOF) {
case 0x7f: fputs("^?", stdout); return ioerr(argv[0]);
}
switch (c ^ 0x80 /* 0b 1000 0000 */) {
case 0x7f: /* ASCII DEL (127d) */
if(fputs("^?", stdout) == EOF) { return ioerr(argv[0]); }
break; break;
case '\n': if(showend) case '\n':
putc('$', stdout); if (showend && fputc('$', stdout) == EOF) {
default: return ioerr(argv[0]);
if(c >= ' ' || c == '\n' || (!showtab && c == '\t')) }
putc(c, stdout); default:
else if (c >= ' ' || c == '\n' || (!showtab && c == '\t')) {
fprintf(stdout, "^%c", c + '@'); if (fputc(c, stdout) == EOF) { return ioerr(argv[0]); }
} else if (fprintf(stdout, "^%c", c + '@') < 0) {
return ioerr(argv[0]);
}
} }
} }

View File

@@ -56,8 +56,14 @@ 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,
@@ -117,8 +123,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(
@@ -133,7 +139,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()
@@ -183,7 +189,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
@@ -191,13 +197,22 @@ 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() { if argv.get(1).is_none() { /* read from stdin */
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) => {
@@ -219,12 +234,13 @@ fn main() -> ExitCode {
}, },
}; };
} }
} else { } else { /* read from argv */
/* 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<String>>() .collect::<Vec<_>>()
.join(" "); .join(" ");
match eval(&input, stack) { match eval(&input, stack) {
@@ -233,7 +249,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::from(0), None => return ExitCode::SUCCESS,
}; };
println!("{}", round_precise(val, precision).to_string()) println!("{}", round_precise(val, precision).to_string())
@@ -244,5 +260,5 @@ fn main() -> ExitCode {
}, },
}; };
} }
ExitCode::from(0) ExitCode::SUCCESS
} }

View File

@@ -17,91 +17,100 @@
* 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) */
#ifndef EX_USAGE #include <sysexits.h> /* EX_OSERR, EX_USAGE */
# include <sysexits.h> #include <unistd.h> /* access(3), getopt(3), pledge(2), unveil(2), F_OK, R_OK,
#endif * W_OK, X_OK */
#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 */
static char args[] = "bcdefghkprsuwxLS"; char *program_name = "scrut";
static char ops[(sizeof args) / (sizeof *args)]; #define OPTS "bcdefgkprsuwxLS"
static char *program_name = "scrut"; /* this is an array so main:sel's size can be known at compile time */
static char opts[] = OPTS;
int main(int argc, char *argv[]){ static int
struct stat buf; usage(char *argv0) {
int c; (void)fprintf(stderr, "Usage: %s [-" OPTS "] file...\n", argv0);
size_t i;
char *p;
if(argc < 2) return EX_USAGE;
goto usage; }
memset(ops, '\0', sizeof ops); int main(int argc, char *argv[]) {
while((c = getopt(argc, argv, args)) != -1) char sel[(sizeof opts) / (sizeof *opts)];
if((p = strchr(args, c)) == NULL)
goto usage; program_name = argv[0] == NULL ? program_name : argv[0];
else
ops[p - args] = c; #ifdef __OpenBSD__
/* straighten out ops */ if (pledge("rpath stdio unveil", NULL) == -1) {
for(i = 0, p = ops; i < (sizeof ops) / (sizeof *ops); ++i) perror(program_name);
if(ops[i] != '\0'){ return EX_OSERR;
*p = ops[i]; }
if(&ops[i] != p++) #endif
ops[i] = '\0';
if (argc < 2) { return usage(program_name); }
{ /* option parsing */
char *p;
memset(sel, '\0', sizeof sel);
for (int c; (c = getopt(argc, argv, opts)) != -1;) {
if ((p = strchr(opts, c)) == NULL) { return usage(argv[0]); }
else {
assert(p - opts < sizeof sel / sizeof *sel); /* bounds check */
sel[p - opts] = c;
}
} }
if(optind == argc) /* straighten out selections; permute out nulls */
goto usage; p = sel;
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'; }
}
}
}
argv += optind; if (optind == argc) { return usage(argv[0]); }
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(i = 0; ops[i] != '\0'; ++i) for (size_t i = 0; sel[i] != '\0'; ++i) {
if(ops[i] == 'e') if (
continue; (sel[i] == 'b' && !S_ISBLK(buf.st_mode))
else if(ops[i] == 'h'){ || (sel[i] == 'c' && !S_ISCHR(buf.st_mode))
usage: fprintf(stderr, "Usage: %s [-%s] file...\n", || (sel[i] == 'd' && !S_ISDIR(buf.st_mode))
argv[0] == NULL || (sel[i] == 'e' && 0)
? program_name || (sel[i] == 'f' && !S_ISREG(buf.st_mode))
: argv[0], || (sel[i] == 'g' && !(buf.st_mode & S_ISGID))
args); || (sel[i] == 'k' && !(buf.st_mode & S_ISVTX))
|| (sel[i] == 'p' && !S_ISFIFO(buf.st_mode))
return EX_USAGE; || (sel[i] == 'r' && access(*argv, R_OK) != 0)
}else if( || (sel[i] == 'u' && !(buf.st_mode & S_ISUID))
(ops[i] == 'b' || (sel[i] == 'w' && access(*argv, W_OK) != 0)
&& !S_ISBLK(buf.st_mode)) || (sel[i] == 'x' && access(*argv, X_OK) != 0)
|| (ops[i] == 'c' || (sel[i] == 'L' && !S_ISLNK(buf.st_mode))
&& !S_ISCHR(buf.st_mode)) || (sel[i] == 'S' && !S_ISSOCK(buf.st_mode))
|| (ops[i] == 'd' ) { return EXIT_FAILURE; }
&& !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,17 +19,21 @@
#include <ctype.h> #include <ctype.h>
#include <stddef.h> /* NULL */ #include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3) */ #include <stdio.h> /* fprintf(3), perror(3) */
#include <stdlib.h> /* EXIT_FAILURE */ #include <stdlib.h> /* size_t, EXIT_FAILURE */
#include <string.h> /* strcmp(3) */ #include <string.h> /* strcmp(3) */
#include <sysexits.h> #include <sysexits.h> /* EX_OSERR, EX_USAGE */
static char *program_name = "str"; #ifdef __OpenBSD__
# 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 },
@@ -41,35 +45,52 @@ static struct {
{ "isprint", isprint }, { "isprint", isprint },
{ "ispunct", ispunct }, { "ispunct", ispunct },
{ "isspace", isspace }, { "isspace", isspace },
{ "isupper", isupper } { "isupper", isupper },
{ NULL, NULL } /* marks end */
}; };
int main(int argc, char *argv[]){ static int
int ctype; usage(char *argv0) {
int i; (void)fprintf(stderr, "Usage: %s type string...\n", argv0);
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)
for(i = 0; argv[0][i] != '\0'; ++i) int main(int argc, char *argv[]) {
/* First checks if argv[0][i] is valid ASCII; ctypes(3) size_t ctype; // selected from ctypes.h; index of ctype
* don't handle non-ASCII. int retval; // initially fail but becomes success on the first valid char
* This is bad. */ program_name = argv[0] == NULL ? program_name : argv[0];
if((unsigned char)argv[0][i] < 0x80 && !ctypes[ctype].f(argv[0][i]))
return 1; #ifdef __OpenBSD__
else if (pledge("stdio", NULL) == -1) {
r = 0; perror(program_name);
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,24 +1,62 @@
#include <stdio.h> /* fprintf(3), stderr */ /*
#include <stdlib.h> /* EXIT_FAILURE */ * Copyright (c) 2023 DTB <trinity@trinity.moe>
#include <sysexits.h> * 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/.
*/
#include <stdio.h> /* fprintf(3), perror(3), stderr */
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
static char *program_name = "strcmp"; #ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
#endif
int main(int argc, char *argv[]){ char *program_name = "strcmp";
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) {
if(*argv[i-1] > *argv[i]) /* a former string has a greater byte value */
if (*argv[i-1] > *argv[i]) {
return 1; return 1;
else if(*argv[i-1] < *argv[i]++) /* a latter string has a greater byte value */
} else if (*argv[i-1] < *argv[i]++) {
return -1; /* actually 255 */ return -1; /* actually 255 */
}
}
}
return 0; return EX_OK;
} }

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024 DTB <trinity@trinity.moe> * Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
* *
* This program is free software: you can redistribute it and/or modify it under * This program is free software: you can redistribute it and/or modify it under
@@ -18,69 +19,90 @@
use std::{ use std::{
env::args, env::args,
io::{ stdin, stdout, Error, ErrorKind, Read, Write }, io::{ stdin, stdout, Error, Read, Write },
process::ExitCode, process::ExitCode,
vec::Vec vec::Vec
}; };
extern crate getopt; extern crate getopt;
use getopt::GetOpt;
extern crate sysexits; extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; extern crate strerror;
fn oserr(s: &str, e: Error) -> ExitCode { use getopt::GetOpt;
eprintln!("{}: {}", s, e); use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
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: {} [-f] [-w word_size]", s); eprintln!("Usage: {} [-w word_size]", 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
let mut force = false; while let Some(opt) = argv.getopt("w:") {
let mut wordsize: usize = 2;
while let Some(opt) = argv.getopt("fw:") {
match opt.opt() { match opt.opt() {
Ok("f") => force = true,
Ok("w") => { Ok("w") => {
if let Some(arg) = opt.arg() { match opt.arg().unwrap().parse::<usize>() {
match arg.parse::<usize>() { Ok(w) if w % 2 == 0 => { wordsize = w; },
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); }, _ => { return usage(&argv[0]); },
} }
} optind = opt.ind();
}, },
_ => { return usage(&argv[0]); } _ => { return usage(&argv[0]); }
} }
} }
if optind < argv.len() {
return usage(&argv[0]);
}
buf.resize(wordsize, 0); buf.resize(wordsize, 0);
loop { loop {
match input.read(&mut buf) { match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8), Ok(0) => break ExitCode::from(EX_OK as u8), // read nothing; bye
Ok(v) if v == wordsize => { 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 oserr(&argv[0], e) break ioerr(&argv[0], e);
} }
}, },
Ok(v) => { Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) { if let Err(e) = output.write(&buf[..v]) {
break oserr(&argv[0], e) break ioerr(&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,9 +1,18 @@
/* /*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> * Copyright (c) 20232024 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>.
*/ */
int main() {} #ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
#ifdef __OpenBSD__
pledge(NULL, NULL);
#endif
}

36
tests/README Normal file
View File

@@ -0,0 +1,36 @@
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/>.

48
tests/bonsai/dj.mk Executable file
View File

@@ -0,0 +1,48 @@
# 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

18
tests/bonsai/false.mk Executable file
View File

@@ -0,0 +1,18 @@
# 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

31
tests/bonsai/fop.mk Executable file
View File

@@ -0,0 +1,31 @@
# 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')"

32
tests/bonsai/hru.mk Executable file
View File

@@ -0,0 +1,32 @@
# 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"

67
tests/bonsai/intcmp.mk Executable file
View File

@@ -0,0 +1,67 @@
# 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?

34
tests/bonsai/mm.mk Executable file
View File

@@ -0,0 +1,34 @@
# 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

90
tests/bonsai/npc.mk Executable file
View File

@@ -0,0 +1,90 @@
#!/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

43
tests/bonsai/rpn.mk Executable file
View File

@@ -0,0 +1,43 @@
# 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

46
tests/bonsai/scrut.mk Executable file
View File

@@ -0,0 +1,46 @@
#!/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

20
tests/bonsai/str.mk Executable file
View File

@@ -0,0 +1,20 @@
# 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

31
tests/bonsai/strcmp.mk Executable file
View File

@@ -0,0 +1,31 @@
# 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

22
tests/bonsai/swab.mk Executable file
View File

@@ -0,0 +1,22 @@
# 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

19
tests/bonsai/true.mk Executable file
View File

@@ -0,0 +1,19 @@
#!/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

View File

@@ -1,24 +0,0 @@
#!/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

22
tests/posix/bin/cat Executable file
View File

@@ -0,0 +1,22 @@
#!/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

12
tests/posix/bin/false Executable file
View File

@@ -0,0 +1,12 @@
#!/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 "$@"

11
tests/posix/bin/true Executable file
View File

@@ -0,0 +1,11 @@
#!/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 "$@"

5
tests/posix/posix_env Normal file
View File

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

15
tests/tests.mk Normal file
View File

@@ -0,0 +1,15 @@
# 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)