use super::io::{self, Read, Write};
use bytemuck::Zeroable;
use super::io::LogicalSector;
use crate::types::{U16LsbMsb, U32LsbMsb};
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DirectoryRecordHeader {
pub len: u8,
pub extended_attr_record: u8,
pub extent: U32LsbMsb,
pub data_len: U32LsbMsb,
pub date_time: DirDateTime,
pub flags: u8,
pub file_unit_size: u8,
pub interleave_gap_size: u8,
pub volume_sequence_number: U16LsbMsb,
pub file_identifier_len: u8,
}
impl Default for DirectoryRecordHeader {
fn default() -> Self {
Self {
len: 0,
extended_attr_record: 0,
extent: U32LsbMsb::new(0),
data_len: U32LsbMsb::new(0),
date_time: DirDateTime::now(),
flags: 0,
file_unit_size: 0,
interleave_gap_size: 0,
volume_sequence_number: U16LsbMsb::new(0),
file_identifier_len: 0,
}
}
}
impl DirectoryRecordHeader {
pub fn from_bytes(bytes: &[u8]) -> &Self {
bytemuck::from_bytes(bytes)
}
pub fn to_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
pub fn is_directory(&self) -> bool {
FileFlags::from_bits_retain(self.flags).contains(FileFlags::DIRECTORY)
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct DirectoryRecord {
data: [u8; 256],
}
impl Default for DirectoryRecord {
fn default() -> Self {
bytemuck::Zeroable::zeroed()
}
}
#[derive(Debug, Clone, Copy)]
pub struct NotADirectoryError;
impl core::fmt::Display for NotADirectoryError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "not a directory")
}
}
#[cfg(feature = "std")]
impl std::error::Error for NotADirectoryError {}
impl DirectoryRecord {
const DATA_START: usize = size_of::<DirectoryRecordHeader>();
#[inline]
pub fn header(&self) -> &DirectoryRecordHeader {
bytemuck::from_bytes(&self.data[0..Self::DATA_START])
}
#[inline]
pub fn header_mut(&mut self) -> &mut DirectoryRecordHeader {
bytemuck::from_bytes_mut(&mut self.data[0..size_of::<DirectoryRecordHeader>()])
}
#[inline]
pub fn name(&self) -> &[u8] {
let len = self.header().file_identifier_len as usize;
&self.data[Self::DATA_START..Self::DATA_START + len]
}
#[cfg(feature = "alloc")]
pub fn joliet_name(&self) -> alloc::string::String {
crate::joliet::decode_joliet_name(self.name())
}
#[cfg(feature = "alloc")]
pub fn is_joliet_name(&self) -> bool {
crate::joliet::is_likely_joliet_name(self.name())
}
#[inline]
pub fn system_use(&self) -> &[u8] {
let header = self.header();
let su_start = (Self::DATA_START + header.file_identifier_len as usize + 1) & !1;
if su_start >= header.len as usize {
return &[];
}
&self.data[su_start..header.len as usize]
}
#[inline]
pub fn is_special(&self) -> bool {
self.name() == b"\x00" || self.name() == b"\x01"
}
#[inline]
pub fn is_directory(&self) -> bool {
self.header().is_directory()
}
pub fn is_file(&self) -> bool {
!self.header().is_directory()
}
pub fn as_dir_ref(&self) -> Result<DirectoryRef, NotADirectoryError> {
if !self.is_directory() {
return Err(NotADirectoryError);
}
let header = self.header();
Ok(DirectoryRef {
extent: LogicalSector(header.extent.read() as usize),
size: header.data_len.read() as usize,
})
}
pub fn size(&self) -> usize {
self.header().len as usize
}
pub fn to_bytes(&self) -> &[u8] {
&self.data[0..self.size()]
}
pub fn new(name: &[u8], system_use: &[u8], directory: DirectoryRef, flags: FileFlags) -> Self {
let mut sel = Self::zeroed();
let su_start = (Self::DATA_START + name.len() + 1) & !1;
let total = su_start + system_use.len();
let record_len = (total + 1) & !1;
debug_assert!(
record_len <= 255,
"DirectoryRecord too large: {} bytes (name={}, su={})",
record_len,
name.len(),
system_use.len()
);
*sel.header_mut() = DirectoryRecordHeader {
len: record_len as u8,
extended_attr_record: 0,
extent: U32LsbMsb::new(directory.extent.0 as u32),
data_len: U32LsbMsb::new(directory.size as u32),
date_time: DirDateTime::now(),
flags: flags.bits(),
file_unit_size: 0,
interleave_gap_size: 0,
volume_sequence_number: U16LsbMsb::new(1),
file_identifier_len: name.len() as u8,
};
sel.data[Self::DATA_START..Self::DATA_START + name.len()].copy_from_slice(name);
sel.data[su_start..su_start + system_use.len()].copy_from_slice(system_use);
sel
}
pub fn with_len(name_len: usize, su_len: usize) -> Self {
let mut sel = Self::zeroed();
let su_start = (Self::DATA_START + name_len + 1) & !1;
let total = su_start + su_len;
let record_len = (total + 1) & !1;
sel.header_mut().len = record_len as u8;
sel
}
}
io_transform! {
impl DirectoryRecord {
pub async fn parse<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut sel = Self::zeroed();
reader.read_exact(&mut sel.data[0..Self::DATA_START]).await?;
let size = sel.size();
if size > Self::DATA_START {
reader.read_exact(&mut sel.data[Self::DATA_START..size]).await?;
}
Ok(sel)
}
pub async fn write<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
let size = self.size();
writer.write_all(&self.data[0..size]).await?;
Ok(size)
}
}
}
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RootDirectoryEntry {
pub header: DirectoryRecordHeader,
pub padding: u8,
}
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DirDateTime {
year: u8,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
offset: u8,
}
impl DirDateTime {
#[cfg(feature = "std")]
pub fn now() -> Self {
use chrono::{Datelike, Timelike, Utc};
let now = Utc::now();
Self {
year: (now.year() - 1900) as u8,
month: now.month() as u8,
day: now.day() as u8,
hour: now.hour() as u8,
minute: now.minute() as u8,
second: now.second() as u8,
offset: 0,
}
}
#[cfg(not(feature = "std"))]
pub fn now() -> Self {
Self::default()
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct DirectoryRef {
pub extent: LogicalSector,
pub size: usize,
}
bitflags::bitflags! {
#[derive(Clone, Copy)]
pub struct FileFlags: u8 {
const HIDDEN = 0b0000_0001;
const DIRECTORY = 0b0000_0010;
const ASSOCIATED_FILE = 0b0000_0100;
const EXTENDED_ATTRIBUTES = 0b0000_1000;
const EXTENDED_PERMISSIONS = 0b0001_0000;
const NOT_FINAL = 0b1000_0000;
}
}