41 Commits

Author SHA1 Message Date
127192185f Merge branch 'dj-fix' (closes #66) 2024-03-18 21:01:48 -06:00
f81232685a Merge branch 'mm' 2024-03-18 20:56:06 -06:00
05b5a4480c Merge branch 'swab' (closes #22) 2024-03-18 20:53:08 -06:00
DTB
135bf2a8eb mm(1), mm.1: bring source in line with documentation 2024-02-26 09:11:47 -07:00
DTB
d74bc715cf mm(1): fix full file handling 2024-02-26 09:02:58 -07:00
DTB
f14877118d Makefile: add swab(1) 2024-02-26 08:43:03 -07:00
DTB
bbac85daf8 swab(1): add copyright notice 2024-02-26 08:42:06 -07:00
DTB
1e041a52a2 swab.1: add swab(1) man page 2024-02-24 03:21:20 -07:00
DTB
e788947fc4 swab(1): add argument parsing 2024-02-24 03:04:05 -07:00
c97201fca9 Merge branch 'contributing-changes' 2024-02-23 23:20:16 -07:00
06fc461985 CONTRIBUTING: fixed random "utils" 2024-02-23 23:19:28 -07:00
e6b1db3f40 Merge branch 'hru' (closes #33) 2024-02-23 22:15:43 -07:00
ef1643660b hru(1): fixed lack of newline 2024-02-23 22:11:23 -07:00
b0636b416e Merge branch 'fop-trim' (closes #54, closes #58) 2024-02-23 22:05:56 -07:00
bd08ef479c hru(1): error handling tweaks 2024-02-23 21:58:23 -07:00
3cee3de900 fop(1): committing to Rust error messages 2024-02-23 21:46:50 -07:00
DTB
3f0d95fe8f swab(1): minimum viable program 2024-02-23 20:49:24 -07:00
DTB
58245c9484 dj(1): remove function pointer hijinks (fixes #66) 2024-02-22 19:27:14 -07:00
DTB
395205d4c6 mm.1: fix case in copyright thingy 2024-02-22 19:14:38 -07:00
90de1bf9a4 Makefile: RUSTFLAGS not RUSTCFLAGS 2024-02-18 20:03:04 -07:00
1c2e7ea14b Merge branch 'scrut-manpage' (fixes #49) 2024-02-18 15:20:13 -07:00
0d445c71f4 CONTRIBUTING: fixed wording and updated 2024-02-18 15:09:09 -07:00
ee3877b607 hru.1: update manpage to be more clear 2024-02-18 14:59:12 -07:00
0801d89128 hru(1): fixed kilo prefix 2024-02-18 14:49:09 -07:00
DTB
194b19f94b mm(1): add 2024-02-17 23:43:22 -07:00
DTB
e93745ef28 .editorconfig: remove stale configure config 2024-02-17 23:25:35 -07:00
DTB
de9f8dc037 scrut.1: update man page based on review (see #62) 2024-02-17 23:12:39 -07:00
413ee49af4 hru.1: updated manpage with correct SI reference 2024-02-16 21:36:16 -07:00
DTB
bee0165074 scrut(1): add man page 2024-02-16 01:40:38 -07:00
361f0ddb8b hru.1: added manpage for hru(1) 2024-02-14 23:31:54 -07:00
448211bbe2 fop(1): better newline removal (preserves existing newlines) 2024-02-14 23:05:39 -07:00
4c663bf9dd fop(1): closure fix 2024-02-14 22:55:28 -07:00
f8e3013563 fop(1): fixed trimming and handled unwraps (closes #58) 2024-02-14 22:53:45 -07:00
e3a0069180 hru(1): improved SI prefix logic 2024-02-14 00:07:06 -07:00
1299aefc39 hru(1): fixed overflow 2024-02-13 23:56:01 -07:00
d0205b71da hru(1): read stdin until EOF 2024-02-13 17:48:25 -07:00
1ee668aed6 hru(1): made it actually work 2024-02-13 17:32:31 -07:00
d45e3410f8 CONTRIBUTING: fixed some small issues 2024-02-07 22:03:08 -07:00
452a1295e6 CONTRIBUTING: updated and added copyright info 2024-02-07 21:54:46 -07:00
fa686eefb9 hru(1): updated copyright year 2024-02-07 21:42:43 -07:00
be89e72c45 Makefile, hru(1): add hru(1) 2024-02-07 20:58:57 -07:00
12 changed files with 825 additions and 51 deletions

View File

@@ -12,6 +12,3 @@ indent_size = 2
[*.sh]
indent_size = 2
[configure]
indent_size = 2

View File

@@ -90,26 +90,44 @@ notice:
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
When writing code, make sure lines never exceed 80 characters in width when
using four-character-wide indentation steps.
Make sure lines never exceed 80 columns in width when using four-character
indentation steps. This helps contributors with smaller screens, those using
side-by-side editor windows or panes, and those who have no text wrapping in
their editor or terminal.
For usage text and help messages, please do not implement a -h option. Just
print usage information when any erroneous option is specified. Follow the
NetBSD style guide for usage text output format [1].
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
style guide for the usage texts output format [1].
If committing a new source file for a utility, format the commit message like
this:
[1] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
$ git commit -m 'tool(1): <information>'
If committing a new source file, format the commit message following these
guidelines:
$ git commit -m 'tool(1): add feature x'
If committing a new library or header file:
$ git commit -m 'library(1): <information>'
$ git commit -m 'library(3): fix overflow'
$ git commit -m 'header.h(3): add header.h(3)'
If committing a new manual page:
$ git commit -m 'tool.1: add author details'
If modifying some other file or directory:
$ git commit -m 'README: clarification'
$ git commit -m 'tests: posix: fixed bug #47'
$ git commit -m 'docs: tool(1): added author information'
$ git commit -m 'README: clarify'
$ git commit -m 'tests/posix: fix bug #47'
etc.
For multiple of these:
$ git commit -m 'Makefile, tool(1): add tool(1)'
$ git commit -m 'tool(1): add tool(1); library(3), library.3: add library(3)'
$ git commit -m 'tool(1): fix #42 & add feature x'
Commit messages should be written in the present tense.
--
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/>

View File

@@ -17,7 +17,7 @@ CC=cc
RUSTC=rustc
.PHONY: all
all: dj false fop intcmp rpn scrut str strcmp true
all: dj false fop hru intcmp mm rpn scrut str strcmp swab true
build:
# keep build/include until bindgen(1) has stdin support
@@ -54,7 +54,7 @@ build/o/libsysexits.rlib: build
| $(RUSTC) $(RUSTFLAGS) --crate-type lib -o build/o/libsysexits.rlib -
build/o/libgetopt.rlib: src/getopt-rs/lib.rs
$(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
-o build/o/libgetopt.rlib src/getopt-rs/lib.rs
.PHONY: dj
@@ -74,11 +74,23 @@ build/bin/fop: src/fop.rs build build/o/libgetopt.rlib build/o/libsysexits.rlib
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/fop.rs
.PHONY: hru
hru: build/bin/hru
build/bin/hru: src/hru.rs build build/o/libgetopt.rlib build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/hru.rs
.PHONY: intcmp
intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.c build
$(CC) $(CFLAGS) -o $@ src/intcmp.c
.PHONY: mm
mm: build/bin/mm
build/bin/mm: src/mm.c build
$(CC) $(CFLAGS) -o $@ src/mm.c
.PHONY: rpn
rpn: build/bin/rpn
build/bin/rpn: src/rpn.rs build build/o/libsysexits.rlib
@@ -101,6 +113,13 @@ strcmp: build/bin/strcmp
build/bin/strcmp: src/strcmp.c build
$(CC) $(CFLAGS) -o $@ src/strcmp.c
.PHONY: swab
swab: build/bin/swab
build/bin/swab: src/swab.rs build build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/swab.rs
.PHONY: true
true: build/bin/true
build/bin/true: src/true.c build

57
docs/hru.1 Normal file
View File

@@ -0,0 +1,57 @@
.\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" 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/>.
.TH rpn 1
.SH NAME
hru \(en human readable units
.SH SYNOPSIS
hru
.SH DESCRIPTION
Hru reads byte counts in the form of whole numbers from the standard input and
writes to the standard output the same number converted one of the units of data
defined by the International System of Units.
The program will convert the byte count to the highest unit possible where the
value is greater than one.
.SH DIAGNOSTICS
If encountering non-integer characters in the standard input, hru will exit with
the appropriate error code as defined by sysexits.h(3) and print an error
message.
.SH RATIONALE
The GNU projects ls(1) implementation contains a human-readable option (-h)
that, when specified, makes the tool print size information in a format more
immediately readable. This functionality is useful not only in the context of
ls(1) so the decision was made to split it into a new tool. The original
functionality in GNUs ls(1) can be emulated with fop(1) combined with this
program.
.SH STANDARDS
Hru follows the standard unit prefixes as specified by the Bureau International
des Poids et Mesures (BIPM) in the ninth edition of The International System of
Units (SI).
.SH AUTHOR
Written by Emma Tebibyte <emma@tebibyte.media>.
.SH COPYRIGHT
Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
GNU ls(1), The International System of Units (SI) 9th Edition

76
docs/mm.1 Normal file
View File

@@ -0,0 +1,76 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" 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/>.
.TH mm 1
.SH NAME
mm \(en middleman
.SH SYNOPSIS
mm
.RB ( -aenu )
.RB ( -i
.RB [ input ])
.RB ( -o
.RB [ output ])
.SH DESCRIPTION
Mm catenates input files and writes them to the start of each output file.
.SH OPTIONS
Mm, upon receiving the
.B -a
option, will open subsequent outputs for appending rather than updating.
.PP
The
.B -i
option opens a path as an input. Without any inputs specified mm will use
standard input. Standard input itself can be specified by giving the path '-'.
.PP
The
.B -o
option opens a path as an output. Without any outputs specified mm will use
standard output. Standard output itself can be specified by giving the
path '-'. Standard error itself can be specified with the
.B -e
option.
.PP
The
.B -u
option ensures neither input or output will be buffered.
.PP
The
.B -n
option tells mm to ignore SIGINT signals.
.SH DIAGNOSTICS
If an output can no longer be written mm prints a diagnostic message, ceases
writing to that particular output, and if there are more outputs specified,
continues, eventually exiting unsuccessfully.
.PP
On error mm prints a diagnostic message and exits with the appropriate
sysexits.h(3) status.
.SH BUGS
Mm does not truncate existing files, which may lead to unexpected results.
.SH RATIONALE
Mm was modeled after the cat and tee utilities specified in POSIX.
.SH COPYRIGHT
Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
cat(1p), dd(1), dj(1), tee(1p)

93
docs/scrut.1 Normal file
View File

@@ -0,0 +1,93 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" 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/>.
.TH scrut 1
.SH NAME
scrut \(en scrutinize file properties
.SH SYNOPSIS
scrut
.RB ( -bcdefgkprsuwxLS )
.RB [ file... ]
.SH DESCRIPTION
Scrut determines if given files comply with the opted requirements.
.SH OPTIONS
.B -b
requires the given files to exist and be block special files.
.PP
.B -c
requires the given files to exist and be character special files.
.PP
.B -d
requires the given files to exist and be directories.
.PP
.B -e
requires the given files to exist, and is redundant to any other option.
.PP
.B -e
requires the given files to exist and be regular files.
.PP
.B -g
requires the given files to exist and have their set group ID flags set.
.PP
.B -k
requires the given files to exist and have their sticky bit set.
.PP
.B -p
requires the given files to exist and be named pipes.
.PP
.B -r
requires the given files to exist and be readable.
.PP
.B -u
requires the given files to exist and have their set user ID flags set.
.PP
.B -w
requires the given files to exist and be writable.
.PP
.B -x
requires the given files to exist and be executable.
.PP
.B -L
requires the given files to exist and be symbolic links.
.PP
.B -S
requires the given files to exist and be sockets.
.SH EXIT STATUS
Scrut prints a debug message and exits unsuccessfully with the appropriate
sysexits.h(3) error code if invoked incorrectly. Scrut exits successfully if
the given files comply with their requirements and unsuccessfully otherwise.
.SH STANDARDS
Scrut is nearly compatible with POSIX's test utility though it is narrower in
scope. Notably, the
.B -h
option is now invalid and therefore shows usage information instead of being an
alias to the modern
.B -L
option.
.SH AUTHOR
Written by DTB <trinity@trinity.moe>.
.SH COPYRIGHT
Copyright © 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
access(3p), lstat(3p), test(1p)

71
docs/swab.1 Normal file
View File

@@ -0,0 +1,71 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" 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/>.
.TH swab 1
.SH NAME
swab \(en swap bytes
.SH SYNOPSIS
swab
.RB ( -f )
.RB ( -w
.R [
.B word size
.R ])
.SH USAGE
Swab swaps the latter and former halves of a block of bytes.
.SH EXAMPLES
The following sh(1p) line:
.R printf 'hello world!\n' | swab
Produces the following output:
.R ehll oowlr!d
.SH OPTIONS
The
.B -f
option ignores system call interruptions.
.PP
The
.B -w
option configures the word size; that is, the size in bytes of the block size
on which to operate. By default the word size is 2. The word size must be
cleanly divisible by 2, otherwise the block of bytes being processed can't be
halved.
.SH DIAGNOSTICS
If an error is encountered in input, output, or invocation, a diagnostic
message will be written to standard error and swab will exit with the
appropriate status from sysexits.h(3).
.SH RATIONALE
Swab was modeled after the
.R conv=swab
functionality specified in the POSIX dd utility but additionally allows the
word size to be configured.
.PP
Swab is useful for fixing the endianness of binary files produced on other
machines.
.SH COPYRIGHT
Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
dd(1p)

View File

@@ -203,28 +203,28 @@ Io_fdseek(struct Io *io){
if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1)
return -1;
else if(io->fl == write_flags){
memset(io->buf, '\0', io->bs);
/* This is a dirty trick; rather than testing conditions and operating
* likewise, because the parameters to read or write are going to be
* the same either way, just use a function pointer to keep track of
* the intended operation. */
op = (int (*)(int, void *, size_t))&write;
/* Function pointer casts are risky; this works because the difference
* is in the second parameter and only that write(2) makes the buffer
* const whereas read(2) does not. To avoid even the slightest
* undefined behavior comment out the cast, just be ready for a
* -Wincompatible-function-pointer-types if your compiler notices it.
*/
}else
op = &read;
/* 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 = (*op)(io->fd, io->buf, MIN(io->bs, io->seek))) == 0)
/* second chance */
io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
/* repeated code to get the condition out of the loop */
if(io->fl == write_flags){
memset(io->buf, '\0', io->bs);
/* We're going to cheat and use bufuse as the retval for write(2),
* which is fine because it'll be zeroed as this function returns
* anyway. */
do{
if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else if(io->fl == read_flags){
do{
if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else
return EX_SOFTWARE;
io->bufuse = 0;

View File

@@ -18,7 +18,7 @@
use std::{
env::args,
io::{ Read, stdin, Write },
io::{ Read, stdin, stdout, Write },
process::{ Command, exit, Stdio },
};
@@ -26,7 +26,7 @@ extern crate sysexits;
extern crate getopt;
use getopt::{ Opt, Parser };
use sysexits::{ EX_DATAERR, EX_USAGE };
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
fn main() {
let argv = args().collect::<Vec<String>>();
@@ -54,43 +54,65 @@ fn main() {
exit(EX_USAGE);
});
let index = argv[index_arg].parse::<usize>().unwrap_or_else(|_| {
eprintln!("{}: {}: Not an integer.", argv[0], argv[1]);
let index = argv[index_arg].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}.", argv[0], argv[1], e);
exit(EX_DATAERR);
});
let mut buf = String::new();
stdin().read_to_string(&mut buf).unwrap();
let _ = stdin().read_to_string(&mut buf);
let mut fields = buf.split(d).collect::<Vec<&str>>();
let opts = argv.iter().clone().skip(command_arg + 1).collect::<Vec<&String>>();
let opts = argv
.iter()
.clone()
.skip(command_arg + 1)
.collect::<Vec<&String>>();
let mut spawned = Command::new(argv.get(command_arg).unwrap())
.args(opts)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
.unwrap_or_else( |e| {
eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e);
exit(EX_UNAVAILABLE);
});
let field = fields.get(index).unwrap_or_else(|| {
eprintln!(
"{}: {}: No such index in input.",
argv[0],
index.to_string()
index.to_string(),
);
exit(EX_DATAERR);
});
if let Some(mut child_stdin) = spawned.stdin.take() {
child_stdin.write_all(field.as_bytes()).unwrap();
let _ = child_stdin.write_all(field.as_bytes());
drop(child_stdin);
}
let output = spawned.wait_with_output().unwrap();
let output = spawned.wait_with_output().unwrap_or_else(|e| {
eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e);
exit(EX_IOERR);
});
let new_field = String::from_utf8(output.stdout).unwrap();
let mut replace = output.stdout.clone();
if replace.pop() != Some(b'\n') { replace = output.stdout; }
let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e);
exit(EX_IOERR);
});
fields[index] = &new_field;
print!("{}", fields.join(&d.to_string()));
stdout().write_all(
fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e|{
eprintln!("{}: {}.", argv[0], e);
exit(EX_IOERR);
});
}

107
src/hru.rs Normal file
View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
cmp::Ordering,
env::args,
io::{ stdin, stdout, Write },
process::{ ExitCode, exit },
};
extern crate sysexits;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE };
const LIST: [(u32, &str); 10] = [
(3, "k"),
(6, "M"),
(9, "G"),
(12, "T"),
(15, "P"),
(18, "E"),
(21, "Z"),
(24, "Y"),
(27, "R"),
(30, "Q")
];
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
let mut out = (input as f64, (0_u32, ""));
if input < 1000 { return Ok(out); }
for (n, p) in LIST {
let c = match 10_u128.checked_pow(n) {
Some(c) => c,
None => {
return Err(format!("10^{}: Integer overflow.", n.to_string()));
},
};
match c.cmp(&input) {
Ordering::Less => {
out = (input as f64 / c as f64, (n, p));
},
Ordering::Equal => {
return Ok((input as f64 / c as f64, (n, p)));
},
Ordering::Greater => {},
};
}
Ok(out)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut buf = String::new();
while let Ok(_) = stdin().read_line(&mut buf) {
if buf.is_empty() { return ExitCode::SUCCESS; }
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(err) => {
eprintln!("{}: {}.", argv[0], err);
return ExitCode::from(EX_DATAERR as u8);
},
};
let (number, prefix) = match convert(n) {
Ok(x) => x,
Err(err) => {
eprintln!("{}: {}.", argv[0], err);
return ExitCode::from(EX_SOFTWARE as u8);
},
};
let si_prefix = format!("{}B", prefix.1);
let out = ((number * 10.0).round() / 10.0).to_string();
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())
.unwrap_or_else(|e| {
eprintln!("{}: {}.", argv[0], e);
exit(EX_IOERR);
});
}
ExitCode::SUCCESS
}

224
src/mm.c Normal file
View File

@@ -0,0 +1,224 @@
/*
* 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
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:
fprintf(stderr, "Usage: %s (-aenu) (-i [input])..."
" (-o [output])...\n", argv[0]);
retval = EX_USAGE;
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;
}

90
src/swab.rs Normal file
View File

@@ -0,0 +1,90 @@
/*
* 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,
io::{ stdin, stdout, Error, ErrorKind, Read, Write },
process::ExitCode,
vec::Vec
};
extern crate getopt;
use getopt::{ Opt, Parser };
extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
fn oserr(s: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", s, e);
ExitCode::from(EX_OSERR as u8)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} (-f) (-w [wordsize])", s);
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut buf: Vec<u8> = Vec::new();
let mut input = stdin();
let mut output = stdout().lock();
let mut opts = Parser::new(&argv, "fw:");
let mut force = false;
let mut wordsize: usize = 2;
loop {
match opts.next() {
None => break,
Some(opt) =>
match opt {
Ok(Opt('f', None)) => force = true,
Ok(Opt('w', Some(arg))) => {
match arg.parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
},
_ => { return usage(&argv[0]); }
}
}
}
buf.resize(wordsize, 0);
loop {
match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8),
Ok(v) if v == wordsize => {
let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
break oserr(&argv[0], e)
}
},
Ok(v) => {
if let Err(e) = output.write(&buf[..v]) {
break oserr(&argv[0], e)
}
},
Err(e) if e.kind() == ErrorKind::Interrupted && force => continue,
Err(e) => break oserr(&argv[0], e)
}
}
}