diff --git a/Makefile b/Makefile index d564a3c..70ab13c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # Copyright (c) 2023–2024 Emma Tebibyte # Copyright (c) 2023–2024 DTB # Copyright (c) 2023 Sasha Koshka +# Copyright (c) 2024 Aaditya Aryal # SPDX-License-Identifier: FSFAP # # Copying and distribution of this file, with or without modification, are @@ -8,16 +9,26 @@ # notice are preserved. This file is offered as-is, without any warranty. .POSIX: + +# if using BSD make(1), remove these pragmas because they break it .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1) .PRAGMA: command_comment # breaks without this? -PREFIX=/usr/local +DESTDIR ?= dist +PREFIX ?= /usr/local -CC=cc -RUSTC=rustc +SYSEXITS != printf '\043include \n' | cpp -M - | sed 's/ /\n/g' \ + | sed -n 's/sysexits\.h//p' || printf 'include\n' + +CC ?= cc +RUSTC ?= rustc +RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ + --extern sysexits=build/o/libsysexits.rlib \ + --extern strerror=build/o/libstrerror.rlib +CFLAGS += -I$(SYSEXITS) .PHONY: all -all: dj false fop hru intcmp mm rpn scrut str strcmp swab true +all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true build: # keep build/include until bindgen(1) has stdin support @@ -26,36 +37,40 @@ build: .PHONY: clean clean: - rm -rf build/ dist/ + rm -rf build dist dist: all - mkdir -p dist/bin dist/share/man/man1 - cp build/bin/* dist/bin/ - cp docs/*.1 dist/share/man/man1/ + mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 + cp build/bin/* $(DESTDIR)/$(PREFIX)/bin + cp docs/*.1 $(DESTDIR)/$(PREFIX)/share/man/man1 .PHONY: install install: dist - mkdir -p $(PREFIX) - cp -r dist/* $(PREFIX)/ + cp -r $(DESTDIR)/* / .PHONY: test test: build tests/posix-compat.sh $(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt -build/o/libsysexits.rlib: build +.PHONY: rustlibs +rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \ + build/o/libstrerror.rlib + +build/o/libgetopt.rlib: build src/getopt-rs/lib.rs + $(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \ + -o $@ src/getopt-rs/lib.rs + +build/o/libstrerror.rlib: build src/strerror.rs + $(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \ + src/strerror.rs + +build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h # bandage solution until bindgen(1) gets stdin support - printf '#define EXIT_FAILURE 1\n' | cat - include/sysexits.h \ + printf '#define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \ > build/include/sysexits.h bindgen --default-macro-constant-type signed --use-core --formatter=none \ - "$$(printf '#include \n' \ - | cpp -M -idirafter "build/include" - \ - | sed 's/ /\n/g' | grep sysexits.h)" \ - | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o build/o/libsysexits.rlib - - -build/o/libgetopt.rlib: src/getopt-rs/lib.rs - $(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \ - -o build/o/libgetopt.rlib src/getopt-rs/lib.rs + build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ - .PHONY: dj dj: build/bin/dj @@ -69,17 +84,13 @@ build/bin/false: src/false.c build .PHONY: fop fop: build/bin/fop -build/bin/fop: src/fop.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/fop.rs +build/bin/fop: src/fop.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -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 +build/bin/hru: src/hru.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/hru.rs .PHONY: intcmp intcmp: build/bin/intcmp @@ -91,12 +102,16 @@ mm: build/bin/mm build/bin/mm: src/mm.c build $(CC) $(CFLAGS) -o $@ src/mm.c + +.PHONY: npc +npc: build/bin/npc +build/bin/npc: src/npc.c build + $(CC) $(CFLAGAS) -o $@ src/npc.c + .PHONY: rpn rpn: build/bin/rpn -build/bin/rpn: src/rpn.rs build build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) \ - --extern sysexits=build/o/libsysexits.rlib \ - -o $@ src/rpn.rs +build/bin/rpn: src/rpn.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs .PHONY: scrut scrut: build/bin/scrut diff --git a/src/fop.rs b/src/fop.rs index c9a767e..b829602 100644 --- a/src/fop.rs +++ b/src/fop.rs @@ -22,10 +22,12 @@ use std::{ process::{ Command, exit, Stdio }, }; -extern crate sysexits; extern crate getopt; +extern crate strerror; +extern crate sysexits; use getopt::{ Opt, Parser }; +use strerror::StrError; use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; fn main() { @@ -55,7 +57,7 @@ fn main() { }); let index = argv[index_arg].parse::().unwrap_or_else(|e| { - eprintln!("{}: {}: {}.", argv[0], argv[1], e); + eprintln!("{}: {}: {}", argv[0], argv[1], e); exit(EX_DATAERR); }); @@ -75,13 +77,13 @@ fn main() { .stdout(Stdio::piped()) .spawn() .unwrap_or_else( |e| { - eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e); + eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); exit(EX_UNAVAILABLE); }); let field = fields.get(index).unwrap_or_else(|| { eprintln!( - "{}: {}: No such index in input.", + "{}: {}: No such index in input", argv[0], index.to_string(), ); @@ -94,7 +96,7 @@ fn main() { } let output = spawned.wait_with_output().unwrap_or_else(|e| { - eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e); + eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); exit(EX_IOERR); }); @@ -103,7 +105,7 @@ fn main() { 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); + eprintln!("{}: {}: {}", argv[0], argv[command_arg], e); exit(EX_IOERR); }); @@ -111,8 +113,8 @@ fn main() { stdout().write_all( fields.join(&d.to_string()).as_bytes() - ).unwrap_or_else(|e|{ - eprintln!("{}: {}.", argv[0], e); + ).unwrap_or_else(|e| { + eprintln!("{}: {}", argv[0], e.strerror()); exit(EX_IOERR); }); } diff --git a/src/hru.rs b/src/hru.rs index 0e0b25d..b7937f7 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -23,8 +23,10 @@ use std::{ process::{ ExitCode, exit }, }; +extern crate strerror; extern crate sysexits; +use strerror::StrError; use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE }; const LIST: [(u32, &str); 10] = [ @@ -49,7 +51,7 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> { let c = match 10_u128.checked_pow(n) { Some(c) => c, None => { - return Err(format!("10^{}: Integer overflow.", n.to_string())); + return Err(format!("10^{}: Integer overflow", n.to_string())); }, }; @@ -79,7 +81,7 @@ fn main() -> ExitCode { f }, Err(err) => { - eprintln!("{}: {}.", argv[0], err); + eprintln!("{}: {}", argv[0], err); return ExitCode::from(EX_DATAERR as u8); }, }; @@ -87,7 +89,7 @@ fn main() -> ExitCode { let (number, prefix) = match convert(n) { Ok(x) => x, Err(err) => { - eprintln!("{}: {}.", argv[0], err); + eprintln!("{}: {}", argv[0], err); return ExitCode::from(EX_SOFTWARE as u8); }, }; @@ -98,7 +100,7 @@ fn main() -> ExitCode { stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) .unwrap_or_else(|e| { - eprintln!("{}: {}.", argv[0], e); + eprintln!("{}: {}", argv[0], e.strerror()); exit(EX_IOERR); }); } diff --git a/src/mm.c b/src/mm.c index ff62148..dc337b7 100644 --- a/src/mm.c +++ b/src/mm.c @@ -106,6 +106,15 @@ oserr(char *s, char *r){ } \ 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} */ @@ -178,12 +187,15 @@ int main(int argc, char *argv[]){ k = 1; break; default: - fprintf(stderr, "Usage: %s (-aenu) (-i [input])..." - " (-o [output])...\n", argv[0]); - retval = EX_USAGE; + 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; diff --git a/src/rpn.rs b/src/rpn.rs index 0cb23b8..2bfbbf5 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -172,7 +172,7 @@ fn eval( }; } else { return Err(EvaluationError { - message: format!("{}: Unexpected operation.", op), + message: format!("{}: Unexpected operation", op), code: EX_DATAERR, }) } diff --git a/src/scrut.c b/src/scrut.c index e2494d3..c5b675f 100644 --- a/src/scrut.c +++ b/src/scrut.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2023 DTB + * Copyright (c) 2023–2024 DTB + * Copyright (c) 2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under @@ -17,13 +18,15 @@ */ #include /* fprintf(3), stderr, NULL */ -#include /* EXIT_FAILURE */ +#include /* EXIT_FAILURE, EXIT_SUCCESS */ #include /* memset(3), strchr(3) */ +#ifndef EX_USAGE +# include +#endif #include /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */ #include /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR, * S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK, * S_ISUID, S_ISVTX */ -#include static char args[] = "bcdefghkprsuwxLS"; static char ops[(sizeof args) / (sizeof *args)]; @@ -57,7 +60,7 @@ int main(int argc, char *argv[]){ argv += optind; do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) - return 1; /* doesn't exist or isn't stattable */ + return EXIT_FAILURE; /* doesn't exist or isn't stattable */ for(i = 0; ops[i] != '\0'; ++i) if(ops[i] == 'e') @@ -97,8 +100,8 @@ usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n", && !S_ISLNK(buf.st_mode)) || (ops[i] == 'S' && !S_ISSOCK(buf.st_mode))) - return 1; + return EXIT_FAILURE; }while(*++argv != NULL); - return 0; + return EXIT_SUCCESS; } diff --git a/src/strerror.rs b/src/strerror.rs new file mode 100644 index 0000000..e306e7a --- /dev/null +++ b/src/strerror.rs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Emma Tebibyte + * 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. + */ + +use std::ffi::{ c_int, c_char, CStr }; + +pub trait StrError { fn strerror(&self) -> String; } + +impl StrError for std::io::Error { + /* wrapper function for use in Rust */ + fn strerror(&self) -> String { + /* Get the raw OS error. If it’s None, what the hell is going on‽ */ + let errno = self.raw_os_error().unwrap_or(0) as c_int; + + /* Get a CStr from the error message so that it’s referenced and then + * convert it to an owned value. If the string is not valid UTF-8, + * return that error instead. */ + match unsafe { CStr::from_ptr(strerror(errno)) }.to_str() { + Ok(s) => s.to_owned(), // yay!! :D + Err(e) => e.to_string(), // awww :( + } + } +} + +/* binding to strerror(3p) */ +extern "C" { fn strerror(errnum: c_int) -> *mut c_char; } diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..4a602b1 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,10 @@ +extern crate strerror; + +use strerror::raw_message; + +fn main() { + stdout.write_all(b"meow\n").unwrap_or_else(|e| { + eprintln!("{}", raw_message(e)); + std::process::exit(1); + }); +}