From a87c8a7ee201939115cb34b66ed2ecfc3ee4d6de Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 16 Oct 2025 10:42:41 +0300 Subject: [PATCH] WIP: Add tar utility --- kernel/libk/src/vfs/node/mod.rs | 12 ++ kernel/libk/src/vfs/node/ops.rs | 3 + kernel/libk/src/vfs/node/traits.rs | 7 + kernel/libk/src/vfs/node/tree.rs | 9 +- userspace/Cargo.lock | 8 + userspace/Cargo.toml | 2 + userspace/arch/x86_64/inittab | 2 +- userspace/etc/test.tar | Bin 0 -> 10240 bytes userspace/lib/stuff/Cargo.toml | 10 ++ userspace/lib/stuff/src/lib.rs | 1 + userspace/lib/stuff/src/tar/mod.rs | 149 ++++++++++++++++ userspace/lib/stuff/src/tar/reader.rs | 80 +++++++++ userspace/lib/stuff/src/tar/writer.rs | 135 ++++++++++++++ userspace/sysutils/Cargo.toml | 5 + userspace/sysutils/src/tar.rs | 246 ++++++++++++++++++++++++++ userspace/sysutils/src/tst.rs | 48 +++-- userspace/tools/shell/src/readline.rs | 27 ++- xtask/src/build/userspace.rs | 1 + 18 files changed, 712 insertions(+), 33 deletions(-) create mode 100644 userspace/etc/test.tar create mode 100644 userspace/lib/stuff/Cargo.toml create mode 100644 userspace/lib/stuff/src/lib.rs create mode 100644 userspace/lib/stuff/src/tar/mod.rs create mode 100644 userspace/lib/stuff/src/tar/reader.rs create mode 100644 userspace/lib/stuff/src/tar/writer.rs create mode 100644 userspace/sysutils/src/tar.rs diff --git a/kernel/libk/src/vfs/node/mod.rs b/kernel/libk/src/vfs/node/mod.rs index af1e077f..c2927c65 100644 --- a/kernel/libk/src/vfs/node/mod.rs +++ b/kernel/libk/src/vfs/node/mod.rs @@ -503,6 +503,18 @@ impl Node { } } +impl DirectoryData { + fn contains(&self, node: &NodeRef, name: &Filename) -> bool { + { + let cache = self.children.lock(); + if cache.iter().any(|(n, _)| n == name) { + return true; + } + } + self.imp.contains(node, name) + } +} + impl HardlinkData { fn target(&self, link: &NodeRef) -> Result { let target = self.imp.target(link)?; diff --git a/kernel/libk/src/vfs/node/ops.rs b/kernel/libk/src/vfs/node/ops.rs index 208af5cf..a77a4197 100644 --- a/kernel/libk/src/vfs/node/ops.rs +++ b/kernel/libk/src/vfs/node/ops.rs @@ -110,6 +110,9 @@ impl Node { /// Creates an entry within a directory with given [CreateInfo]. pub fn create(self: &NodeRef, info: CreateInfo, check: AccessToken) -> Result { let directory = self.as_directory()?; + if directory.contains(self, info.name) { + return Err(Error::AlreadyExists); + } let node = directory.imp.create_node(self, &info)?; self.create_node(node.clone(), info.name, check.clone())?; diff --git a/kernel/libk/src/vfs/node/traits.rs b/kernel/libk/src/vfs/node/traits.rs index a8460791..392beb8b 100644 --- a/kernel/libk/src/vfs/node/traits.rs +++ b/kernel/libk/src/vfs/node/traits.rs @@ -149,6 +149,13 @@ pub trait DirectoryImpl: CommonImpl { Err(Error::NotImplemented) } + fn contains(&self, node: &NodeRef, name: &Filename) -> bool { + match self.lookup(node, name) { + Ok(_) => true, + Err(_) => false, + } + } + /// Returns the "length" of the directory in entries fn len(&self, node: &NodeRef) -> Result { let _ = node; diff --git a/kernel/libk/src/vfs/node/tree.rs b/kernel/libk/src/vfs/node/tree.rs index 40cf1318..a671a1f1 100644 --- a/kernel/libk/src/vfs/node/tree.rs +++ b/kernel/libk/src/vfs/node/tree.rs @@ -32,12 +32,9 @@ impl Node { let directory = self.as_directory()?; let mut children = directory.children.lock(); - // TODO check if an entry already exists with such name - - // if children.contains_key(&name) { - // log::warn!("Directory cache already contains an entry: {:?}", name); - // return Err(Error::AlreadyExists); - // } + if children.iter().any(|(n, _)| name == *n) { + return Err(Error::AlreadyExists); + } assert!(child.parent.replace(Some(self.clone())).is_none()); children.push((name, child)); diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 05b38b88..e59a61f4 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -2979,6 +2979,13 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "stuff" +version = "0.1.0" +dependencies = [ + "bytemuck", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3039,6 +3046,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "stuff", "thiserror 1.0.69", "tui", "yggdrasil-abi", diff --git a/userspace/Cargo.toml b/userspace/Cargo.toml index aa06b2e8..d69cc63d 100644 --- a/userspace/Cargo.toml +++ b/userspace/Cargo.toml @@ -12,6 +12,7 @@ members = [ "lib/libterm", "lib/logsink", "lib/runtime", + "lib/stuff", "lib/uipc", "lib/yasync", "netutils", @@ -84,6 +85,7 @@ logsink.path = "lib/logsink" libutil.path = "../lib/libutil" cryptic.path = "lib/cryptic" hclient.path = "lib/hclient" +stuff.path = "lib/stuff" [workspace.lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] } diff --git a/userspace/arch/x86_64/inittab b/userspace/arch/x86_64/inittab index d389a091..9c848816 100644 --- a/userspace/arch/x86_64/inittab +++ b/userspace/arch/x86_64/inittab @@ -1,4 +1,4 @@ init:1:wait:/sbin/rc default logd:1:once:/sbin/logd -# user:1:once:/sbin/login /dev/ttyS0 +user:1:once:/sbin/login /dev/ttyS0 diff --git a/userspace/etc/test.tar b/userspace/etc/test.tar new file mode 100644 index 0000000000000000000000000000000000000000..3bf190d9327037e2d362af7a92737c89cb94b90a GIT binary patch literal 10240 zcmeHK|8AQw5a#bbMZ~|&RX_e)E0MD!AM)!e5rA0Y2jODti7TRUF4j5U--_Hi_bN zy$+&y6=!jC>7K^HGP76&OsYLQGwav%r$_kEnx9vTMMyCa7Wj0*5H3lm2NF>{ zxOY|x5)8VrjnvFKsg&Ce5W$w)XFVK*ll!n{mVsn@Jx(8T=n4*fvW|+|1TSjFPtByr zQoKOqMNV1~X9Zr=niX-97x)K-=J?@PYJb5OH(JMbz^v{>1^1%1G&}rYnwN7HdM%XY z=20DHsWWXjv!mhD?60>uTV4;=h{^(!qE$;!T;sjzfthZbqYZ&oi-DDrgW>fLLabO= zGxBLXzT?$sNlRTmihHKeIqaHQ-tH&tN^NFT>%C&+yAp@H^p=#c9yeSJO9N-&h4C^>-IIFqAei|kR^qJ;E15dKh;06IHYd3(CLnGN% zlSwG+8&Y2Vy86&z?wvb7F z2%V+&%2?3lcV5{6knv^7sykZ3iT!8$PFSdUIkwQ&B@C4jy`}6mX9!9Vw7hsEz&u6S zE|mSW@IH53ba%dgXS+Q6_RjQA=l%2N|13J<|1?`Cp8wx}^@HGm|6MRR4w#7@MesoB zZ_5aDV62E5h!)8Fe8q4SfOxplZWc3IAeW`Y}8L9s!SlN5CWC l5%36j1Uv#B0gr%3z$4%h@CbMWJOUm8kAO$OBk(r}{08T@cMSjl literal 0 HcmV?d00001 diff --git a/userspace/lib/stuff/Cargo.toml b/userspace/lib/stuff/Cargo.toml new file mode 100644 index 00000000..0c10d932 --- /dev/null +++ b/userspace/lib/stuff/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "stuff" +version = "0.1.0" +edition = "2021" + +[dependencies] +bytemuck.workspace = true + +[lints] +workspace = true diff --git a/userspace/lib/stuff/src/lib.rs b/userspace/lib/stuff/src/lib.rs new file mode 100644 index 00000000..a183a1cb --- /dev/null +++ b/userspace/lib/stuff/src/lib.rs @@ -0,0 +1 @@ +pub mod tar; diff --git a/userspace/lib/stuff/src/tar/mod.rs b/userspace/lib/stuff/src/tar/mod.rs new file mode 100644 index 00000000..a00e20ca --- /dev/null +++ b/userspace/lib/stuff/src/tar/mod.rs @@ -0,0 +1,149 @@ +use std::fmt; + +use bytemuck::{Pod, Zeroable}; + +mod reader; +mod writer; + +pub use reader::{TarReader, TarReaderEntry}; +pub use writer::{TarFileWriter, TarNewFileMetadata, TarWriter}; + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct TarString([u8; N]); + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct TarOctal([u8; N]); + +#[derive(Zeroable, Pod, Clone, Copy)] +#[repr(C)] +pub struct TarHeader { + pub name: TarString<100>, + pub mode: TarOctal<8>, + pub uid: TarOctal<8>, + pub gid: TarOctal<8>, + pub size: TarOctal<12>, + pub mtime: TarOctal<12>, + pub checksum: TarOctal<8>, + pub file_type: u8, + pub link: TarString<100>, + pub ustar_magic: [u8; 6], + pub ustar_version: [u8; 2], + pub uid_name: TarString<32>, + pub gid_name: TarString<32>, + pub dev_major: TarOctal<8>, + pub dev_minor: TarOctal<8>, + pub filename_prefix: TarString<155>, + _0: [u8; 12], +} + +unsafe impl Pod for TarString {} +unsafe impl Zeroable for TarString {} + +unsafe impl Pod for TarOctal {} +unsafe impl Zeroable for TarOctal {} + +impl TarOctal { + pub const fn zero() -> Self { + let mut bytes = [b'0'; N]; + bytes[N - 1] = 0; + Self(bytes) + } + + pub fn from_u64(value: u64) -> Option { + // Limit check + if (N - 1) * 3 < 64 && value >= (1 << ((N - 1) * 3)) { + return None; + } + + let mut bytes = [0; N]; + let mut j = N - 2; + for i in 0..N - 1 { + let v = ((value >> (j * 3)) & 0x7) as u8 + b'0'; + bytes[i] = v; + j -= 1; + } + + Some(Self(bytes)) + } + + pub fn from_u32(value: u32) -> Option { + Self::from_u64(value as u64) + } + + pub fn as_usize(&self) -> Option { + let mut value = 0; + + for i in 0..N { + let ch = self.0[i]; + if ch == b'\0' || ch == b' ' { + break; + } + + if ch < b'0' || ch > b'7' { + return None; + } + + value <<= 3; + value |= (ch - b'0') as usize; + } + + Some(value) + } +} + +impl TarString { + pub const EMPTY: Self = Self([0; N]); + + pub fn new(s: &str) -> Option { + if s.len() < N { + let mut bytes = [0; N]; + bytes[..s.len()].copy_from_slice(s.as_bytes()); + Some(Self(bytes)) + } else { + None + } + } + + pub fn is_empty(&self) -> bool { + self.0[0] == b'\0' + } + + pub fn len(&self) -> usize { + self.0.iter().position(|&p| p == b'\0').unwrap_or(N) + } + + pub fn as_str(&self) -> Option<&str> { + core::str::from_utf8(&self.0[..self.len()]).ok() + } +} + +impl fmt::Display for TarString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; + + for ch in self.0 { + if ch == b'\0' { + break; + } + f.write_char(ch as char)?; + } + + Ok(()) + } +} + +impl TarHeader { + pub fn is_empty(&self) -> bool { + self.name.is_empty() + } + + pub fn is_dir(&self) -> bool { + self.file_type == b'5' + } + + pub fn is_file(&self) -> bool { + self.file_type == b'\0' || self.file_type == b'0' + } +} diff --git a/userspace/lib/stuff/src/tar/reader.rs b/userspace/lib/stuff/src/tar/reader.rs new file mode 100644 index 00000000..20f86381 --- /dev/null +++ b/userspace/lib/stuff/src/tar/reader.rs @@ -0,0 +1,80 @@ +use std::io::{self, Read, Seek}; + +use crate::tar::TarHeader; + +pub struct TarReader { + input: R, + end_of_archive: bool, + position: u64, +} + +pub struct TarReaderEntry<'a, R: Read + Seek> { + reader: &'a mut TarReader, + position: u64, + end: u64, +} + +impl TarReader { + pub fn new(input: R) -> Self { + Self { + input, + position: 0, + end_of_archive: false, + } + } + + pub fn next(&mut self) -> Result)>, io::Error> { + let mut have_empty = false; + + while !self.end_of_archive { + self.input.seek(io::SeekFrom::Start(self.position))?; + + let mut bytes = [0; 512]; + self.input.read_exact(&mut bytes)?; + let header = bytemuck::from_bytes::(&bytes); + + if header.is_empty() { + if have_empty { + self.end_of_archive = true; + break; + } else { + have_empty = true; + continue; + } + } + + let entry_size = header.size.as_usize().unwrap(); + let skip = (entry_size + 1023) & !511; + + let position = self.position + 512; + let end = self.position + 512 + entry_size as u64; + + self.position += skip as u64; + + return Ok(Some(( + *header, + TarReaderEntry { + reader: self, + position, + end, + }, + ))); + } + + Ok(None) + } +} + +impl Read for TarReaderEntry<'_, R> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let amount = buf.len().min((self.end - self.position) as usize); + if amount != 0 { + self.reader.input.seek(io::SeekFrom::Start(self.position))?; + let result = self.reader.input.read(&mut buf[..amount])?; + self.position += result as u64; + Ok(result) + } else { + Ok(0) + } + } +} diff --git a/userspace/lib/stuff/src/tar/writer.rs b/userspace/lib/stuff/src/tar/writer.rs new file mode 100644 index 00000000..8e919cf8 --- /dev/null +++ b/userspace/lib/stuff/src/tar/writer.rs @@ -0,0 +1,135 @@ +use std::{ + fs::File, + io::{self, BufReader, Read, Write}, + path::Path, +}; + +use crate::tar::{TarHeader, TarOctal, TarString}; + +pub struct TarWriter { + output: W, +} + +pub struct TarFileWriter<'a, W: Write> { + writer: &'a mut TarWriter, + written: u64, + size: u64, +} + +pub struct TarNewFileMetadata { + pub uid: u32, + pub gid: u32, + pub mode: u32, +} + +impl TarWriter { + pub fn new(output: W) -> Self { + Self { output } + } + + pub fn finish(&mut self) -> io::Result<()> { + // Two empty blocks + let empty = [0; 512]; + self.output.write_all(&empty)?; + self.output.write_all(&empty)?; + self.output.flush() + } + + pub fn begin_file( + &mut self, + path: &str, + size: u64, + metadata: TarNewFileMetadata, + ) -> io::Result> { + // TODO error reporting + // TODO long filenames + let header = TarHeader { + name: TarString::new(path).unwrap(), + mode: TarOctal::from_u32(metadata.mode).unwrap(), + uid: TarOctal::from_u32(metadata.uid).unwrap(), + gid: TarOctal::from_u32(metadata.gid).unwrap(), + size: TarOctal::from_u64(size).unwrap(), + mtime: TarOctal::zero(), + checksum: TarOctal::zero(), + file_type: b'0', + link: TarString::EMPTY, + ustar_magic: *b"ustar\0", + ustar_version: *b"00", + uid_name: TarString::EMPTY, + gid_name: TarString::EMPTY, + dev_major: TarOctal::zero(), + dev_minor: TarOctal::zero(), + filename_prefix: TarString::EMPTY, + _0: [0; 12], + }; + + self.output.write_all(bytemuck::bytes_of(&header))?; + Ok(TarFileWriter { + writer: self, + written: 0, + size, + }) + } + + pub fn append_file>( + &mut self, + path: P, + path_in_archive: &str, + ) -> io::Result<()> { + let file = File::open(path)?; + let metadata = file.metadata()?; + let mut reader = BufReader::new(file); + // TODO + let tar_metadata = TarNewFileMetadata { + uid: 0, + gid: 0, + mode: 0o644, + }; + let mut writer = self.begin_file(path_in_archive, metadata.len(), tar_metadata)?; + + let mut buffer = [0; 8192]; + loop { + let len = reader.read(&mut buffer)?; + if len == 0 { + break; + } + writer.write_all(&buffer[..len])?; + } + writer.finish()?; + + Ok(()) + } +} + +impl TarFileWriter<'_, W> { + pub fn finish(mut self) -> io::Result<()> { + let aligned_size = (self.size + 511) & !511; + if self.written < aligned_size { + let zeroes = aligned_size - self.written; + // TODO suboptimal, but don't want Seek on writer? + for _ in 0..zeroes { + self.writer.output.write_all(&[0])?; + } + self.writer.output.flush()?; + self.written = self.size; + } + Ok(()) + } +} + +impl Write for TarFileWriter<'_, W> { + fn write(&mut self, buf: &[u8]) -> io::Result { + let can_write = (self.size - self.written).min(buf.len() as u64); + if can_write != 0 { + let len = self.writer.output.write(&buf[..can_write as usize])?; + self.written += len as u64; + Ok(len) + } else { + Ok(0) + } + } + + fn flush(&mut self) -> io::Result<()> { + self.writer.output.flush() + } +} diff --git a/userspace/sysutils/Cargo.toml b/userspace/sysutils/Cargo.toml index a4c627b8..5f4cfb28 100644 --- a/userspace/sysutils/Cargo.toml +++ b/userspace/sysutils/Cargo.toml @@ -21,6 +21,7 @@ serde_json.workspace = true sha2.workspace = true chrono.workspace = true tui.workspace = true +stuff.workspace = true # Own regex implementation? regex = "1.11.1" @@ -77,6 +78,10 @@ path = "src/kmod.rs" name = "echo" path = "src/echo.rs" +[[bin]] +name = "tar" +path = "src/tar.rs" + [[bin]] name = "ls" path = "src/ls.rs" diff --git a/userspace/sysutils/src/tar.rs b/userspace/sysutils/src/tar.rs new file mode 100644 index 00000000..f2ee9303 --- /dev/null +++ b/userspace/sysutils/src/tar.rs @@ -0,0 +1,246 @@ +use std::{ + fmt, + fs::{self, File}, + io::{self, stdout, BufWriter, Read, Seek, Write}, + path::{Path, PathBuf}, + process::ExitCode, +}; + +use clap::Parser; +use stuff::tar::{TarHeader, TarReader, TarReaderEntry, TarWriter}; + +#[derive(Debug, Parser)] +struct Args { + #[clap(short = 'C')] + chdir: Option, + + #[clap(short = 'c')] + create: bool, + #[clap(short = 't')] + print: bool, + #[clap(short = 'x')] + extract: bool, + + #[clap(short = 'f')] + archive: PathBuf, + + inputs: Vec, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Action { + Create, + Print, + Extract, +} + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("{0}: {1}")] + Io(PathBuf, io::Error), + #[error("No action specified, use -c/-t/-x")] + NoAction, + #[error("Multiple actions specified, use only -c/-t/-x")] + MultipleActions, +} + +trait ContextualizeError { + type Output; + + fn contextualize>(self, p: P) -> Self::Output; +} + +impl ContextualizeError for Result { + type Output = Result::Output>; + + fn contextualize>(self, p: P) -> Self::Output { + match self { + Ok(val) => Ok(val), + Err(error) => Err(error.contextualize(p)), + } + } +} + +impl ContextualizeError for io::Error { + type Output = Error; + + fn contextualize>(self, p: P) -> Self::Output { + Error::Io(p.into(), self) + } +} + +fn create_archive( + output: impl AsRef, + inputs: &[PathBuf], + chdir: Option<&PathBuf>, +) -> Result<(), Error> { + fn write_path( + writer: &mut TarWriter, + fs_path: &Path, + archive_path: &Path, + ) -> Result<(), Error> { + if fs_path.is_file() { + println!("{}", archive_path.display()); + let archive_path_str = archive_path.to_str().unwrap(); + writer + .append_file(fs_path, archive_path_str) + .contextualize(fs_path)?; + Ok(()) + } else if fs_path.is_dir() { + let mut dir = fs::read_dir(fs_path).contextualize(fs_path)?; + for entry in dir { + let entry = entry.contextualize(fs_path)?; + let filename = entry.file_name(); + if filename.eq_ignore_ascii_case(".") || filename.eq_ignore_ascii_case("..") { + continue; + } + write_path( + writer, + &fs_path.join(&filename), + &archive_path.join(&filename), + )?; + } + Ok(()) + } else { + todo!() + } + } + + if inputs.is_empty() { + todo!(); + } + + let archive = output.as_ref(); + let mut writer = File::create(archive) + .map(BufWriter::new) + .map(TarWriter::new) + .contextualize(archive)?; + + let chdir = match chdir { + Some(chdir) => chdir.as_path(), + None => ".".as_ref(), + }; + + for input in inputs { + if input.is_absolute() || input.starts_with("/") { + // fs path == archive path + write_path(&mut writer, input, input)?; + } else { + todo!(); + // write_path(&mut writer, chdir, chdir.join(input).as_path())?; + } + } + + writer.finish().contextualize(archive)?; + + Ok(()) +} + +fn print_archive(archive: impl AsRef) -> Result<(), Error> { + let archive = archive.as_ref(); + let input = File::open(archive).contextualize(archive)?; + let mut reader = TarReader::new(input); + + loop { + let Some((header, _)) = reader.next().contextualize(archive)? else { + break; + }; + + println!("{}", header.name); + } + + Ok(()) +} + +fn extract_archive(archive: impl AsRef, chdir: Option<&PathBuf>) -> Result<(), Error> { + fn extract_file( + entry: &mut TarReaderEntry, + header: &TarHeader, + chdir: Option<&PathBuf>, + ) -> Result<(), Error> { + // TODO path sanitization + let Some(raw_path) = header.name.as_str() else { + eprintln!("Skipping invalid path {}", header.name); + return Ok(()); + }; + let sanitized_path = raw_path.trim_start_matches('/'); + let sanitized_path = match chdir { + Some(chdir) => chdir.join(sanitized_path), + None => sanitized_path.into(), + }; + + println!("{}", sanitized_path.display()); + + if header.is_file() { + if let Some(parent) = sanitized_path.parent() { + std::fs::create_dir_all(parent).contextualize(parent)?; + } + + // Write the file + let mut output = File::create(&sanitized_path) + .map(BufWriter::new) + .contextualize(&sanitized_path)?; + + let mut buffer = [0; 4096]; + loop { + let len = entry.read(&mut buffer).contextualize(raw_path)?; + if len == 0 { + break; + } + + output + .write_all(&buffer[..len]) + .contextualize(&sanitized_path)?; + } + + Ok(()) + } else if header.is_dir() { + std::fs::create_dir_all(&sanitized_path).contextualize(sanitized_path) + } else { + Ok(()) + } + } + + let archive = archive.as_ref(); + let input = File::open(archive).contextualize(archive)?; + let mut reader = TarReader::new(input); + + loop { + let Some((header, mut entry)) = reader.next().contextualize(archive)? else { + break; + }; + + extract_file(&mut entry, &header, chdir) + .inspect_err(|e| eprintln!("{e}")) + .ok(); + } + + Ok(()) +} + +fn run(args: &Args) -> Result<(), Error> { + let action = match (args.create, args.print, args.extract) { + (false, false, false) => return Err(Error::NoAction), + (true, false, false) => Action::Create, + (false, true, false) => Action::Print, + (false, false, true) => Action::Extract, + (_, _, _) => return Err(Error::MultipleActions), + }; + + match action { + Action::Create => create_archive(&args.archive, &args.inputs, args.chdir.as_ref()), + Action::Print => print_archive(&args.archive), + Action::Extract => extract_archive(&args.archive, args.chdir.as_ref()), + } +} + +fn main() -> ExitCode { + let args = Args::parse(); + match run(&args) { + Ok(()) => ExitCode::SUCCESS, + Err(error) => { + eprintln!("{error}"); + ExitCode::FAILURE + } + } +} diff --git a/userspace/sysutils/src/tst.rs b/userspace/sysutils/src/tst.rs index ff7fecb4..c114f3d8 100644 --- a/userspace/sysutils/src/tst.rs +++ b/userspace/sysutils/src/tst.rs @@ -1,30 +1,28 @@ -use std::{collections::VecDeque, fmt::Write}; +use std::{sync::mpsc, time::Duration}; -use libterm::{Clear, Term, TermKey}; +fn producer(tx: mpsc::Sender) { + println!("Producer started"); + for i in 0..10 { + std::thread::sleep(Duration::from_secs(1)); + tx.send(i).ok(); + } + println!("Producer finished"); +} + +fn consumer(rx: mpsc::Receiver) { + println!("Consumer started"); + while let Ok(msg) = rx.recv() { + println!("Consumer: {msg}"); + } + println!("Consumer finished"); +} fn main() { - let mut term = Term::open().expect("open term"); + let (tx, rx) = mpsc::channel(); + let jh0 = std::thread::spawn(move || producer(tx)); + let jh1 = std::thread::spawn(move || consumer(rx)); - let mut keys = VecDeque::new(); - - loop { - term.clear(Clear::All).ok(); - for (i, key) in keys.iter().enumerate() { - term.set_cursor_position(i, 0).ok(); - write!(term, "{key:?}").ok(); - } - term.flush().ok(); - - let key = term.read_key().unwrap(); - - if key == TermKey::Eof { - break; - } - - keys.push_front(key); - - if keys.len() >= 20 { - keys.pop_back(); - } - } + jh0.join().unwrap(); + jh1.join().unwrap(); + println!("Finished"); } diff --git a/userspace/tools/shell/src/readline.rs b/userspace/tools/shell/src/readline.rs index e797e878..ce863a6f 100644 --- a/userspace/tools/shell/src/readline.rs +++ b/userspace/tools/shell/src/readline.rs @@ -10,6 +10,24 @@ enum Outcome { Data(usize), } +struct Readline<'a> { + stdin: &'a mut TerminalInput, + stdout: &'a mut Stdout, + buffer: &'a mut String, +} + +impl<'a> Readline<'a> { + fn new(stdin: &'a mut TerminalInput, stdout: &'a mut Stdout, buffer: &'a mut String) -> Self { + Self { + stdin, + stdout, + buffer, + } + } + + fn handle_char(&mut self, ch: char) {} +} + fn readline_inner( stdin: &mut TerminalInput, stdout: &mut Stdout, @@ -50,7 +68,14 @@ fn readline_inner( } // Tab '\t' => { - log::info!("Tab"); + // TODO parse the buffer into a command struct and do ops on the command + // or at least check the token/word before current to see if it's a + // separator like ';', '&&' etc + let (is_command, last_word) = match buffer.rsplit_once(' ') { + Some((_, last)) => (true, last), + None => (false, buffer.trim()), + }; + log::info!("Tab {last_word:?}"); } ch if ch.is_whitespace() || ch.is_ascii_graphic() => { let bytes = ch.encode_utf8(&mut ch_buffer); diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index 47b1cb63..216d20a5 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -30,6 +30,7 @@ const PROGRAMS: &[(&str, &str)] = &[ ("shell", "bin/sh"), // sysutils ("cat", "bin/cat"), + ("tar", "bin/tar"), ("chmod", "bin/chmod"), ("chroot", "sbin/chroot"), ("date", "bin/date"),