WIP: Add tar utility
This commit is contained in:
@@ -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<NodeRef, Error> {
|
||||
let target = self.imp.target(link)?;
|
||||
|
||||
@@ -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<NodeRef, Error> {
|
||||
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())?;
|
||||
|
||||
@@ -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<usize, Error> {
|
||||
let _ = node;
|
||||
|
||||
@@ -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));
|
||||
|
||||
Generated
+8
@@ -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",
|
||||
|
||||
@@ -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)'] }
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "stuff"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytemuck.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1 @@
|
||||
pub mod tar;
|
||||
@@ -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<const N: usize>([u8; N]);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct TarOctal<const N: usize>([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<const N: usize> Pod for TarString<N> {}
|
||||
unsafe impl<const N: usize> Zeroable for TarString<N> {}
|
||||
|
||||
unsafe impl<const N: usize> Pod for TarOctal<N> {}
|
||||
unsafe impl<const N: usize> Zeroable for TarOctal<N> {}
|
||||
|
||||
impl<const N: usize> TarOctal<N> {
|
||||
pub const fn zero() -> Self {
|
||||
let mut bytes = [b'0'; N];
|
||||
bytes[N - 1] = 0;
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub fn from_u64(value: u64) -> Option<Self> {
|
||||
// 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> {
|
||||
Self::from_u64(value as u64)
|
||||
}
|
||||
|
||||
pub fn as_usize(&self) -> Option<usize> {
|
||||
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<const N: usize> TarString<N> {
|
||||
pub const EMPTY: Self = Self([0; N]);
|
||||
|
||||
pub fn new(s: &str) -> Option<Self> {
|
||||
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<const N: usize> fmt::Display for TarString<N> {
|
||||
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'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use std::io::{self, Read, Seek};
|
||||
|
||||
use crate::tar::TarHeader;
|
||||
|
||||
pub struct TarReader<R: Read + Seek> {
|
||||
input: R,
|
||||
end_of_archive: bool,
|
||||
position: u64,
|
||||
}
|
||||
|
||||
pub struct TarReaderEntry<'a, R: Read + Seek> {
|
||||
reader: &'a mut TarReader<R>,
|
||||
position: u64,
|
||||
end: u64,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> TarReader<R> {
|
||||
pub fn new(input: R) -> Self {
|
||||
Self {
|
||||
input,
|
||||
position: 0,
|
||||
end_of_archive: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Result<Option<(TarHeader, TarReaderEntry<'_, R>)>, 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::<TarHeader>(&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<R: Read + Seek> Read for TarReaderEntry<'_, R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader, Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use crate::tar::{TarHeader, TarOctal, TarString};
|
||||
|
||||
pub struct TarWriter<W: Write> {
|
||||
output: W,
|
||||
}
|
||||
|
||||
pub struct TarFileWriter<'a, W: Write> {
|
||||
writer: &'a mut TarWriter<W>,
|
||||
written: u64,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
pub struct TarNewFileMetadata {
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub mode: u32,
|
||||
}
|
||||
|
||||
impl<W: Write> TarWriter<W> {
|
||||
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<TarFileWriter<'_, W>> {
|
||||
// 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<P: AsRef<Path>>(
|
||||
&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<W: Write> 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<W: Write> Write for TarFileWriter<'_, W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
|
||||
#[clap(short = 'c')]
|
||||
create: bool,
|
||||
#[clap(short = 't')]
|
||||
print: bool,
|
||||
#[clap(short = 'x')]
|
||||
extract: bool,
|
||||
|
||||
#[clap(short = 'f')]
|
||||
archive: PathBuf,
|
||||
|
||||
inputs: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[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<P: Into<PathBuf>>(self, p: P) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<T, E: ContextualizeError> ContextualizeError for Result<T, E> {
|
||||
type Output = Result<T, <E as ContextualizeError>::Output>;
|
||||
|
||||
fn contextualize<P: Into<PathBuf>>(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<P: Into<PathBuf>>(self, p: P) -> Self::Output {
|
||||
Error::Io(p.into(), self)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_archive(
|
||||
output: impl AsRef<Path>,
|
||||
inputs: &[PathBuf],
|
||||
chdir: Option<&PathBuf>,
|
||||
) -> Result<(), Error> {
|
||||
fn write_path<W: Write>(
|
||||
writer: &mut TarWriter<W>,
|
||||
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<Path>) -> 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<Path>, chdir: Option<&PathBuf>) -> Result<(), Error> {
|
||||
fn extract_file<R: Read + Seek>(
|
||||
entry: &mut TarReaderEntry<R>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<u32>) {
|
||||
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<u32>) {
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user