diff --git a/build.sh b/build.sh index edb50732..d17dc0b2 100755 --- a/build.sh +++ b/build.sh @@ -59,6 +59,7 @@ pack_initrd() { cp ${build_dir}/cat ${root_dir}/bin/ cp ${build_dir}/hexd ${root_dir}/bin/ cp ${build_dir}/colors ${root_dir}/bin/ + cp ${build_dir}/dd ${root_dir}/bin/ # red cp ${build_dir}/red ${root_dir}/bin/red diff --git a/sysutils/Cargo.toml b/sysutils/Cargo.toml index 64d5e973..65f12760 100644 --- a/sysutils/Cargo.toml +++ b/sysutils/Cargo.toml @@ -50,3 +50,7 @@ path = "src/hexd.rs" [[bin]] name = "colors" path = "src/colors.rs" + +[[bin]] +name = "dd" +path = "src/dd.rs" diff --git a/sysutils/src/dd.rs b/sysutils/src/dd.rs new file mode 100644 index 00000000..1dd481f7 --- /dev/null +++ b/sysutils/src/dd.rs @@ -0,0 +1,130 @@ +#![feature(yggdrasil_os)] +use std::{ + io::{self, Read, Seek, SeekFrom, Write}, + process::ExitCode, +}; + +use clap::Parser; +use sysutils::{Input, Output}; + +#[derive(Parser)] +struct Args { + #[arg(short)] + source: Option, + #[arg(short)] + destination: Option, + + #[arg(long)] + src_skip: Option, + #[arg(long, default_value_t = 512)] + src_bs: u64, + #[arg(short, long, default_value_t = usize::MAX)] + count: usize, + + // TODO: remove this when pipes are a thing + #[arg(short = 'x', long)] + as_hex: bool +} + +fn dump_block(offset: u64, data: &[u8]) { + const WINDOW_SIZE: usize = 16; + let window_count = (data.len() + WINDOW_SIZE) / WINDOW_SIZE; + + for iw in 0..window_count { + let off = iw * WINDOW_SIZE; + let len = core::cmp::min(data.len() - off, WINDOW_SIZE); + if len == 0 { + break; + } + + let window = &data[off..off + len]; + + print!("{:08X}: ", offset + off as u64); + for i in 0..WINDOW_SIZE { + if i < window.len() { + print!("{:02X}", window[i]); + } else { + print!(" "); + } + + if i % 2 == 1 { + print!(" "); + } + } + + for &ch in window { + if ch.is_ascii_graphic() || ch == b' ' { + print!("{}", ch as char); + } else { + print!("."); + } + } + + println!(); + } +} + +fn run( + mut input: I, + mut output: O, + src_position: u64, + src_block_size: u64, + mut count: usize, + as_hex: bool, +) -> io::Result<()> { + let mut block = vec![0; src_block_size as usize]; + let mut offset = 0; + + input.seek(SeekFrom::Start(src_position * src_block_size))?; + + while count != 0 { + let read_count = input.read(&mut block)?; + + if read_count == 0 { + break; + } + + if as_hex { + dump_block((src_position + offset) * src_block_size, &block[..read_count]); + } else { + output.write(&block[..read_count])?; + } + + count -= 1; + offset += 1; + } + + Ok(()) +} + +fn main() -> ExitCode { + let args = Args::parse(); + + let src_path = args.source.as_deref().unwrap_or("-"); + let dst_path = args.destination.as_deref().unwrap_or("-"); + + if src_path == dst_path && src_path != "-" { + eprintln!("Input and output cannot be the same: {}", src_path); + return ExitCode::FAILURE; + } + + if args.as_hex && dst_path != "-" { + eprintln!("--as-hex requires stdout output"); + return ExitCode::FAILURE; + } + + let input = Input::open_str(src_path).unwrap(); + let output = Output::open_str(dst_path).unwrap(); + + let src_position = args.src_skip.unwrap_or(0); + + match run(input, output, src_position, args.src_bs, args.count, args.as_hex) { + Ok(_) => { + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("Error: {}", e); + ExitCode::FAILURE + } + } +} diff --git a/sysutils/src/lib.rs b/sysutils/src/lib.rs index 28ffdaf8..071fa4f9 100644 --- a/sysutils/src/lib.rs +++ b/sysutils/src/lib.rs @@ -1,6 +1,6 @@ use std::{ fs::File, - io::{self, Read}, + io::{self, Read, Write, Seek, SeekFrom}, }; #[cfg(unix)] @@ -65,6 +65,11 @@ pub enum Input { File(io::BufReader), } +pub enum Output { + Stdout(io::Stdout), + File(io::BufWriter), +} + impl Input { pub fn open_str(arg: &str) -> io::Result { if arg == "-" { @@ -97,3 +102,61 @@ impl Read for Input { } } } + +impl Seek for Input { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match self { + Self::Stdin(_) => todo!(), + Self::File(value) => value.seek(pos), + } + } +} + +impl Output { + pub fn open_str(arg: &str) -> io::Result { + if arg == "-" { + Ok(Self::Stdout(io::stdout())) + } else { + let file = File::create(arg)?; + let writer = io::BufWriter::new(file); + Ok(Self::File(writer)) + } + } +} + +impl From for Output { + fn from(value: io::Stdout) -> Self { + Self::Stdout(value) + } +} + +impl From for Output { + fn from(value: File) -> Self { + Self::File(io::BufWriter::new(value)) + } +} + +impl Write for Output { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + Self::Stdout(value) => value.write(buf), + Self::File(value) => value.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + Self::Stdout(value) => value.flush(), + Self::File(value) => value.flush(), + } + } +} + +impl Seek for Output { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match self { + Self::Stdout(_) => todo!(), + Self::File(value) => value.seek(pos), + } + } +}