use crate::{
util::{from_oct_ch, parse_time, Array48, Array64, FromDec, FromHex},
Device,
};
use std::{fmt, time::Duration};
#[derive(Debug)]
pub enum MTreeLine<'a> {
Blank,
Comment(&'a [u8]),
Special(SpecialKind, Vec<Keyword<'a>>),
Relative(&'a [u8], Vec<Keyword<'a>>),
DotDot,
Full(&'a [u8], Vec<Keyword<'a>>),
}
impl<'a> MTreeLine<'a> {
pub fn from_bytes(input: &'a [u8]) -> ParserResult<MTreeLine<'a>> {
let mut parts = input
.split(|ch| *ch == b' ')
.filter(|word| !word.is_empty());
let first = match parts.next() {
Some(f) => f,
None => return Ok(MTreeLine::Blank),
};
if first[0] == b'#' {
return Ok(MTreeLine::Comment(input));
}
if first == b".." {
return Ok(MTreeLine::DotDot);
}
let mut params = Vec::new();
for part in parts {
let keyword = Keyword::from_bytes(part);
debug_assert!(
keyword.is_ok(),
"could not parse bytes: {}",
String::from_utf8_lossy(part)
);
if let Ok(keyword) = keyword {
params.push(keyword);
}
}
if first[0] == b'/' {
let kind = SpecialKind::from_bytes(&first[1..])?;
Ok(MTreeLine::Special(kind, params))
} else if first.contains(&b'/') {
Ok(MTreeLine::Full(first, params))
} else {
Ok(MTreeLine::Relative(first, params))
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SpecialKind {
Set,
Unset,
}
impl SpecialKind {
fn from_bytes(input: &[u8]) -> ParserResult<SpecialKind> {
Ok(match input {
b"set" => SpecialKind::Set,
b"unset" => SpecialKind::Unset,
_ => {
return Err(format!(
r#""{}" is not a special command"#,
String::from_utf8_lossy(input)
)
.into());
}
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Keyword<'a> {
Checksum(u64),
DeviceRef(DeviceRef<'a>),
Contents(&'a [u8]),
Flags(&'a [u8]),
Gid(u64),
Gname(&'a [u8]),
Ignore,
Inode(u64),
Link(&'a [u8]),
Md5(u128),
Mode(FileMode),
NLink(u64),
NoChange,
Optional,
ResidentDeviceRef(DeviceRef<'a>),
Rmd160([u8; 20]),
Sha1([u8; 20]),
Sha256([u8; 32]),
Sha384(Array48<u8>),
Sha512(Array64<u8>),
Size(u64),
Time(Duration),
Type(FileType),
Uid(u64),
Uname(&'a [u8]),
}
impl<'a> Keyword<'a> {
fn from_bytes(input: &'a [u8]) -> ParserResult<Keyword<'a>> {
fn next<'a>(field: &'static str, val: Option<&'a [u8]>) -> ParserResult<&'a [u8]> {
val.ok_or_else(|| format!(r#""{}" requires a parameter, none found"#, field).into())
}
let mut iter = input.splitn(2, |ch| *ch == b'=');
let key = iter.next().unwrap(); Ok(match key {
b"cksum" => Keyword::Checksum(u64::from_dec(next("cksum", iter.next())?)?),
b"device" => Keyword::DeviceRef(DeviceRef::from_bytes(next("devices", iter.next())?)?),
b"contents" => Keyword::Contents(next("contents", iter.next())?),
b"flags" => Keyword::Flags(next("flags", iter.next())?),
b"gid" => Keyword::Gid(u64::from_dec(next("gid", iter.next())?)?),
b"gname" => Keyword::Gname(next("gname", iter.next())?),
b"ignore" => Keyword::Ignore,
b"inode" => Keyword::Inode(u64::from_dec(next("inode", iter.next())?)?),
b"link" => Keyword::Link(next("link", iter.next())?),
b"md5" | b"md5digest" => {
Keyword::Md5(u128::from_hex(next("md5|md5digest", iter.next())?)?)
}
b"mode" => Keyword::Mode(FileMode::from_bytes(next("mode", iter.next())?)?),
b"nlink" => Keyword::NLink(u64::from_dec(next("nlink", iter.next())?)?),
b"nochange" => Keyword::NoChange,
b"optional" => Keyword::Optional,
b"resdevice" => {
Keyword::ResidentDeviceRef(DeviceRef::from_bytes(next("resdevice", iter.next())?)?)
}
b"rmd160" | b"rmd160digest" | b"ripemd160digest" => Keyword::Rmd160(
<[u8; 20]>::from_hex(next("rmd160|rmd160digest|ripemd160digest", iter.next())?)?,
),
b"sha1" | b"sha1digest" => {
Keyword::Sha1(<[u8; 20]>::from_hex(next("sha1|sha1digest", iter.next())?)?)
}
b"sha256" | b"sha256digest" => Keyword::Sha256(<[u8; 32]>::from_hex(next(
"sha256|sha256digest",
iter.next(),
)?)?),
b"sha384" | b"sha384digest" => Keyword::Sha384(<Array48<u8>>::from_hex(next(
"sha384|sha384digest",
iter.next(),
)?)?),
b"sha512" | b"sha512digest" => Keyword::Sha512(<Array64<u8>>::from_hex(next(
"sha512|sha512digest",
iter.next(),
)?)?),
b"size" => Keyword::Size(u64::from_dec(next("size", iter.next())?)?),
b"time" => Keyword::Time(parse_time(next("time", iter.next())?)?),
b"type" => Keyword::Type(FileType::from_bytes(next("type", iter.next())?)?),
b"uid" => Keyword::Uid(u64::from_dec(next("uid", iter.next())?)?),
b"uname" => Keyword::Uname(next("uname", iter.next())?),
other => {
return Err(format!(
r#""{}" is not a valid parameter key (in "{}")"#,
String::from_utf8_lossy(other),
String::from_utf8_lossy(input)
)
.into());
}
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct DeviceRef<'a> {
format: Format,
major: &'a [u8],
minor: &'a [u8],
subunit: Option<&'a [u8]>,
}
impl<'a> DeviceRef<'a> {
pub fn to_device(&self) -> Device {
Device {
format: self.format,
major: self.major.to_owned(),
minor: self.minor.to_owned(),
subunit: self.subunit.map(|val| val.to_owned()),
}
}
fn from_bytes(input: &'a [u8]) -> ParserResult<DeviceRef<'a>> {
let mut iter = input.splitn(4, |ch| *ch == b',');
let format = Format::from_bytes(iter.next().ok_or_else(|| {
format!(
r#"could not read format from device "{}""#,
String::from_utf8_lossy(input)
)
})?)?;
let major = iter.next().ok_or_else(|| {
format!(
r#"could not read major field from device "{}""#,
String::from_utf8_lossy(input)
)
})?;
let minor = iter.next().ok_or_else(|| {
format!(
r#"could not read minor field from device "{}""#,
String::from_utf8_lossy(input)
)
})?;
let subunit = iter.next();
Ok(DeviceRef {
format,
major,
minor,
subunit,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Format {
Native,
Bsd386,
Bsd4,
BsdOs,
FreeBsd,
Hpux,
Isc,
Linux,
NetBsd,
Osf1,
Sco,
Solaris,
SunOs,
Svr3,
Svr4,
Ultrix,
}
impl Format {
fn from_bytes(bytes: &[u8]) -> ParserResult<Format> {
Ok(match bytes {
b"native" => Format::Native,
b"386bsd" => Format::Bsd386,
b"4bsd" => Format::Bsd4,
b"bsdos" => Format::BsdOs,
b"freebsd" => Format::FreeBsd,
b"hpux" => Format::Hpux,
b"isc" => Format::Isc,
b"linux" => Format::Linux,
b"netbsd" => Format::NetBsd,
b"osf1" => Format::Osf1,
b"sco" => Format::Sco,
b"solaris" => Format::Solaris,
b"sunos" => Format::SunOs,
b"svr3" => Format::Svr3,
b"svr4" => Format::Svr4,
b"ultrix" => Format::Ultrix,
ref other => {
return Err(format!(
r#""{}" is not a valid format"#,
String::from_utf8_lossy(other)
)
.into());
}
})
}
}
#[test]
fn test_format_from_butes() {
for (input, res) in vec![
(&b"native"[..], Format::Native),
(&b"386bsd"[..], Format::Bsd386),
(&b"4bsd"[..], Format::Bsd4),
(&b"bsdos"[..], Format::BsdOs),
(&b"freebsd"[..], Format::FreeBsd),
(&b"hpux"[..], Format::Hpux),
(&b"isc"[..], Format::Isc),
(&b"linux"[..], Format::Linux),
(&b"netbsd"[..], Format::NetBsd),
(&b"osf1"[..], Format::Osf1),
(&b"sco"[..], Format::Sco),
(&b"solaris"[..], Format::Solaris),
(&b"sunos"[..], Format::SunOs),
(&b"svr3"[..], Format::Svr3),
(&b"svr4"[..], Format::Svr4),
(&b"ultrix"[..], Format::Ultrix),
] {
assert_eq!(Format::from_bytes(&input[..]), Ok(res));
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FileType {
BlockDevice,
CharacterDevice,
Directory,
Fifo,
File,
SymbolicLink,
Socket,
}
impl FileType {
fn from_bytes(input: &[u8]) -> ParserResult<FileType> {
Ok(match input {
b"block" => FileType::BlockDevice,
b"char" => FileType::CharacterDevice,
b"dir" => FileType::Directory,
b"fifo" => FileType::Fifo,
b"file" => FileType::File,
b"link" => FileType::SymbolicLink,
b"socket" => FileType::Socket,
_ => {
return Err(format!(
r#""{}" is not a valid file type"#,
String::from_utf8_lossy(input)
)
.into());
}
})
}
fn as_str(&self) -> &'static str {
match self {
FileType::BlockDevice => "block",
FileType::CharacterDevice => "char",
FileType::Directory => "dir",
FileType::Fifo => "fifo",
FileType::File => "file",
FileType::SymbolicLink => "link",
FileType::Socket => "socket",
}
}
}
impl fmt::Display for FileType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[test]
fn test_type_from_bytes() {
for (input, res) in vec![
(&b"block"[..], FileType::BlockDevice),
(&b"char"[..], FileType::CharacterDevice),
(&b"dir"[..], FileType::Directory),
(&b"fifo"[..], FileType::Fifo),
(&b"file"[..], FileType::File),
(&b"link"[..], FileType::SymbolicLink),
(&b"socket"[..], FileType::Socket),
] {
assert_eq!(FileType::from_bytes(&input[..]), Ok(res));
}
assert!(FileType::from_bytes(&b"other"[..]).is_err());
}
bitflags::bitflags! {
pub struct Perms: u8 {
const READ = 0b100;
const WRITE = 0b010;
const EXECUTE = 0b001;
}
}
impl fmt::Display for Perms {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.contains(Perms::READ) {
f.write_str("r")?;
} else {
f.write_str("-")?;
}
if self.contains(Perms::WRITE) {
f.write_str("w")?;
} else {
f.write_str("-")?;
}
if self.contains(Perms::EXECUTE) {
f.write_str("x")?;
} else {
f.write_str("-")?;
}
Ok(())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FileMode {
pub setuid: bool,
pub setgid: bool,
pub owner: Perms,
pub group: Perms,
pub other: Perms,
}
impl FileMode {
fn from_bytes(input: &[u8]) -> ParserResult<FileMode> {
#[inline]
fn from_bytes_opt(mut input: &[u8]) -> Option<FileMode> {
let (setuid, setgid) = if input.len() == 4 {
let setid = input[0];
input = &input[1..];
(setid & 0b100 != 0, setid & 0b010 != 0)
} else {
(false, false)
};
if input.len() != 3 {
return None;
}
let owner = from_oct_ch(input[0])?;
let group = from_oct_ch(input[1])?;
let other = from_oct_ch(input[2])?;
Some(FileMode {
setuid,
setgid,
owner: Perms { bits: owner },
group: Perms { bits: group },
other: Perms { bits: other },
})
}
from_bytes_opt(input).ok_or_else(|| {
format!(
r#"mode value must be 3 octal chars, found "{}""#,
String::from_utf8_lossy(input)
)
.into()
})
}
}
impl fmt::Display for FileMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}{}", self.owner, self.group, self.other)
}
}
impl fmt::Octal for FileMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:o}{:o}{:o}", self.owner, self.group, self.other)
}
}
pub(crate) type ParserResult<T> = Result<T, ParserError>;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ParserError(pub String);
impl From<String> for ParserError {
fn from(s: String) -> ParserError {
ParserError(s)
}
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for ParserError {}