overgrown/src/mm.rs

186 lines
4.9 KiB
Rust
Raw Normal View History

2024-07-13 23:49:27 -06:00
/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
2024-07-13 23:49:27 -06:00
* 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 };
2024-07-14 18:41:18 -06:00
use ArgMode::*;
2024-07-14 18:39:55 -06:00
enum ArgMode { In, Out }
2024-07-13 23:49:27 -06:00
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
2024-07-13 23:49:27 -06:00
2024-07-14 00:31:23 -06:00
let mut a = false; /* append to the file */
2024-07-13 23:49:27 -06:00
let mut e = false; /* use stderr as an output */
2024-07-14 00:31:23 -06:00
let mut t = true; /* do not truncate the file before writing */
2024-07-13 23:49:27 -06:00
let mut u = false; /* unbuffer i/o */
2024-07-14 00:31:23 -06:00
let mut ins = Vec::new(); /* initial input file path vector */
let mut outs = Vec::new(); /* initial output file path vector */
2024-07-14 18:56:23 -06:00
let mut mode: Option<ArgMode> = None; /* mode set by last-used option */
let mut optind = 0;
2024-07-13 23:49:27 -06:00
while let Some(opt) = argv.getopt("aei:o:tu") {
2024-07-13 23:49:27 -06:00
match opt.opt() {
Ok("a") => a = true,
Ok("e") => e = true,
Ok("u") => u = true,
Ok("t") => t = false,
Ok("i") => { /* add inputs */
2024-07-13 23:49:27 -06:00
let input = opt.arg().unwrap();
ins.push(input);
2024-07-14 18:56:23 -06:00
mode = Some(In); /* latest argument == -i */
2024-07-13 23:49:27 -06:00
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
outs.push(output);
2024-07-14 18:56:23 -06:00
mode = Some(Out); /* latest argument == -o */
2024-07-13 23:49:27 -06:00
},
Err(_) | Ok(_) => {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
},
};
2024-07-14 18:56:23 -06:00
optind = opt.ind();
}
let remaining = argv.iter().skip(optind);
2024-07-14 18:56:23 -06:00
/* check the last flag specified */
if let Some(m) = mode {
for arg in remaining {
2024-07-14 18:56:23 -06:00
/* move the subsequent arguments to the list of inputs or outputs */
match m {
2024-07-14 18:41:18 -06:00
In => ins.push(arg.to_string()),
Out => outs.push(arg.to_string()),
};
}
2024-07-14 18:56:23 -06:00
} else {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
2024-07-13 23:49:27 -06:00
}
/* 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()); }
2024-07-13 23:49:27 -06:00
/* map all path strings to files */
let inputs = ins.iter().map(|file| {
/* if a file is “-”, it is stdin */
if *file == "-" {
2024-07-14 00:31:23 -06:00
/* portable way to access stdin as a file */
return unsafe { File::from_raw_fd(stdin().as_raw_fd()) };
2024-07-13 23:49:27 -06:00
}
2024-07-14 00:31:23 -06:00
match File::open(file) {
2024-07-13 23:49:27 -06:00
Ok(f) => f,
Err(e) => {
2024-07-14 00:31:23 -06:00
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
2024-07-13 23:49:27 -06:00
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 == "-" {
2024-07-14 00:31:23 -06:00
/* portable way to access stdout as a file */
return unsafe { File::from_raw_fd(stdout().as_raw_fd()) };
2024-07-13 23:49:27 -06:00
}
2024-07-14 00:31:23 -06:00
let options = File::options()
/* dont truncate if -t is specified, append if -a is specified */
2024-07-14 00:31:23 -06:00
.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 {
2024-07-13 23:49:27 -06:00
Ok(f) => return f,
Err(e) => {
2024-07-14 00:31:23 -06:00
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
2024-07-13 23:49:27 -06:00
exit(EX_IOERR);
},
};
}).collect::<Vec<_>>();
/* if -e is specified, use stderr */
if e {
2024-07-14 00:31:23 -06:00
/* portable way to access stderr as a file */
outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) });
2024-07-13 23:49:27 -06:00
}
let mut outputs = outputs.iter().map(|o| {
if u {
2024-07-14 00:31:23 -06:00
/* unbuffered writing through a buffer of capacity 0 */
2024-07-13 23:49:27 -06:00
BufWriter::with_capacity(0, o)
} else {
2024-07-14 00:31:23 -06:00
/* theoretically buffered writing */
2024-07-13 23:49:27 -06:00
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);
}
2024-07-13 23:49:27 -06:00
}
}
}
}
ExitCode::SUCCESS
}