use super::io::{self, Read};
#[cfg(feature = "std")]
use super::io::{Writable, Write};
use super::susp::SystemUseHeader;
use crate::types::U32LsbMsb;
#[cfg(feature = "alloc")]
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PosixFileMode: u32 {
const S_IFSOCK = 0o140000;
const S_IFLNK = 0o120000;
const S_IFREG = 0o100000;
const S_IFBLK = 0o060000;
const S_IFDIR = 0o040000;
const S_IFCHR = 0o020000;
const S_IFIFO = 0o010000;
const S_ISUID = 0o4000;
const S_ISGID = 0o2000;
const S_ISVTX = 0o1000;
const S_IRUSR = 0o0400;
const S_IWUSR = 0o0200;
const S_IXUSR = 0o0100;
const S_IRGRP = 0o0040;
const S_IWGRP = 0o0020;
const S_IXGRP = 0o0010;
const S_IROTH = 0o0004;
const S_IWOTH = 0o0002;
const S_IXOTH = 0o0001;
}
}
#[cfg(feature = "alloc")]
impl PosixFileMode {
pub fn regular(perms: u32) -> Self {
Self::S_IFREG | Self::from_bits_truncate(perms & 0o7777)
}
pub fn directory(perms: u32) -> Self {
Self::S_IFDIR | Self::from_bits_truncate(perms & 0o7777)
}
pub fn symlink() -> Self {
Self::S_IFLNK | Self::from_bits_truncate(0o777)
}
pub fn default_file() -> Self {
Self::regular(0o644)
}
pub fn default_dir() -> Self {
Self::directory(0o755)
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct PxEntry {
pub file_mode: U32LsbMsb,
pub file_links: U32LsbMsb,
pub file_uid: U32LsbMsb,
pub file_gid: U32LsbMsb,
pub file_serial: U32LsbMsb,
}
impl PxEntry {
pub fn new(mode: u32, nlink: u32, uid: u32, gid: u32, serial: u32) -> Self {
Self {
file_mode: U32LsbMsb::new(mode),
file_links: U32LsbMsb::new(nlink),
file_uid: U32LsbMsb::new(uid),
file_gid: U32LsbMsb::new(gid),
file_serial: U32LsbMsb::new(serial),
}
}
}
#[cfg(feature = "std")]
impl PxEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"PX",
length: 44,
version: 1,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct PnEntry {
pub dev_high: U32LsbMsb,
pub dev_low: U32LsbMsb,
}
impl PnEntry {
pub fn new(major: u32, minor: u32) -> Self {
Self {
dev_high: U32LsbMsb::new(major),
dev_low: U32LsbMsb::new(minor),
}
}
pub fn dev(&self) -> u64 {
((self.dev_high.read() as u64) << 32) | (self.dev_low.read() as u64)
}
}
#[cfg(feature = "std")]
impl PnEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"PN",
length: 20,
version: 1,
}
}
}
#[cfg(feature = "alloc")]
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NmFlags: u8 {
const CONTINUE = 0x01;
const CURRENT = 0x02;
const PARENT = 0x04;
const RESERVED3 = 0x08;
const RESERVED4 = 0x10;
const HOST = 0x20;
}
}
#[derive(Debug, Clone)]
#[cfg(feature = "alloc")]
pub struct NmEntry {
pub flags: NmFlags,
pub name: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
impl NmEntry {
pub fn new(name: &[u8]) -> Self {
Self {
flags: NmFlags::empty(),
name: name.to_vec(),
}
}
pub fn current() -> Self {
Self {
flags: NmFlags::CURRENT,
name: alloc::vec::Vec::new(),
}
}
pub fn parent() -> Self {
Self {
flags: NmFlags::PARENT,
name: alloc::vec::Vec::new(),
}
}
pub fn size(&self) -> usize {
5 + self.name.len()
}
}
#[cfg(feature = "std")]
impl NmEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"NM",
length: self.size() as u8,
version: 1,
}
}
}
#[cfg(feature = "alloc")]
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlComponentFlags: u8 {
const CONTINUE = 0x01;
const CURRENT = 0x02;
const PARENT = 0x04;
const ROOT = 0x08;
const RESERVED4 = 0x10;
const VOLROOT = 0x20;
}
}
#[derive(Debug, Clone)]
#[cfg(feature = "alloc")]
pub struct SlComponent {
pub flags: SlComponentFlags,
pub content: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
impl SlComponent {
pub fn new(segment: &[u8]) -> Self {
Self {
flags: SlComponentFlags::empty(),
content: segment.to_vec(),
}
}
pub fn root() -> Self {
Self {
flags: SlComponentFlags::ROOT,
content: alloc::vec::Vec::new(),
}
}
pub fn current() -> Self {
Self {
flags: SlComponentFlags::CURRENT,
content: alloc::vec::Vec::new(),
}
}
pub fn parent() -> Self {
Self {
flags: SlComponentFlags::PARENT,
content: alloc::vec::Vec::new(),
}
}
pub fn size(&self) -> usize {
2 + self.content.len()
}
}
#[derive(Debug, Clone)]
#[cfg(feature = "alloc")]
pub struct SlEntry {
pub flags: u8,
pub components: alloc::vec::Vec<SlComponent>,
}
#[cfg(feature = "alloc")]
impl SlEntry {
pub fn from_path(target: &str) -> Self {
let mut components = alloc::vec::Vec::new();
if target.starts_with('/') {
components.push(SlComponent::root());
}
for segment in target.split('/').filter(|s| !s.is_empty()) {
match segment {
"." => components.push(SlComponent::current()),
".." => components.push(SlComponent::parent()),
_ => components.push(SlComponent::new(segment.as_bytes())),
}
}
Self {
flags: 0,
components,
}
}
pub fn size(&self) -> usize {
5 + self.components.iter().map(|c| c.size()).sum::<usize>()
}
}
#[cfg(feature = "std")]
impl SlEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"SL",
length: self.size() as u8,
version: 1,
}
}
}
#[cfg(feature = "alloc")]
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TfFlags: u8 {
const CREATION = 0x01;
const MODIFY = 0x02;
const ACCESS = 0x04;
const ATTRIBUTES = 0x08;
const BACKUP = 0x10;
const EXPIRATION = 0x20;
const EFFECTIVE = 0x40;
const LONG_FORM = 0x80;
}
}
#[derive(Debug, Clone)]
#[cfg(feature = "alloc")]
pub struct TfEntry {
pub flags: TfFlags,
pub timestamps: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
impl TfEntry {
pub fn new_short(mtime: &[u8; 7], atime: &[u8; 7]) -> Self {
let flags = TfFlags::MODIFY | TfFlags::ACCESS;
let mut timestamps = alloc::vec::Vec::with_capacity(14);
timestamps.extend_from_slice(mtime);
timestamps.extend_from_slice(atime);
Self { flags, timestamps }
}
pub fn size(&self) -> usize {
5 + self.timestamps.len()
}
}
#[cfg(feature = "std")]
impl TfEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"TF",
length: self.size() as u8,
version: 1,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct ClEntry {
pub child_directory_location: U32LsbMsb,
}
impl ClEntry {
pub fn new(location: u32) -> Self {
Self {
child_directory_location: U32LsbMsb::new(location),
}
}
}
#[cfg(feature = "std")]
impl ClEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"CL",
length: 12,
version: 1,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct PlEntry {
pub parent_directory_location: U32LsbMsb,
}
impl PlEntry {
pub fn new(location: u32) -> Self {
Self {
parent_directory_location: U32LsbMsb::new(location),
}
}
}
#[cfg(feature = "std")]
impl PlEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"PL",
length: 12,
version: 1,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ReEntry;
#[cfg(feature = "std")]
impl ReEntry {
pub fn header(&self) -> SystemUseHeader {
SystemUseHeader {
sig: *b"RE",
length: 4,
version: 1,
}
}
}
io_transform! {
#[cfg(feature = "std")]
impl Writable for PxEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(bytemuck::bytes_of(self)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl PxEntry {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 40];
let len = (header.length as usize).saturating_sub(4).min(40);
data.read_exact(&mut buf[..len]).await?;
Ok(Self {
file_mode: *bytemuck::from_bytes(&buf[0..8]),
file_links: *bytemuck::from_bytes(&buf[8..16]),
file_uid: *bytemuck::from_bytes(&buf[16..24]),
file_gid: *bytemuck::from_bytes(&buf[24..32]),
file_serial: if len >= 40 {
*bytemuck::from_bytes(&buf[32..40])
} else {
U32LsbMsb::new(0)
},
})
}
}
#[cfg(feature = "std")]
impl Writable for PnEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(bytemuck::bytes_of(self)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl PnEntry {
pub async fn parse_data<R: Read>(_header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 16];
data.read_exact(&mut buf).await?;
Ok(Self {
dev_high: *bytemuck::from_bytes(&buf[0..8]),
dev_low: *bytemuck::from_bytes(&buf[8..16]),
})
}
}
#[cfg(feature = "std")]
impl Writable for NmEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(&[self.flags.bits()]).await?;
writer.write_all(&self.name).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl NmEntry {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut flags_byte = [0u8; 1];
data.read_exact(&mut flags_byte).await?;
let name_len = (header.length as usize).saturating_sub(5);
let mut name = alloc::vec![0u8; name_len];
data.read_exact(&mut name).await?;
Ok(Self {
flags: NmFlags::from_bits_truncate(flags_byte[0]),
name,
})
}
}
#[cfg(feature = "std")]
impl Writable for SlEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(&[self.flags]).await?;
for component in &self.components {
writer.write_all(&[component.flags.bits(), component.content.len() as u8]).await?;
writer.write_all(&component.content).await?;
}
Ok(())
}
}
#[cfg(feature = "std")]
impl SlEntry {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut flags = [0u8; 1];
data.read_exact(&mut flags).await?;
let mut remaining = (header.length as usize).saturating_sub(5);
let mut components = alloc::vec::Vec::new();
while remaining >= 2 {
let mut comp_header = [0u8; 2];
data.read_exact(&mut comp_header).await?;
remaining -= 2;
let comp_flags = SlComponentFlags::from_bits_truncate(comp_header[0]);
let comp_len = comp_header[1] as usize;
let mut content = alloc::vec![0u8; comp_len.min(remaining)];
data.read_exact(&mut content).await?;
remaining -= content.len();
components.push(SlComponent {
flags: comp_flags,
content,
});
}
Ok(Self {
flags: flags[0],
components,
})
}
}
#[cfg(feature = "std")]
impl Writable for TfEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(&[self.flags.bits()]).await?;
writer.write_all(&self.timestamps).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl TfEntry {
pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut flags_byte = [0u8; 1];
data.read_exact(&mut flags_byte).await?;
let flags = TfFlags::from_bits_truncate(flags_byte[0]);
let ts_len = (header.length as usize).saturating_sub(5);
let mut timestamps = alloc::vec![0u8; ts_len];
data.read_exact(&mut timestamps).await?;
Ok(Self { flags, timestamps })
}
}
#[cfg(feature = "std")]
impl Writable for ClEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(bytemuck::bytes_of(&self.child_directory_location)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl ClEntry {
pub async fn parse_data<R: Read>(_header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 8];
data.read_exact(&mut buf).await?;
Ok(Self {
child_directory_location: *bytemuck::from_bytes(&buf),
})
}
}
#[cfg(feature = "std")]
impl Writable for PlEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await?;
writer.write_all(bytemuck::bytes_of(&self.parent_directory_location)).await?;
Ok(())
}
}
#[cfg(feature = "std")]
impl PlEntry {
pub async fn parse_data<R: Read>(_header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 8];
data.read_exact(&mut buf).await?;
Ok(Self {
parent_directory_location: *bytemuck::from_bytes(&buf),
})
}
}
#[cfg(feature = "std")]
impl Writable for ReEntry {
async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header().write(writer).await
}
}
}
#[derive(Debug, Clone, Copy)]
#[cfg(feature = "alloc")]
pub struct RripOptions {
pub enabled: bool,
pub relocate_deep_dirs: bool,
pub preserve_permissions: bool,
pub preserve_ownership: bool,
pub preserve_timestamps: bool,
pub preserve_symlinks: bool,
pub preserve_devices: bool,
}
#[cfg(feature = "alloc")]
impl Default for RripOptions {
fn default() -> Self {
Self {
enabled: true,
relocate_deep_dirs: true,
preserve_permissions: true,
preserve_ownership: true,
preserve_timestamps: true,
preserve_symlinks: true,
preserve_devices: true,
}
}
}
#[cfg(feature = "alloc")]
impl RripOptions {
pub fn disabled() -> Self {
Self {
enabled: false,
relocate_deep_dirs: false,
preserve_permissions: false,
preserve_ownership: false,
preserve_timestamps: false,
preserve_symlinks: false,
preserve_devices: false,
}
}
}
#[cfg(feature = "alloc")]
pub struct RripBuilder {
builder: super::susp::SystemUseBuilder,
}
#[cfg(feature = "alloc")]
impl RripBuilder {
pub fn new() -> Self {
Self {
builder: super::susp::SystemUseBuilder::new(),
}
}
pub fn add_sp(&mut self, bytes_skipped: u8) -> &mut Self {
self.builder.add_sp(bytes_skipped);
self
}
pub fn add_rrip_er(&mut self) -> &mut Self {
self.builder.add_er(
"RRIP_1991A",
"THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS",
"PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE. SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR CONTACT INFORMATION.",
1,
);
self
}
pub fn add_px(&mut self, mode: u32, nlink: u32, uid: u32, gid: u32, serial: u32) -> &mut Self {
let px = PxEntry::new(mode, nlink, uid, gid, serial);
let mut buf = alloc::vec![0u8; 44];
buf[0..2].copy_from_slice(b"PX");
buf[2] = 44;
buf[3] = 1;
buf[4..44].copy_from_slice(bytemuck::bytes_of(&px));
self.builder.add_raw(buf);
self
}
pub fn add_pn(&mut self, major: u32, minor: u32) -> &mut Self {
let pn = PnEntry::new(major, minor);
let mut buf = alloc::vec![0u8; 20];
buf[0..2].copy_from_slice(b"PN");
buf[2] = 20;
buf[3] = 1;
buf[4..20].copy_from_slice(bytemuck::bytes_of(&pn));
self.builder.add_raw(buf);
self
}
pub fn add_nm(&mut self, name: &[u8]) -> &mut Self {
let mut remaining = name;
let max_per_entry = 250;
while !remaining.is_empty() {
let chunk_len = remaining.len().min(max_per_entry);
let (chunk, rest) = remaining.split_at(chunk_len);
remaining = rest;
let flags = if remaining.is_empty() { 0 } else { 0x01 }; let total_len = 5 + chunk.len();
let mut buf = alloc::vec![0u8; total_len];
buf[0..2].copy_from_slice(b"NM");
buf[2] = total_len as u8;
buf[3] = 1;
buf[4] = flags;
buf[5..].copy_from_slice(chunk);
self.builder.add_raw(buf);
}
self
}
pub fn add_nm_current(&mut self) -> &mut Self {
let buf = alloc::vec![b'N', b'M', 5, 1, 0x02]; self.builder.add_raw(buf);
self
}
pub fn add_nm_parent(&mut self) -> &mut Self {
let buf = alloc::vec![b'N', b'M', 5, 1, 0x04]; self.builder.add_raw(buf);
self
}
pub fn add_sl(&mut self, target: &str) -> &mut Self {
let sl = SlEntry::from_path(target);
let size = sl.size();
let mut buf = alloc::vec![0u8; size];
buf[0..2].copy_from_slice(b"SL");
buf[2] = size as u8;
buf[3] = 1;
buf[4] = sl.flags;
let mut offset = 5;
for component in &sl.components {
buf[offset] = component.flags.bits();
buf[offset + 1] = component.content.len() as u8;
buf[offset + 2..offset + 2 + component.content.len()]
.copy_from_slice(&component.content);
offset += 2 + component.content.len();
}
self.builder.add_raw(buf);
self
}
pub fn add_tf_short(&mut self, mtime: &[u8; 7], atime: &[u8; 7]) -> &mut Self {
let mut buf = alloc::vec![0u8; 19]; buf[0..2].copy_from_slice(b"TF");
buf[2] = 19;
buf[3] = 1;
buf[4] = 0x06; buf[5..12].copy_from_slice(mtime);
buf[12..19].copy_from_slice(atime);
self.builder.add_raw(buf);
self
}
pub fn add_cl(&mut self, child_location: u32) -> &mut Self {
let cl = ClEntry::new(child_location);
let mut buf = alloc::vec![0u8; 12];
buf[0..2].copy_from_slice(b"CL");
buf[2] = 12;
buf[3] = 1;
buf[4..12].copy_from_slice(bytemuck::bytes_of(&cl.child_directory_location));
self.builder.add_raw(buf);
self
}
pub fn add_pl(&mut self, parent_location: u32) -> &mut Self {
let pl = PlEntry::new(parent_location);
let mut buf = alloc::vec![0u8; 12];
buf[0..2].copy_from_slice(b"PL");
buf[2] = 12;
buf[3] = 1;
buf[4..12].copy_from_slice(bytemuck::bytes_of(&pl.parent_directory_location));
self.builder.add_raw(buf);
self
}
pub fn add_re(&mut self) -> &mut Self {
let buf = alloc::vec![b'R', b'E', 4, 1];
self.builder.add_raw(buf);
self
}
pub fn add_ce(&mut self, ce: super::susp::ContinuationArea) -> &mut Self {
self.builder.add_ce(ce);
self
}
pub fn add_st(&mut self) -> &mut Self {
self.builder.add_st();
self
}
pub fn size(&self) -> usize {
self.builder.size()
}
pub fn build(&self) -> alloc::vec::Vec<u8> {
self.builder.build()
}
pub fn build_split(&self, max_inline: usize) -> super::susp::SplitSu {
self.builder.build_split(max_inline)
}
pub fn is_empty(&self) -> bool {
self.builder.is_empty()
}
}
#[cfg(feature = "alloc")]
impl Default for RripBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
#[cfg(feature = "alloc")]
pub enum RockRidgeEntry {
PosixAttributes(PxEntry),
DeviceNumber(PnEntry),
AlternateName(NmEntry),
SymbolicLink(SlEntry),
Timestamps(TfEntry),
ChildLink(ClEntry),
ParentLink(PlEntry),
Relocated,
Unknown(SystemUseHeader),
}
#[cfg(all(feature = "std", test))]
mod tests {
use super::*;
use alloc::vec;
static_assertions::const_assert_eq!(size_of::<PxEntry>(), 40);
static_assertions::const_assert_eq!(size_of::<PnEntry>(), 16);
static_assertions::const_assert_eq!(size_of::<ClEntry>(), 8);
static_assertions::const_assert_eq!(size_of::<PlEntry>(), 8);
#[test]
fn test_px_entry_new() {
let px = PxEntry::new(0o100644, 1, 1000, 1000, 12345);
assert_eq!(px.file_mode.read(), 0o100644);
assert_eq!(px.file_links.read(), 1);
assert_eq!(px.file_uid.read(), 1000);
assert_eq!(px.file_gid.read(), 1000);
assert_eq!(px.file_serial.read(), 12345);
}
#[test]
fn test_px_entry_header() {
let px = PxEntry::new(0o100644, 1, 0, 0, 0);
let header = px.header();
assert_eq!(&header.sig, b"PX");
assert_eq!(header.length, 44);
assert_eq!(header.version, 1);
}
#[test]
fn test_pn_entry_new() {
let pn = PnEntry::new(8, 1);
assert_eq!(pn.dev_high.read(), 8);
assert_eq!(pn.dev_low.read(), 1);
}
#[test]
fn test_pn_entry_dev() {
let pn = PnEntry::new(0x12345678, 0xABCDEF01);
let dev = pn.dev();
assert_eq!(dev, (0x12345678u64 << 32) | 0xABCDEF01u64);
}
#[test]
fn test_cl_entry_new() {
let cl = ClEntry::new(100);
assert_eq!(cl.child_directory_location.read(), 100);
}
#[test]
fn test_pl_entry_new() {
let pl = PlEntry::new(50);
assert_eq!(pl.parent_directory_location.read(), 50);
}
#[test]
fn test_posix_file_mode_regular() {
let mode = PosixFileMode::regular(0o644);
assert!(mode.contains(PosixFileMode::S_IFREG));
assert!(mode.contains(PosixFileMode::S_IRUSR));
assert!(mode.contains(PosixFileMode::S_IWUSR));
assert!(mode.contains(PosixFileMode::S_IRGRP));
assert!(mode.contains(PosixFileMode::S_IROTH));
assert!(!mode.contains(PosixFileMode::S_IXUSR));
}
#[test]
fn test_posix_file_mode_directory() {
let mode = PosixFileMode::directory(0o755);
assert!(mode.contains(PosixFileMode::S_IFDIR));
assert!(mode.contains(PosixFileMode::S_IRUSR));
assert!(mode.contains(PosixFileMode::S_IWUSR));
assert!(mode.contains(PosixFileMode::S_IXUSR));
assert!(mode.contains(PosixFileMode::S_IRGRP));
assert!(mode.contains(PosixFileMode::S_IXGRP));
}
#[test]
fn test_posix_file_mode_symlink() {
let mode = PosixFileMode::symlink();
assert!(mode.contains(PosixFileMode::S_IFLNK));
}
#[test]
fn test_nm_entry_new() {
let nm = NmEntry::new(b"test_file.txt");
assert!(nm.flags.is_empty());
assert_eq!(nm.name, b"test_file.txt");
assert_eq!(nm.size(), 5 + 13);
}
#[test]
fn test_nm_entry_special() {
let current = NmEntry::current();
assert!(current.flags.contains(NmFlags::CURRENT));
assert!(current.name.is_empty());
let parent = NmEntry::parent();
assert!(parent.flags.contains(NmFlags::PARENT));
assert!(parent.name.is_empty());
}
#[test]
fn test_sl_component_new() {
let comp = SlComponent::new(b"subdir");
assert!(comp.flags.is_empty());
assert_eq!(comp.content, b"subdir");
assert_eq!(comp.size(), 2 + 6);
}
#[test]
fn test_sl_component_special() {
let root = SlComponent::root();
assert!(root.flags.contains(SlComponentFlags::ROOT));
assert!(root.content.is_empty());
let current = SlComponent::current();
assert!(current.flags.contains(SlComponentFlags::CURRENT));
let parent = SlComponent::parent();
assert!(parent.flags.contains(SlComponentFlags::PARENT));
}
#[test]
fn test_sl_entry_from_path_absolute() {
let sl = SlEntry::from_path("/usr/bin/test");
assert_eq!(sl.components.len(), 4); assert!(sl.components[0].flags.contains(SlComponentFlags::ROOT));
assert_eq!(sl.components[1].content, b"usr");
assert_eq!(sl.components[2].content, b"bin");
assert_eq!(sl.components[3].content, b"test");
}
#[test]
fn test_sl_entry_from_path_relative() {
let sl = SlEntry::from_path("../lib/libtest.so");
assert_eq!(sl.components.len(), 3); assert!(sl.components[0].flags.contains(SlComponentFlags::PARENT));
assert_eq!(sl.components[1].content, b"lib");
assert_eq!(sl.components[2].content, b"libtest.so");
}
#[test]
fn test_sl_entry_from_path_current() {
let sl = SlEntry::from_path("./local");
assert_eq!(sl.components.len(), 2); assert!(sl.components[0].flags.contains(SlComponentFlags::CURRENT));
assert_eq!(sl.components[1].content, b"local");
}
#[test]
fn test_tf_entry_new_short() {
let mtime = [126, 1, 15, 10, 30, 0, 0]; let atime = [126, 1, 15, 12, 0, 0, 0]; let tf = TfEntry::new_short(&mtime, &atime);
assert!(tf.flags.contains(TfFlags::MODIFY));
assert!(tf.flags.contains(TfFlags::ACCESS));
assert!(!tf.flags.contains(TfFlags::LONG_FORM));
assert_eq!(tf.timestamps.len(), 14);
}
#[test]
fn test_rrip_options_default() {
let opts = RripOptions::default();
assert!(opts.enabled);
assert!(opts.relocate_deep_dirs);
assert!(opts.preserve_permissions);
assert!(opts.preserve_ownership);
assert!(opts.preserve_timestamps);
assert!(opts.preserve_symlinks);
assert!(opts.preserve_devices);
}
#[test]
fn test_rrip_options_disabled() {
let opts = RripOptions::disabled();
assert!(!opts.enabled);
assert!(!opts.relocate_deep_dirs);
assert!(!opts.preserve_permissions);
}
#[test]
fn test_rrip_builder_empty() {
let builder = RripBuilder::new();
assert!(builder.is_empty());
assert_eq!(builder.size(), 0);
}
#[test]
fn test_rrip_builder_px() {
let mut builder = RripBuilder::new();
builder.add_px(0o100644, 1, 1000, 1000, 12345);
assert_eq!(builder.size(), 44);
let data = builder.build();
assert_eq!(&data[0..2], b"PX");
assert_eq!(data[2], 44); assert_eq!(data[3], 1); }
#[test]
fn test_rrip_builder_pn() {
let mut builder = RripBuilder::new();
builder.add_pn(8, 1);
assert_eq!(builder.size(), 20);
let data = builder.build();
assert_eq!(&data[0..2], b"PN");
assert_eq!(data[2], 20);
}
#[test]
fn test_rrip_builder_nm() {
let mut builder = RripBuilder::new();
builder.add_nm(b"test.txt");
assert_eq!(builder.size(), 5 + 8);
let data = builder.build();
assert_eq!(&data[0..2], b"NM");
assert_eq!(data[4], 0); assert_eq!(&data[5..], b"test.txt");
}
#[test]
fn test_rrip_builder_nm_special() {
let mut builder = RripBuilder::new();
builder.add_nm_current();
let data = builder.build();
assert_eq!(data[4], 0x02);
let mut builder = RripBuilder::new();
builder.add_nm_parent();
let data = builder.build();
assert_eq!(data[4], 0x04); }
#[test]
fn test_rrip_builder_sl() {
let mut builder = RripBuilder::new();
builder.add_sl("../lib");
let data = builder.build();
assert_eq!(&data[0..2], b"SL");
}
#[test]
fn test_rrip_builder_cl_pl_re() {
let mut builder = RripBuilder::new();
builder.add_cl(100).add_pl(50).add_re();
let data = builder.build();
assert_eq!(&data[0..2], b"CL");
assert_eq!(data[2], 12);
assert_eq!(&data[12..14], b"PL");
assert_eq!(data[14], 12);
assert_eq!(&data[24..26], b"RE");
assert_eq!(data[26], 4);
}
#[test]
fn test_rrip_builder_complete() {
let mut builder = RripBuilder::new();
builder
.add_sp(0)
.add_rrip_er()
.add_px(0o100644, 1, 1000, 1000, 1)
.add_nm(b"testfile.txt")
.add_st();
let data = builder.build();
assert!(!data.is_empty());
assert_eq!(&data[0..2], b"SP");
}
#[test]
fn test_rrip_builder_long_name_split() {
let mut builder = RripBuilder::new();
let long_name = vec![b'a'; 300];
builder.add_nm(&long_name);
let data = builder.build();
assert_eq!(&data[0..2], b"NM");
assert_eq!(data[4] & 0x01, 0x01);
}
}