use crate::Utf16LeStr;
use crate::attribute::FileAttributeFlags;
use crate::err::{Error, Result};
use crate::utils::{read_u64, windows_filetime_to_timestamp};
use log::trace;
use byteorder::{LittleEndian, ReadBytesExt};
use jiff::Timestamp;
use num_traits::FromPrimitive;
use serde::Serialize;
use std::io::Cursor;
use winstructs::ntfs::mft_reference::MftReference;
#[derive(FromPrimitive, Serialize, Clone, Copy, Debug, PartialOrd, PartialEq)]
#[repr(u8)]
pub enum FileNamespace {
POSIX = 0,
Win32 = 1,
DOS = 2,
Win32AndDos = 3,
}
impl FileNamespace {
pub(crate) fn is_long(self) -> bool {
self != FileNamespace::DOS
}
}
#[derive(Serialize, Clone, Debug, PartialEq)]
pub struct FileNameAttr<'a> {
pub parent: MftReference,
#[serde(serialize_with = "crate::utils::serialize_timestamp")]
pub created: Timestamp,
#[serde(serialize_with = "crate::utils::serialize_timestamp")]
pub modified: Timestamp,
#[serde(serialize_with = "crate::utils::serialize_timestamp")]
pub mft_modified: Timestamp,
#[serde(serialize_with = "crate::utils::serialize_timestamp")]
pub accessed: Timestamp,
pub logical_size: u64,
pub physical_size: u64,
pub flags: FileAttributeFlags,
pub reparse_value: u32,
pub name_length: u8,
pub namespace: FileNamespace,
pub name: Utf16LeStr<'a>,
}
impl<'a> FileNameAttr<'a> {
pub fn from_slice(value: &'a [u8]) -> Result<FileNameAttr<'a>> {
let mut stream = Cursor::new(value);
trace!("Offset {}: FilenameAttr", stream.position());
let parent =
MftReference::from_reader(&mut stream).map_err(Error::failed_to_read_mft_reference)?;
let created =
crate::utils::windows_filetime_to_timestamp(stream.read_u64::<LittleEndian>()?)?;
let modified =
crate::utils::windows_filetime_to_timestamp(stream.read_u64::<LittleEndian>()?)?;
let mft_modified =
crate::utils::windows_filetime_to_timestamp(stream.read_u64::<LittleEndian>()?)?;
let accessed =
crate::utils::windows_filetime_to_timestamp(stream.read_u64::<LittleEndian>()?)?;
let physical_size = stream.read_u64::<LittleEndian>()?;
let logical_size = stream.read_u64::<LittleEndian>()?;
let flags = FileAttributeFlags::from_bits_retain(stream.read_u32::<LittleEndian>()?);
let reparse_value = stream.read_u32::<LittleEndian>()?;
let name_length = stream.read_u8()?;
let namespace = stream.read_u8()?;
let namespace =
FileNamespace::from_u8(namespace).ok_or(Error::UnknownNamespace { namespace })?;
let name_pos = stream.position() as usize;
let name_len_bytes = name_length as usize * 2;
let name_bytes = value
.get(name_pos..name_pos + name_len_bytes)
.ok_or(Error::InvalidFilename)?;
let name = Utf16LeStr::from_utf16le_bytes(name_bytes);
Ok(FileNameAttr {
parent,
created,
modified,
mft_modified,
accessed,
logical_size,
physical_size,
flags,
reparse_value,
name_length,
namespace,
name,
})
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct NameInParent<'a> {
pub(crate) parent: u64,
pub(crate) namespace: FileNamespace,
pub(crate) name: Utf16LeStr<'a>,
}
impl<'a> NameInParent<'a> {
pub(crate) fn from_slice(value: &'a [u8]) -> Option<NameInParent<'a>> {
let timestamps_valid = (0x08..0x28).step_by(8).all(|offset| {
read_u64(value, offset)
.is_some_and(|filetime| windows_filetime_to_timestamp(filetime).is_ok())
});
if !timestamps_valid {
return None;
}
let name_length = usize::from(*value.get(0x40)?);
let namespace = FileNamespace::from_u8(*value.get(0x41)?)?;
let name = value.get(0x42..0x42 + name_length * 2)?;
Some(NameInParent {
parent: MftReference::from(read_u64(value, 0)?).entry,
namespace,
name: Utf16LeStr::from_utf16le_bytes(name),
})
}
}
#[cfg(test)]
mod tests {
use super::{FileNameAttr, FileNamespace, NameInParent};
use crate::entry::tests::filename_value_bytes;
#[test]
fn name_in_parent_accepts_what_file_name_attr_accepts() {
let assert_agree = |value: &[u8]| {
let name = NameInParent::from_slice(value).map(|n| (n.parent, n.namespace, n.name));
let full = FileNameAttr::from_slice(value)
.ok()
.map(|f| (f.parent.entry, f.namespace, f.name));
assert_eq!(name, full, "value {value:02X?}");
};
let log_file = filename_value_bytes("$LogFile", FileNamespace::Win32AndDos);
assert!(NameInParent::from_slice(&log_file).is_some());
for len in 0..=log_file.len() {
assert_agree(&log_file[..len]);
}
for position in 0..log_file.len() {
for byte in [0x00, 0x01, 0x04, 0x7F, 0x80, 0xFF] {
let mut value = log_file.clone();
value[position] = byte;
assert_agree(&value);
}
}
}
#[test]
fn flags_keep_unknown_bits() {
let mut value = vec![0u8; 66];
value[0x38..0x3C].copy_from_slice(&0x8000_0006u32.to_le_bytes());
let attr = FileNameAttr::from_slice(&value).unwrap();
assert_eq!(attr.flags.bits(), 0x8000_0006);
}
}