2024-07-13 23:49:27 -06:00
|
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
|
2024-07-14 18:34:08 -06:00
|
|
|
|
* 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,
|
2024-08-22 00:22:05 -06:00
|
|
|
|
io::{ Error, BufWriter, Read, Write, stderr, stdin, stdout },
|
2024-07-13 23:49:27 -06:00
|
|
|
|
os::fd::{ AsRawFd, FromRawFd },
|
2024-08-22 00:22:05 -06:00
|
|
|
|
process::{ ExitCode, exit},
|
2024-07-13 23:49:27 -06:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
extern crate getopt;
|
|
|
|
|
extern crate strerror;
|
|
|
|
|
extern crate sysexits;
|
|
|
|
|
|
|
|
|
|
use getopt::GetOpt;
|
|
|
|
|
use strerror::StrError;
|
|
|
|
|
use sysexits::{ EX_IOERR, EX_USAGE };
|
|
|
|
|
|
2024-08-10 13:29:27 -06:00
|
|
|
|
#[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,
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-14 18:41:18 -06:00
|
|
|
|
use ArgMode::*;
|
|
|
|
|
|
2024-07-14 18:39:55 -06:00
|
|
|
|
enum ArgMode { In, Out }
|
2024-07-14 18:34:08 -06:00
|
|
|
|
|
2024-08-24 15:53:58 -06:00
|
|
|
|
fn err(argv0: &String, e: Error, code: Option<u8>) -> ExitCode {
|
2024-08-22 00:22:05 -06:00
|
|
|
|
eprintln!("{}: {}", argv0, e.strerror());
|
2024-08-24 15:53:58 -06:00
|
|
|
|
ExitCode::from(code.unwrap_or(1 /* unknown error */))
|
2024-08-22 00:22:05 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn usage(argv0: &String) -> ExitCode {
|
|
|
|
|
eprintln!("Usage: {} [-aetu] [-i input] [-o output]", argv0);
|
2024-08-24 15:53:58 -06:00
|
|
|
|
ExitCode::from(EX_USAGE)
|
2024-08-22 00:22:05 -06:00
|
|
|
|
}
|
|
|
|
|
|
2024-07-13 23:49:27 -06:00
|
|
|
|
fn main() -> ExitCode {
|
|
|
|
|
let argv = args().collect::<Vec<_>>();
|
|
|
|
|
|
2024-08-17 01:51:25 -06:00
|
|
|
|
#[cfg(target_os="openbsd")] {
|
2024-08-17 14:58:56 -06:00
|
|
|
|
let promises = Promises::new("cpath rpath stdio unveil wpath");
|
2024-08-10 13:29:27 -06:00
|
|
|
|
if let Err(e) = pledge(Some(promises), None) {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
return err(&argv[0], e, Some(EX_OSERR));
|
2024-08-10 13:29: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 */
|
2024-07-14 18:34:08 -06:00
|
|
|
|
let mut optind = 0;
|
2024-07-13 23:49:27 -06:00
|
|
|
|
|
2024-07-13 23:56:30 -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,
|
2024-07-13 23:56:30 -06:00
|
|
|
|
Ok("e") => e = true,
|
|
|
|
|
Ok("u") => u = true,
|
|
|
|
|
Ok("t") => t = false,
|
2024-07-14 18:34:08 -06:00
|
|
|
|
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 */
|
2024-08-21 22:43:29 -06:00
|
|
|
|
let output = opt.arg().unwrap();
|
2024-07-13 23:49:27 -06:00
|
|
|
|
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(_) => {
|
2024-08-22 00:22:05 -06:00
|
|
|
|
return usage(&argv[0]);
|
2024-07-13 23:49:27 -06:00
|
|
|
|
},
|
|
|
|
|
};
|
2024-07-14 18:56:23 -06:00
|
|
|
|
|
2024-07-14 18:34:08 -06:00
|
|
|
|
optind = opt.ind();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let remaining = argv.iter().skip(optind);
|
|
|
|
|
|
2024-07-14 18:56:23 -06:00
|
|
|
|
/* check the last flag specified */
|
2024-07-14 18:34:08 -06:00
|
|
|
|
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 */
|
2024-07-14 18:34:08 -06:00
|
|
|
|
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:34:08 -06:00
|
|
|
|
};
|
|
|
|
|
}
|
2024-07-13 23:49:27 -06:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-17 01:51:25 -06:00
|
|
|
|
#[cfg(target_os="openbsd")] {
|
2024-08-16 18:43:51 -06:00
|
|
|
|
for input in &ins {
|
|
|
|
|
let perms = UnveilPerms::new(vec!['r']);
|
|
|
|
|
|
|
|
|
|
if let Err(e) = unveil(Some(&input), Some(perms)) {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
return err(&argv[0], e, Some(EX_OSERR));
|
2024-08-16 18:43:51 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for output in &outs {
|
|
|
|
|
let perms = UnveilPerms::new(vec!['c', 'w']);
|
|
|
|
|
|
|
|
|
|
if let Err(e) = unveil(Some(&output), Some(perms)) {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
return err(&argv[0], e, Some(EX_OSERR));
|
2024-08-16 18:43:51 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-10 13:29:27 -06:00
|
|
|
|
if let Err(e) = unveil(None, None) {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
return err(&argv[0], e, Some(EX_OSERR));
|
2024-08-10 13:29:27 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-10 14:20:38 -06:00
|
|
|
|
if ins.is_empty() && outs.is_empty() && argv.len() > optind {
|
2024-08-22 00:22:05 -06:00
|
|
|
|
return usage(&argv[0]);
|
2024-08-10 14:20:38 -06:00
|
|
|
|
}
|
|
|
|
|
|
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 */
|
2024-07-14 14:15:55 -06:00
|
|
|
|
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-08-24 15:53:58 -06:00
|
|
|
|
let _ = err(&(argv[0].clone() + ": " + file), e, None);
|
|
|
|
|
exit(EX_IOERR.into());
|
2024-07-13 23:49:27 -06:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}).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()
|
2024-07-14 00:33:37 -06:00
|
|
|
|
/* don’t 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-08-24 15:53:58 -06:00
|
|
|
|
let _ = err(&(argv[0].clone() + ": " + file), e, None);
|
|
|
|
|
exit(EX_IOERR.into());
|
2024-07-13 23:49:27 -06:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}).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| {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
let _ = err(&argv[0], e, None);
|
|
|
|
|
exit(EX_IOERR.into());
|
2024-07-13 23:49:27 -06:00
|
|
|
|
})
|
|
|
|
|
}) {
|
|
|
|
|
for out in &mut outputs {
|
|
|
|
|
if let Err(e) = out.write(&[byte]) {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
return err(&argv[0], e, Some(EX_IOERR));
|
2024-07-13 23:49:27 -06:00
|
|
|
|
}
|
2024-07-14 14:15:55 -06:00
|
|
|
|
|
|
|
|
|
if u {
|
|
|
|
|
/* immediately flush the output for -u */
|
|
|
|
|
if let Err(e) = out.flush() {
|
2024-08-24 15:53:58 -06:00
|
|
|
|
return err(&argv[0], e, Some(EX_IOERR));
|
2024-07-14 14:15:55 -06:00
|
|
|
|
}
|
2024-07-13 23:49:27 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExitCode::SUCCESS
|
|
|
|
|
}
|