/* * 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 }