Partial port to new spec revision (won't compile yet);
This commit is contained in:
parent
d368bc5764
commit
64a8269b9d
26
Format.md
26
Format.md
@ -76,21 +76,33 @@ The following four bytes store the number of nodes making up the archive.
|
|||||||
| **bytes** | **meaning** |
|
| **bytes** | **meaning** |
|
||||||
| ----- | ------- |
|
| ----- | ------- |
|
||||||
| 0-6 | "\x89haggis" - the haggis "magic" identifier |
|
| 0-6 | "\x89haggis" - the haggis "magic" identifier |
|
||||||
| 7-11 | The number of files in the archive (32 bit unsigned int) |
|
| 7-10 | The number of files in the archive (32 bit unsigned int) |
|
||||||
|
|
||||||
## Nodes
|
## Nodes
|
||||||
| **bytes** | **meaning** |
|
| **bytes** | **meaning** |
|
||||||
| ----- | ------- |
|
| ----- | ------- |
|
||||||
| the next 8 bytes | The **length** of the filename (64 bit unsigned int) |
|
| the next 2 bytes | The **length** of the filename (16 bit unsigned int) |
|
||||||
| the next **length** bytes | The bytes making up the filename |
|
| the next **length** bytes | The bytes making up the filename |
|
||||||
| the next 4 bytes | The files Unix permissions mode (32 bit unsigned int) |
|
|
||||||
| the next 4 bytes | The uid of the file's owner (32 bit unsigned int) |
|
| the next 4 bytes | The uid of the file's owner (32 bit unsigned int) |
|
||||||
| the next 4 bytes | the gid of the file's owner (32 bit unsigned int) |
|
| the next 4 bytes | the gid of the file's owner (32 bit unsigned int) |
|
||||||
| the next 8 bytes | The most recent modification time (64 bit unsigned int) |
|
| the next 8 bytes | The most recent modification time (64 bit unsigned int) |
|
||||||
| the next byte | a flag representing the file's type |
|
| the next 2 bytes | The file's Unix permissions mode and file type (see next section) |
|
||||||
|
|
||||||
## File types
|
## File mode and type
|
||||||
The file types represented by the final flag in the previous table are as follows:
|
To recreate the Unix permissions and file type flag, the two bytes making up this field
|
||||||
|
are first interpreted as a 16-bit integer, which has been stored in little endian format
|
||||||
|
like all of the previous integers. To derive the mode, we `&` the three most significant
|
||||||
|
bits out as cast it to an appropriately sized uint for the platform. The file type flag
|
||||||
|
is made up of the three bits that we previously removed. In pseudo-code:
|
||||||
|
```Rust
|
||||||
|
let bits = [42, 69];
|
||||||
|
let raw = u16::fromLittleEndianBytes(bits);
|
||||||
|
let mask = 0b111 << 13;
|
||||||
|
let mode = raw & mask;
|
||||||
|
let flag = raw & !mask;
|
||||||
|
```
|
||||||
|
|
||||||
|
The file mode flag is then interpreted as follows:
|
||||||
| **flag** | **file type** |
|
| **flag** | **file type** |
|
||||||
| ---- | --------- |
|
| ---- | --------- |
|
||||||
| 0 | Normal file |
|
| 0 | Normal file |
|
||||||
@ -136,7 +148,7 @@ after the last byte of the file.
|
|||||||
### Hard and soft links
|
### Hard and soft links
|
||||||
| **bytes** | **meaning** |
|
| **bytes** | **meaning** |
|
||||||
| ----- | ------- |
|
| ----- | ------- |
|
||||||
| next 8 | the **length** of the link target's file name |
|
| next 2 | the **length** of the link target's file name (16 bit unsigned int |
|
||||||
| the next **length** bytes | the link target's file name |
|
| the next **length** bytes | the link target's file name |
|
||||||
|
|
||||||
The next byte will be the beginning of the following archive node.
|
The next byte will be the beginning of the following archive node.
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use {
|
use {
|
||||||
crate::Error,
|
crate::Error,
|
||||||
std::{io::{Read, Write}, str::FromStr},
|
std::{
|
||||||
|
io::{Read, Write},
|
||||||
|
str::FromStr,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -3,6 +3,35 @@ use {
|
|||||||
std::io::{Read, Write},
|
std::io::{Read, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub(crate) enum Flag {
|
||||||
|
Normal,
|
||||||
|
HardLink,
|
||||||
|
SoftLink,
|
||||||
|
Directory,
|
||||||
|
Character,
|
||||||
|
Block,
|
||||||
|
Fifo,
|
||||||
|
Eof,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Flag {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(Self::Normal),
|
||||||
|
1 => Ok(Self::HardLink),
|
||||||
|
2 => Ok(Self::SoftLink),
|
||||||
|
3 => Ok(Self::Directory),
|
||||||
|
4 => Ok(Self::Character),
|
||||||
|
5 => Ok(Self::Block),
|
||||||
|
6 => Ok(Self::Fifo),
|
||||||
|
7 => Ok(Self::Eof),
|
||||||
|
8 => Err(Error::UnknownFileType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An enum representing the type of file of an archive member
|
/// An enum representing the type of file of an archive member
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FileType {
|
pub enum FileType {
|
||||||
@ -25,45 +54,43 @@ pub enum FileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FileType {
|
impl FileType {
|
||||||
pub(crate) fn read<T: Read>(reader: &mut T) -> Result<Self, Error> {
|
pub(crate) fn read<T: Read>(reader: &mut T, flag: Flag) -> Result<Self, Error> {
|
||||||
let mut buf = [0; 1];
|
match flag {
|
||||||
reader.read_exact(&mut buf)?;
|
Flag::Normal => {
|
||||||
match buf[0] {
|
|
||||||
0 => {
|
|
||||||
let file = File::read(reader)?;
|
let file = File::read(reader)?;
|
||||||
Ok(Self::Normal(file))
|
Ok(Self::Normal(file))
|
||||||
}
|
}
|
||||||
1 => {
|
Flag::HardLink => {
|
||||||
let mut len = [0; 8];
|
let mut len = [0; 2];
|
||||||
reader.read_exact(&mut len)?;
|
reader.read_exact(&mut len)?;
|
||||||
let len = u64::from_le_bytes(len);
|
let len = u16::from_le_bytes(len);
|
||||||
let mut buf = Vec::with_capacity(len.try_into()?);
|
let mut buf = Vec::with_capacity(len.into());
|
||||||
let mut handle = reader.take(len);
|
let mut handle = reader.take(len.into());
|
||||||
handle.read_exact(&mut buf)?;
|
handle.read_exact(&mut buf)?;
|
||||||
let s = String::from_utf8(buf)?;
|
let s = String::from_utf8(buf)?;
|
||||||
Ok(Self::HardLink(s))
|
Ok(Self::HardLink(s))
|
||||||
}
|
}
|
||||||
2 => {
|
Flag::SoftLink => {
|
||||||
let mut len = [0; 8];
|
let mut len = [0; 2];
|
||||||
reader.read_exact(&mut len)?;
|
reader.read_exact(&mut len)?;
|
||||||
let len = u64::from_le_bytes(len);
|
let len = u16::from_le_bytes(len);
|
||||||
let mut buf = Vec::with_capacity(len.try_into()?);
|
let mut buf = Vec::with_capacity(len.into());
|
||||||
let mut handle = reader.take(len);
|
let mut handle = reader.take(len.into());
|
||||||
handle.read_exact(&mut buf)?;
|
handle.read_exact(&mut buf)?;
|
||||||
let s = String::from_utf8(buf)?;
|
let s = String::from_utf8(buf)?;
|
||||||
Ok(Self::SoftLink(s))
|
Ok(Self::SoftLink(s))
|
||||||
}
|
}
|
||||||
3 => Ok(Self::Directory),
|
Flag::Directory => Ok(Self::Directory),
|
||||||
4 => {
|
Flag::Character => {
|
||||||
let sp = Special::read(reader)?;
|
let sp = Special::read(reader)?;
|
||||||
Ok(Self::Character(sp))
|
Ok(Self::Character(sp))
|
||||||
}
|
}
|
||||||
5 => {
|
Flag::Block => {
|
||||||
let sp = Special::read(reader)?;
|
let sp = Special::read(reader)?;
|
||||||
Ok(Self::Block(sp))
|
Ok(Self::Block(sp))
|
||||||
}
|
}
|
||||||
6 => Ok(Self::Fifo),
|
Flag::Fifo => Ok(Self::Fifo),
|
||||||
_ => Err(Error::UnknownFileType),
|
Flag::Eof => Ok(Self::Eof),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ pub use {
|
|||||||
filetype::FileType,
|
filetype::FileType,
|
||||||
node::Node,
|
node::Node,
|
||||||
special::Special,
|
special::Special,
|
||||||
stream::Stream
|
stream::Stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
|
50
src/node.rs
50
src/node.rs
@ -1,5 +1,5 @@
|
|||||||
use {
|
use {
|
||||||
crate::{nix, Algorithm, Checksum, Error, File, FileType, Special},
|
crate::{filetype::Flag, nix, Algorithm, Checksum, Error, File, FileType, Special},
|
||||||
md5::{Digest, Md5},
|
md5::{Digest, Md5},
|
||||||
sha1::Sha1,
|
sha1::Sha1,
|
||||||
sha2::Sha256,
|
sha2::Sha256,
|
||||||
@ -45,14 +45,14 @@ impl From<u32> for Kind {
|
|||||||
pub struct Node {
|
pub struct Node {
|
||||||
/// The filesystem path to this file
|
/// The filesystem path to this file
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// The Unix permissions bits of this file
|
|
||||||
pub mode: u32,
|
|
||||||
/// The user id of this file's owner
|
/// The user id of this file's owner
|
||||||
pub uid: u32,
|
pub uid: u32,
|
||||||
/// The group id of this file's owner
|
/// The group id of this file's owner
|
||||||
pub gid: u32,
|
pub gid: u32,
|
||||||
/// The most recent modification time of this file
|
/// The most recent modification time of this file
|
||||||
pub mtime: u64,
|
pub mtime: u64,
|
||||||
|
/// The Unix permissions bits of this file
|
||||||
|
pub mode: u16,
|
||||||
/// The type of file this node represents
|
/// The type of file this node represents
|
||||||
pub filetype: FileType,
|
pub filetype: FileType,
|
||||||
}
|
}
|
||||||
@ -62,9 +62,9 @@ impl Node {
|
|||||||
/// > Note: this function reads an already created node. To create a new node
|
/// > Note: this function reads an already created node. To create a new node
|
||||||
/// > from a file, use the `from_path` method.
|
/// > from a file, use the `from_path` method.
|
||||||
pub fn read<T: Read>(reader: &mut T) -> Result<Self, Error> {
|
pub fn read<T: Read>(reader: &mut T) -> Result<Self, Error> {
|
||||||
let mut len = [0; 8];
|
let mut len = [0; 2];
|
||||||
reader.read_exact(&mut len)?;
|
reader.read_exact(&mut len)?;
|
||||||
let len = u64::from_le_bytes(len);
|
let len = u16::from_le_bytes(len);
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
@ -75,22 +75,27 @@ impl Node {
|
|||||||
filetype: FileType::Eof,
|
filetype: FileType::Eof,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut name = Vec::with_capacity(len.try_into()?);
|
let mut name = Vec::with_capacity(len.into());
|
||||||
let mut handle = reader.take(len);
|
let mut handle = reader.take(len.into());
|
||||||
handle.read_to_end(&mut name)?;
|
handle.read_to_end(&mut name)?;
|
||||||
let mut buf = [0; 20];
|
let mut buf = [0; 18];
|
||||||
reader.read_exact(&mut buf)?;
|
reader.read_exact(&mut buf)?;
|
||||||
let mode: [u8; 4] = buf[..4].try_into()?;
|
let uid: [u8; 4] = buf[0..4].try_into()?;
|
||||||
let uid: [u8; 4] = buf[4..8].try_into()?;
|
let gid: [u8; 4] = buf[4..8].try_into()?;
|
||||||
let gid: [u8; 4] = buf[8..12].try_into()?;
|
let mtime: [u8; 8] = buf[8..16].try_into()?;
|
||||||
let mtime: [u8; 8] = buf[12..].try_into()?;
|
let raw_mode: [u8; 2] = buf[16..18].try_into()?;
|
||||||
let filetype = FileType::read(reader)?;
|
let raw_mode = u16::from_le_bytes(raw_mode);
|
||||||
|
let mask: u16 = 0b111 << 13;
|
||||||
|
let mode = raw_mode & mask;
|
||||||
|
let flag: u8 = ((raw_mode & !mask) >> 13).try_into()?;
|
||||||
|
let flag: Flag = flag.try_into()?;
|
||||||
|
let filetype = FileType::read(reader, flag)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: String::from_utf8(name)?,
|
name: String::from_utf8(name)?,
|
||||||
mode: u32::from_le_bytes(mode),
|
|
||||||
uid: u32::from_le_bytes(uid),
|
uid: u32::from_le_bytes(uid),
|
||||||
gid: u32::from_le_bytes(gid),
|
gid: u32::from_le_bytes(gid),
|
||||||
mtime: u64::from_le_bytes(mtime),
|
mtime: u64::from_le_bytes(mtime),
|
||||||
|
mode,
|
||||||
filetype,
|
filetype,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -100,10 +105,11 @@ impl Node {
|
|||||||
/// > representation. To extract the contents of a `Node` and write out the
|
/// > representation. To extract the contents of a `Node` and write out the
|
||||||
/// > file it represents, use the `extract` method instead.
|
/// > file it represents, use the `extract` method instead.
|
||||||
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), Error> {
|
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), Error> {
|
||||||
let len = self.name.len() as u64;
|
let len: u16 = self.name.len().try_into()?;
|
||||||
writer.write_all(&len.to_le_bytes())?;
|
writer.write_all(&len.to_le_bytes())?;
|
||||||
writer.write_all(self.name.as_bytes())?;
|
writer.write_all(self.name.as_bytes())?;
|
||||||
[self.mode, self.uid, self.gid]
|
writer.write_all(&self.mode.to_le_bytes())?;
|
||||||
|
[self.uid, self.gid]
|
||||||
.iter()
|
.iter()
|
||||||
.try_for_each(|f| writer.write_all(&f.to_le_bytes()))?;
|
.try_for_each(|f| writer.write_all(&f.to_le_bytes()))?;
|
||||||
writer.write_all(&self.mtime.to_le_bytes())?;
|
writer.write_all(&self.mtime.to_le_bytes())?;
|
||||||
@ -237,7 +243,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
match self.filetype {
|
match self.filetype {
|
||||||
FileType::Eof => {},
|
FileType::Eof => {}
|
||||||
FileType::Fifo => {
|
FileType::Fifo => {
|
||||||
nix::mkfifo(&path, self.mode)?;
|
nix::mkfifo(&path, self.mode)?;
|
||||||
if euid == 0 {
|
if euid == 0 {
|
||||||
@ -294,7 +300,10 @@ impl Node {
|
|||||||
if nix::geteuid() == 0 {
|
if nix::geteuid() == 0 {
|
||||||
nix::chown(dir.to_str().ok_or(Error::NulError)?, self.uid, self.gid)?;
|
nix::chown(dir.to_str().ok_or(Error::NulError)?, self.uid, self.gid)?;
|
||||||
}
|
}
|
||||||
nix::chmod(dir.to_str().ok_or(Error::BadPath)?, self.mode & 0o7777 | 0o100)?;
|
nix::chmod(
|
||||||
|
dir.to_str().ok_or(Error::BadPath)?,
|
||||||
|
self.mode & 0o7777 | 0o100,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,7 +326,10 @@ mod tests {
|
|||||||
for c in &sum {
|
for c in &sum {
|
||||||
write!(s, "{c:02x}").unwrap();
|
write!(s, "{c:02x}").unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(s, "5f1b6e6e31682fb6683db2e78db11e624527c897618f1a5b0a0b5256f557c22d");
|
assert_eq!(
|
||||||
|
s,
|
||||||
|
"5f1b6e6e31682fb6683db2e78db11e624527c897618f1a5b0a0b5256f557c22d"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
use crate::MAGIC;
|
use crate::MAGIC;
|
||||||
|
|
||||||
|
#[cfg(feature = "parallel")]
|
||||||
|
use {
|
||||||
|
crate::FileType,
|
||||||
|
rayon::{iter::ParallelBridge, prelude::ParallelIterator},
|
||||||
|
std::sync::mpsc::Sender,
|
||||||
|
};
|
||||||
use {
|
use {
|
||||||
crate::{Error, Node},
|
crate::{Error, Node},
|
||||||
std::{
|
std::{
|
||||||
@ -7,12 +13,6 @@ use {
|
|||||||
iter::Iterator,
|
iter::Iterator,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "parallel")]
|
|
||||||
use {
|
|
||||||
crate::FileType,
|
|
||||||
rayon::{iter::ParallelBridge, prelude::ParallelIterator},
|
|
||||||
std::sync::mpsc::Sender,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An iterator over a series of archive `Node`'s. This struct is generic over any
|
/// An iterator over a series of archive `Node`'s. This struct is generic over any
|
||||||
/// type which implements `Read`, such as a file or a network stream.
|
/// type which implements `Read`, such as a file or a network stream.
|
||||||
@ -79,24 +79,34 @@ impl<R: Read + Send> Stream<R> {
|
|||||||
n.extract(prefix)?;
|
n.extract(prefix)?;
|
||||||
match n.filetype {
|
match n.filetype {
|
||||||
FileType::Normal(f) => {
|
FileType::Normal(f) => {
|
||||||
s.send(Message::FileExtracted { name: n.name.clone(), size: f.len })
|
s.send(Message::FileExtracted {
|
||||||
|
name: n.name.clone(),
|
||||||
|
size: f.len,
|
||||||
|
})
|
||||||
.map_err(|_| Error::SenderError)?;
|
.map_err(|_| Error::SenderError)?;
|
||||||
},
|
}
|
||||||
FileType::SoftLink(t) | FileType::HardLink(t) => {
|
FileType::SoftLink(t) | FileType::HardLink(t) => {
|
||||||
s.send(Message::LinkCreated { name: n.name.clone(), target: t.clone() })
|
s.send(Message::LinkCreated {
|
||||||
|
name: n.name.clone(),
|
||||||
|
target: t.clone(),
|
||||||
|
})
|
||||||
.map_err(|_| Error::SenderError)?;
|
.map_err(|_| Error::SenderError)?;
|
||||||
},
|
}
|
||||||
FileType::Directory => {
|
FileType::Directory => {
|
||||||
s.send(Message::DirectoryCreated { name: n.name.clone() })
|
s.send(Message::DirectoryCreated {
|
||||||
|
name: n.name.clone(),
|
||||||
|
})
|
||||||
.map_err(|_| Error::SenderError)?;
|
.map_err(|_| Error::SenderError)?;
|
||||||
},
|
}
|
||||||
FileType::Block(_) | FileType::Character(_) | FileType::Fifo => {
|
FileType::Block(_) | FileType::Character(_) | FileType::Fifo => {
|
||||||
s.send(Message::DeviceCreated { name: n.name.clone() })
|
s.send(Message::DeviceCreated {
|
||||||
|
name: n.name.clone(),
|
||||||
|
})
|
||||||
.map_err(|_| Error::SenderError)?;
|
.map_err(|_| Error::SenderError)?;
|
||||||
},
|
}
|
||||||
FileType::Eof => {
|
FileType::Eof => {
|
||||||
s.send(Message::Eof).map_err(|_| Error::SenderError)?;
|
s.send(Message::Eof).map_err(|_| Error::SenderError)?;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
Ok::<(), Error>(())
|
Ok::<(), Error>(())
|
||||||
})?;
|
})?;
|
||||||
|
Loading…
Reference in New Issue
Block a user