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 {
|
impl HardlinkData {
|
||||||
fn target(&self, link: &NodeRef) -> Result<NodeRef, Error> {
|
fn target(&self, link: &NodeRef) -> Result<NodeRef, Error> {
|
||||||
let target = self.imp.target(link)?;
|
let target = self.imp.target(link)?;
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ impl Node {
|
|||||||
/// Creates an entry within a directory with given [CreateInfo].
|
/// Creates an entry within a directory with given [CreateInfo].
|
||||||
pub fn create(self: &NodeRef, info: CreateInfo, check: AccessToken) -> Result<NodeRef, Error> {
|
pub fn create(self: &NodeRef, info: CreateInfo, check: AccessToken) -> Result<NodeRef, Error> {
|
||||||
let directory = self.as_directory()?;
|
let directory = self.as_directory()?;
|
||||||
|
if directory.contains(self, info.name) {
|
||||||
|
return Err(Error::AlreadyExists);
|
||||||
|
}
|
||||||
let node = directory.imp.create_node(self, &info)?;
|
let node = directory.imp.create_node(self, &info)?;
|
||||||
|
|
||||||
self.create_node(node.clone(), info.name, check.clone())?;
|
self.create_node(node.clone(), info.name, check.clone())?;
|
||||||
|
|||||||
@@ -149,6 +149,13 @@ pub trait DirectoryImpl: CommonImpl {
|
|||||||
Err(Error::NotImplemented)
|
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
|
/// Returns the "length" of the directory in entries
|
||||||
fn len(&self, node: &NodeRef) -> Result<usize, Error> {
|
fn len(&self, node: &NodeRef) -> Result<usize, Error> {
|
||||||
let _ = node;
|
let _ = node;
|
||||||
|
|||||||
@@ -32,12 +32,9 @@ impl Node {
|
|||||||
let directory = self.as_directory()?;
|
let directory = self.as_directory()?;
|
||||||
let mut children = directory.children.lock();
|
let mut children = directory.children.lock();
|
||||||
|
|
||||||
// TODO check if an entry already exists with such name
|
if children.iter().any(|(n, _)| name == *n) {
|
||||||
|
return Err(Error::AlreadyExists);
|
||||||
// if children.contains_key(&name) {
|
}
|
||||||
// log::warn!("Directory cache already contains an entry: {:?}", name);
|
|
||||||
// return Err(Error::AlreadyExists);
|
|
||||||
// }
|
|
||||||
|
|
||||||
assert!(child.parent.replace(Some(self.clone())).is_none());
|
assert!(child.parent.replace(Some(self.clone())).is_none());
|
||||||
children.push((name, child));
|
children.push((name, child));
|
||||||
|
|||||||
Generated
+8
@@ -2979,6 +2979,13 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stuff"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@@ -3039,6 +3046,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"stuff",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tui",
|
"tui",
|
||||||
"yggdrasil-abi",
|
"yggdrasil-abi",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ members = [
|
|||||||
"lib/libterm",
|
"lib/libterm",
|
||||||
"lib/logsink",
|
"lib/logsink",
|
||||||
"lib/runtime",
|
"lib/runtime",
|
||||||
|
"lib/stuff",
|
||||||
"lib/uipc",
|
"lib/uipc",
|
||||||
"lib/yasync",
|
"lib/yasync",
|
||||||
"netutils",
|
"netutils",
|
||||||
@@ -84,6 +85,7 @@ logsink.path = "lib/logsink"
|
|||||||
libutil.path = "../lib/libutil"
|
libutil.path = "../lib/libutil"
|
||||||
cryptic.path = "lib/cryptic"
|
cryptic.path = "lib/cryptic"
|
||||||
hclient.path = "lib/hclient"
|
hclient.path = "lib/hclient"
|
||||||
|
stuff.path = "lib/stuff"
|
||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
|
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
init:1:wait:/sbin/rc default
|
init:1:wait:/sbin/rc default
|
||||||
logd:1:once:/sbin/logd
|
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
|
sha2.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
tui.workspace = true
|
tui.workspace = true
|
||||||
|
stuff.workspace = true
|
||||||
|
|
||||||
# Own regex implementation?
|
# Own regex implementation?
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
@@ -77,6 +78,10 @@ path = "src/kmod.rs"
|
|||||||
name = "echo"
|
name = "echo"
|
||||||
path = "src/echo.rs"
|
path = "src/echo.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tar"
|
||||||
|
path = "src/tar.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ls"
|
name = "ls"
|
||||||
path = "src/ls.rs"
|
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() {
|
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();
|
jh0.join().unwrap();
|
||||||
|
jh1.join().unwrap();
|
||||||
loop {
|
println!("Finished");
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ enum Outcome {
|
|||||||
Data(usize),
|
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(
|
fn readline_inner(
|
||||||
stdin: &mut TerminalInput,
|
stdin: &mut TerminalInput,
|
||||||
stdout: &mut Stdout,
|
stdout: &mut Stdout,
|
||||||
@@ -50,7 +68,14 @@ fn readline_inner(
|
|||||||
}
|
}
|
||||||
// Tab
|
// Tab
|
||||||
'\t' => {
|
'\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() => {
|
ch if ch.is_whitespace() || ch.is_ascii_graphic() => {
|
||||||
let bytes = ch.encode_utf8(&mut ch_buffer);
|
let bytes = ch.encode_utf8(&mut ch_buffer);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const PROGRAMS: &[(&str, &str)] = &[
|
|||||||
("shell", "bin/sh"),
|
("shell", "bin/sh"),
|
||||||
// sysutils
|
// sysutils
|
||||||
("cat", "bin/cat"),
|
("cat", "bin/cat"),
|
||||||
|
("tar", "bin/tar"),
|
||||||
("chmod", "bin/chmod"),
|
("chmod", "bin/chmod"),
|
||||||
("chroot", "sbin/chroot"),
|
("chroot", "sbin/chroot"),
|
||||||
("date", "bin/date"),
|
("date", "bin/date"),
|
||||||
|
|||||||
Reference in New Issue
Block a user