diff --git a/Makefile b/Makefile index 5e2c70d..a523591 100644 --- a/Makefile +++ b/Makefile @@ -114,8 +114,8 @@ build/bin/intcmp: src/intcmp.c build .PHONY: mm mm: build/bin/mm -build/bin/mm: src/mm.c build - $(CC) $(CFLAGS) -o $@ src/mm.c +build/bin/mm: src/mm.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/mm.rs .PHONY: npc npc: build/bin/npc diff --git a/src/mm.c b/src/mm.c deleted file mode 100644 index e905b35..0000000 --- a/src/mm.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2024 DTB - * 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 */ -#include /* signal(2), SIG_ERR, SIG_IGN, SIGINT */ -#include /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3), - * setvbuf(3), size_t, _IONBF, NULL */ -#include /* free(3), realloc(3) */ -#include /* strcmp(3), strerror(3) */ -#include /* getopt(3) */ -#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \ - || !defined EX_USAGE -# include -#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 = ""; -static char *stdin_name = ""; -static char *stdout_name = ""; -static char *stderr_name = ""; -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; -} diff --git a/src/mm.rs b/src/mm.rs new file mode 100644 index 0000000..aa7ce19 --- /dev/null +++ b/src/mm.rs @@ -0,0 +1,137 @@ +/* + * 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 + * 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 }; + +fn main() -> ExitCode { + let argv = args().collect::>(); + let usage = format!("Usage: {} [-aeu] [-i input] [-o output]", argv[0]); + + let mut a = false; /* append rather than update */ + let mut e = false; /* use stderr as an output */ + let mut u = false; /* unbuffer i/o */ + let mut ins = Vec::new(); /* initial inputs vector */ + let mut outs = Vec::new(); /* initial outputs vector */ + + while let Some(opt) = argv.getopt("aei:o:u") { + match opt.opt() { + Ok("a") => a = true, + Ok("e") => e = true, + Ok("u") => u = true, + Ok("i") => { /* add input */ + let input = opt.arg().unwrap(); + ins.push(input); + }, + Ok("o") => { /* add output */ + let output = opt.arg().unwrap(); + outs.push(output); + }, + Err(_) | Ok(_) => { + eprintln!("{}", 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() { 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 == "-" { + return unsafe { File::from_raw_fd(stdin().as_raw_fd()) }; /* fd0 = stdin */ + } + + match File::options().append(a).open(file) { + Ok(f) => f, + Err(e) => { + eprintln!("{}: {}", argv[0], e.strerror()); + exit(EX_IOERR); + }, + } + }).collect::>(); + + /* map all path strings to files */ + let mut outputs = outs.iter().map(|file| { + /* of a file is “-”, it is stdout */ + if *file == "-" { + return unsafe { File::from_raw_fd(stdout().as_raw_fd()) }; /* fd1 = stdout */ + } + + match File::options().append(a).open(file) { + Ok(f) => return f, + Err(e) => { + eprintln!("{}: {}", argv[0], e.strerror()); + exit(EX_IOERR); + }, + }; + }).collect::>(); + + /* if -e is specified, use stderr */ + if e { + outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) }); /* fd2 = stderr */ + } + + let mut outputs = outputs.iter().map(|o| { + if u { + BufWriter::with_capacity(0, o) + } else { + BufWriter::new(o) + } + }).collect::>(); + + 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 let Err(e) = out.flush() { + eprintln!("{}: {}", argv[0], e.strerror()); + return ExitCode::from(EX_IOERR as u8); + } + } + } + } + + ExitCode::SUCCESS +}