use super::*;
pub fn name_eq<'a>(desc: &Descriptor, path: &'a [u8]) -> Option<&'a [u8]> {
let name = desc.name();
let mut i = 0;
loop {
if name.len() == i {
if path.len() == i {
break Some(&path[i..]);
}
if path[i] == b'/' || path[i] == b'\\' {
break Some(&path[i + 1..]);
}
break None;
}
if path.len() == i || name[i] != path[i] {
break None;
}
i += 1;
}
}
#[inline]
pub fn next_sibling(desc: &Descriptor, i: usize, end: usize) -> usize {
assert!(i < end, "index out of range");
if desc.is_dir() {
let max_size = end - (i + 1);
let min_size = cmp::min(max_size, desc.content_size as usize);
i + 1 + min_size
}
else {
i + 1
}
}
#[inline]
pub fn find_desc<'a>(dir: &'a [Descriptor], path: &[u8]) -> Option<&'a Descriptor> {
find(dir, path).get(0)
}
#[inline]
pub fn find_dir<'a>(dir: &'a [Descriptor], path: &[u8]) -> Option<&'a [Descriptor]> {
if path.len() == 0 {
Some(dir)
}
else {
find(dir, path).get(1..)
}
}
pub fn find<'a>(dir: &'a [Descriptor], mut path: &[u8]) -> &'a [Descriptor] {
if path.len() == 0 {
return &dir[..0];
}
let mut i = 0;
let mut end = dir.len();
while i < end {
let desc = &dir[i];
let next_i = next_sibling(desc, i, end);
if let Some(tail) = name_eq(desc, path) {
if tail.len() == 0 {
return &dir[i..next_i];
}
if desc.is_dir() {
path = tail;
i = i + 1;
end = next_i;
continue;
}
}
i = next_i;
}
return &dir[..0];
}
pub fn find_encrypted(encrypted_dir: &[Descriptor], mut path: &[u8], section: &Section, key: &Key) -> Option<Descriptor> {
if path.len() == 0 {
return None;
}
let mut i = 0;
let mut end = encrypted_dir.len();
while i < end {
let desc = crypt::decrypt_desc_unchecked(&encrypted_dir[i], section, i * Descriptor::BLOCKS_LEN, key);
let next_i = next_sibling(&desc, i, end);
if let Some(tail) = name_eq(&desc, path) {
if tail.len() == 0 {
return Some(desc);
}
if desc.is_dir() {
path = tail;
i = i + 1;
end = next_i;
continue;
}
}
i = next_i;
}
return None;
}
#[derive(Copy, Clone, Debug)]
pub struct TreeArt<'a> {
pub margin_entry: &'a str,
pub margin_last: &'a str,
pub dir_entry: &'a str,
pub dir_last: &'a str,
pub file_entry: &'a str,
pub file_last: &'a str,
}
impl TreeArt<'static> {
pub const ASCII: TreeArt<'static> = TreeArt {
margin_entry: "| ",
margin_last: " ",
dir_entry: "+- ",
dir_last: "`- ",
file_entry: "| ",
file_last: "` ",
};
pub const UNICODE: TreeArt<'static> = TreeArt {
margin_entry: "│ ",
margin_last: " ",
dir_entry: "├─ 📁 ",
dir_last: "└─ 📁 ",
file_entry: "│ ",
file_last: "└ ",
};
}
pub struct DirFmt<'a> {
root: &'a str,
dir: &'a [Descriptor],
art: &'a TreeArt<'static>,
}
impl<'a> DirFmt<'a> {
#[inline]
pub const fn new(root: &'a str, dir: &'a [Descriptor], art: &'a TreeArt<'static>) -> DirFmt<'a> {
DirFmt { root, dir, art }
}
}
impl<'a> fmt::Display for DirFmt<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.root)?;
f.write_str(if self.root.ends_with("/") { "\n" } else { "/\n" })?;
fmt_rec(f, 0, 0, self.dir, self.art)
}
}
fn fmt_margin<W: fmt::Write>(f: &mut W, margin: u32, depth: u32, art: &TreeArt) -> fmt::Result {
for is_last in (0..depth).map(|i| margin & 1 << i != 0) {
let s = if is_last { art.margin_last } else { art.margin_entry };
f.write_str(s)?;
}
Ok(())
}
fn fmt_rec<W: fmt::Write>(f: &mut W, margin: u32, depth: u32, dir: &[Descriptor], art: &TreeArt) -> fmt::Result {
if depth >= 31 {
return Ok(());
}
let mut was_dir = false;
let mut i = 0;
while i < dir.len() {
let desc = &dir[i];
if i != 0 && (desc.is_dir() || was_dir) {
fmt_margin(f, margin, depth + 1, art)?;
f.write_str("\n")?;
}
was_dir = desc.is_dir();
fmt_margin(f, margin, depth, art)?;
let next_i = next_sibling(desc, i, dir.len());
let is_last = dir.len() == next_i;
let prefix = match (is_last, desc.is_dir()) {
(true, true) => art.dir_last,
(true, false) => art.file_last,
(false, true) => art.dir_entry,
(false, false) => art.file_entry,
};
f.write_str(prefix)?;
match str::from_utf8(desc.name()) {
Ok(name) => f.write_str(name),
Err(_) => f.write_str("err"),
}?;
if desc.is_dir() {
f.write_str("/\n")?;
let new_margin = margin | (is_last as u32) << depth;
fmt_rec(f, new_margin, depth + 1, &dir[i + 1..next_i], art)?;
}
else {
f.write_str("\n")?;
}
i = next_i;
}
Ok(())
}
fn dir_inc(dir: &mut Vec<Descriptor>, path: &mut &[u8], inc: i32) -> usize {
let mut i = 0;
let mut end = dir.len();
while i < end {
let desc = &mut dir[i];
let next_i = next_sibling(desc, i, end);
if let Some(tail) = name_eq(desc, *path) {
if tail.len() == 0 {
*path = tail;
return i;
}
if desc.is_dir() {
desc.content_size = (desc.content_size as i32 + inc) as u32;
*path = tail;
i = i + 1;
end = next_i;
continue;
}
else {
return i;
}
}
i = next_i;
}
return i;
}
fn flenck(path: &[u8]) -> i32 {
let mut components = 0;
for i in 0..path.len() {
if path[i] == b'/' || path[i] == b'\\' {
components += 1;
if i + 1 == path.len() {
return components;
}
}
}
return components + 1;
}
pub fn create<'a>(dir: &'a mut Vec<Descriptor>, path: &[u8]) -> &'a mut Descriptor {
let mut tail = path;
let i = dir_inc(dir, &mut tail, 0);
if tail.is_empty() {
return &mut dir[i];
}
let inc = flenck(tail) as usize;
tail = path;
let _check = dir_inc(dir, &mut tail, inc as i32);
debug_assert_eq!(i, _check);
let mut dir_len = inc as u32;
let _ = dir.splice(i..i, std::iter::repeat_with(|| {
let mut k = 0;
while k < tail.len() && tail[k] != b'/' && tail[k] != b'\\' {
k += 1;
}
dir_len -= 1;
let dir_name = &tail[..k];
tail = &tail[if k == tail.len() { k } else { k + 1 }..];
Descriptor::dir(dir_name, dir_len)
}).take(inc));
return &mut dir[i + inc - 1];
}
pub fn remove(dir: &mut Vec<Descriptor>, path: &[u8]) -> Option<Descriptor> {
let mut temp = path;
let i = dir_inc(dir, &mut temp, 0);
if i >= dir.len() {
return None;
}
temp = path;
let _check = dir_inc(dir, &mut temp, -1);
debug_assert_eq!(i, _check);
Some(dir.remove(i))
}
pub fn fsck(dir: &[Descriptor], high_mark: u32, log: &mut dyn fmt::Write) -> bool {
fsck_rec(dir, high_mark, None, log)
}
struct FsckParents<'a> {
desc: &'a Descriptor,
parents: Option<&'a FsckParents<'a>>,
}
fn fsck_rec(dir: &[Descriptor], high_mark: u32, parents: Option<&FsckParents>, log: &mut dyn fmt::Write) -> bool {
let mut success = true;
let mut i = 0;
while i < dir.len() {
let desc = &dir[i];
i += 1;
if desc.name.buffer[NAME_BUF_LEN - 1] >= NAME_BUF_LEN as u8 {
fsck_error(desc, parents, log, format_args!("invalid name length ({})", desc.name.buffer[NAME_BUF_LEN - 1]));
success = false;
}
if let Err(err) = str::from_utf8(desc.name()) {
fsck_error(desc, parents, log, format_args!("invalid name ({})", err));
success = false;
}
if desc.is_file() {
if desc.section.offset < Header::BLOCKS_LEN as u32 {
fsck_error(desc, parents, log, format_args!("invalid file section (offset={}, size={}): overlaps the header", desc.section.offset, desc.section.size));
success = false;
}
if desc.section.size > high_mark {
fsck_error(desc, parents, log, format_args!("invalid file section (offset={}, size={}): size too large", desc.section.offset, desc.section.size));
success = false;
}
if desc.section.offset > high_mark.saturating_sub(desc.section.size) {
fsck_error(desc, parents, log, format_args!("invalid file section (offset={}, size={}): overlaps the directory", desc.section.offset, desc.section.size));
success = false;
}
if bytes2blocks(desc.content_size) > desc.section.size {
fsck_error(desc, parents, log, format_args!("invalid content size ({}, offset={}, size={}): larger than its section", desc.content_size, desc.section.offset, desc.section.size));
success = false;
}
}
else {
let max_len = dir.len() - i;
if desc.content_size as usize > max_len {
fsck_error(desc, parents, log, format_args!("invalid directory: too many children ({}, max={})", desc.content_size, max_len));
success = false;
break;
}
let children = &dir[i..i + desc.content_size as usize];
if !fsck_rec(children, high_mark, Some(&FsckParents { desc, parents }), log) {
success = false;
}
i += desc.content_size as usize;
}
}
return success;
}
#[inline(never)]
fn fsck_error(desc: &Descriptor, parents: Option<&FsckParents>, log: &mut dyn fmt::Write, args: fmt::Arguments) {
fn print_parents(parents: Option<&FsckParents>, log: &mut dyn fmt::Write) {
if let Some(parents) = parents {
print_parents(parents.parents, log);
let _ = log.write_str("/");
let _ = log.write_str(String::from_utf8_lossy(parents.desc.name()).as_ref());
}
}
print_parents(Some(&FsckParents { desc, parents }), log);
let _ = log.write_str(": ");
let _ = log.write_fmt(args);
let _ = log.write_str("\n");
}
#[cfg(test)]
mod tests;