wxtla 0.3.1

Wired eXploring Target Layer Accessor
Documentation
use super::{
  constants::XFS_MAX_INODE_NUMBER,
  io::{be_u16, be_u32, be_u64, read_slice},
};
use crate::{Error, Result};

#[derive(Clone, Debug)]
pub(crate) struct XfsDirEntry {
  pub(crate) name: String,
  pub(crate) inode_number: u64,
}

pub(crate) fn parse_shortform_directory(data: &[u8], has_ftype: bool) -> Result<Vec<XfsDirEntry>> {
  if data.len() < 2 {
    return Err(Error::invalid_format(
      "xfs shortform directory is too small".to_string(),
    ));
  }

  let count_32 = data[0] as usize;
  let count_64 = data[1] as usize;
  if count_32 != 0 && count_64 != 0 {
    return Err(Error::invalid_format(
      "xfs shortform directory mixes 32-bit and 64-bit entry counters".to_string(),
    ));
  }

  let (entry_count, inode_size, mut offset) = if count_64 == 0 {
    (count_32, 4usize, 6usize)
  } else {
    (count_64, 8usize, 10usize)
  };

  let mut entries = Vec::with_capacity(entry_count);
  for _ in 0..entry_count {
    if offset >= data.len() {
      return Err(Error::invalid_format(
        "xfs shortform directory entry is out of bounds".to_string(),
      ));
    }

    let name_len = data[offset] as usize;
    let mut entry_size = 3usize
      .checked_add(name_len)
      .ok_or_else(|| Error::invalid_range("xfs shortform entry size overflow"))?;
    if has_ftype {
      entry_size = entry_size
        .checked_add(1)
        .ok_or_else(|| Error::invalid_range("xfs shortform entry size overflow"))?;
    }
    entry_size = entry_size
      .checked_add(inode_size)
      .ok_or_else(|| Error::invalid_range("xfs shortform entry size overflow"))?;

    let _ = read_slice(data, offset, entry_size)?;
    offset += 1;
    offset += 2;

    let name = String::from_utf8_lossy(read_slice(data, offset, name_len)?).to_string();
    offset += name_len;

    if has_ftype {
      offset += 1;
    }

    let inode_number = if inode_size == 4 {
      u64::from(be_u32(read_slice(data, offset, 4)?))
    } else {
      be_u64(read_slice(data, offset, 8)?)
    } & XFS_MAX_INODE_NUMBER;
    offset += inode_size;

    if name != "." && name != ".." {
      entries.push(XfsDirEntry { name, inode_number });
    }
  }

  Ok(entries)
}

pub(crate) fn parse_block_directory(
  data: &[u8], has_ftype: bool, out: &mut Vec<XfsDirEntry>,
) -> Result<()> {
  let signature = read_slice(data, 0, 4)?;
  let (header_size, has_footer) = match signature {
    b"XD2B" | b"XD2D" => (16usize, signature == b"XD2B"),
    b"XDB3" | b"XDD3" => (64usize, signature == b"XDB3"),
    b"XD2L" | b"XD2N" | b"XD2F" | b"XDL3" | b"XDN3" | b"XDF3" => return Ok(()),
    _ => {
      return Err(Error::invalid_format(format!(
        "unsupported xfs directory block signature: {:02x}{:02x}{:02x}{:02x}",
        data[0], data[1], data[2], data[3]
      )));
    }
  };

  let entries_end = if has_footer {
    let footer = read_slice(data, data.len() - 8, 8)?;
    let nentries = be_u32(&footer[0..4]) as usize;
    let hash_size = nentries
      .checked_mul(8)
      .ok_or_else(|| Error::invalid_range("xfs directory hash table overflow"))?;
    data
      .len()
      .checked_sub(8 + hash_size)
      .ok_or_else(|| Error::invalid_format("invalid xfs directory hash region"))?
  } else {
    data.len()
  };

  let mut offset = header_size;
  while offset < entries_end {
    let header = read_slice(data, offset, 4)?;
    if be_u16(&header[0..2]) == 0xFFFF {
      let size = usize::from(be_u16(&header[2..4]));
      if size < 4 {
        return Err(Error::invalid_format(
          "invalid xfs free directory region size".to_string(),
        ));
      }
      offset += size;
      continue;
    }

    let inode_number = be_u64(read_slice(data, offset, 8)?) & XFS_MAX_INODE_NUMBER;
    let name_len = data[offset + 8] as usize;

    let mut entry_size = 9usize
      .checked_add(name_len)
      .and_then(|value| value.checked_add(2))
      .ok_or_else(|| Error::invalid_range("xfs directory entry size overflow"))?;
    if has_ftype {
      entry_size += 1;
    }
    let padding = entry_size % 8;
    if padding != 0 {
      entry_size += 8 - padding;
    }

    let name = String::from_utf8_lossy(read_slice(data, offset + 9, name_len)?).to_string();
    if name != "." && name != ".." {
      out.push(XfsDirEntry { name, inode_number });
    }
    offset += entry_size;
  }

  Ok(())
}