Skip to main content

forensic_mount/
types.rs

1#![forbid(unsafe_code)]
2
3//! The filesystem-agnostic value types the FUSE/Dokan mount layer speaks.
4//!
5//! These are 4n6mount's own FUSE-facing vocabulary — a small, `u64`-inode,
6//! serde-friendly model that the mount callbacks (`getattr`/`readdir`/`read`/…)
7//! consume directly. A concrete backend (the memory VFS, or the disk-image
8//! [`EngineFs`](crate::EngineFs) adapter over `forensic-vfs`) converts its native
9//! representation into these types via the [`ForensicFs`](crate::ForensicFs)
10//! trait.
11
12use serde::{Deserialize, Serialize};
13use std::fmt;
14
15/// Filesystem-agnostic file type.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum FsFileType {
18    RegularFile,
19    Directory,
20    Symlink,
21    CharDevice,
22    BlockDevice,
23    Fifo,
24    Socket,
25    Unknown,
26}
27
28/// Filesystem-agnostic timestamp.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
30pub struct FsTimestamp {
31    pub seconds: i64,
32    pub nanoseconds: u32,
33}
34
35/// Filesystem-agnostic file metadata.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct FsMetadata {
38    pub ino: u64,
39    pub file_type: FsFileType,
40    pub mode: u16,
41    pub uid: u32,
42    pub gid: u32,
43    pub size: u64,
44    pub links_count: u16,
45    pub atime: FsTimestamp,
46    pub mtime: FsTimestamp,
47    pub ctime: FsTimestamp,
48    pub crtime: FsTimestamp,
49    pub allocated: bool,
50}
51
52/// Filesystem-agnostic directory entry.
53#[derive(Debug, Clone)]
54pub struct FsDirEntry {
55    pub inode: u64,
56    pub name: Vec<u8>,
57    pub file_type: FsFileType,
58}
59
60impl FsDirEntry {
61    pub fn name_str(&self) -> String {
62        String::from_utf8_lossy(&self.name).to_string()
63    }
64}
65
66/// Deleted inode information.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct FsDeletedInode {
69    pub ino: u64,
70    pub file_type: FsFileType,
71    pub size: u64,
72    pub dtime: u32,
73    pub recoverability: f64,
74}
75
76/// Name/metadata-layer allocation status of a recovered node. `Allocated`
77/// never appears here (a recovered node is unlinked by definition).
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79pub enum FsAllocation {
80    /// The record is unlinked but its parent is still known.
81    Deleted,
82    /// The parent link is gone — a true orphan.
83    Orphan,
84}
85
86/// A recovered deleted (or orphaned) node carrying the identity a consumer
87/// needs to render *and* read it: a readable inode, the recovered name, the
88/// parent inode (or `None` for an orphan), the metadata record id, and MACB
89/// times. Unlike [`FsDeletedInode`] (bare inode + size), this is the rich
90/// surface backing in-place vs `$Orphans` placement — the name is **never
91/// fabricated** (empty when the filesystem destroyed it on delete).
92#[derive(Debug, Clone)]
93pub struct FsDeletedNode {
94    /// Readable inode — usable with `read_file` / `read_file_range`.
95    pub ino: u64,
96    /// Recovered name; may be empty/partial, never fabricated.
97    pub name: Vec<u8>,
98    /// Parent directory inode, or `None` for an orphan.
99    pub parent_ino: Option<u64>,
100    pub size: u64,
101    pub file_type: FsFileType,
102    pub allocation: FsAllocation,
103    /// Metadata address (MFT entry / inode number) — the stable disambiguator.
104    pub record_id: u64,
105    pub atime: FsTimestamp,
106    pub mtime: FsTimestamp,
107    pub ctime: FsTimestamp,
108    pub crtime: FsTimestamp,
109}
110
111/// Result of attempting to recover a deleted file.
112#[derive(Debug, Clone)]
113pub struct FsRecoveryResult {
114    pub ino: u64,
115    pub data: Vec<u8>,
116    pub expected_size: u64,
117    pub recovered_bytes: u64,
118    pub recovery_percentage: f64,
119}
120
121/// A timeline event.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct FsTimelineEvent {
124    pub timestamp: FsTimestamp,
125    pub event_type: FsEventType,
126    pub inode: u64,
127    pub size: u64,
128    pub uid: u32,
129    pub gid: u32,
130}
131
132/// Type of filesystem event.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134pub enum FsEventType {
135    Created,
136    Modified,
137    Accessed,
138    Changed,
139    Deleted,
140    Mounted,
141}
142
143/// A contiguous range of blocks.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct FsBlockRange {
146    pub start: u64,
147    pub length: u64,
148}
149
150/// A journal transaction.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct FsTransaction {
153    pub sequence: u64,
154    pub commit_seconds: u64,
155    pub commit_nanoseconds: u32,
156}
157
158/// Error type for `ForensicFs` operations.
159#[derive(Debug)]
160pub enum FsError {
161    Io(std::io::Error),
162    NotSupported(String),
163    NotFound(String),
164    Corrupt(String),
165    Other(String),
166}
167
168impl fmt::Display for FsError {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            Self::Io(e) => write!(f, "I/O error: {e}"),
172            Self::NotSupported(msg) => write!(f, "not supported: {msg}"),
173            Self::NotFound(msg) => write!(f, "not found: {msg}"),
174            Self::Corrupt(msg) => write!(f, "corrupt: {msg}"),
175            Self::Other(msg) => write!(f, "{msg}"),
176        }
177    }
178}
179
180impl std::error::Error for FsError {
181    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
182        match self {
183            Self::Io(e) => Some(e),
184            _ => None,
185        }
186    }
187}
188
189impl From<std::io::Error> for FsError {
190    fn from(e: std::io::Error) -> Self {
191        Self::Io(e)
192    }
193}
194
195/// Convenience alias.
196pub type FsResult<T> = std::result::Result<T, FsError>;
197
198/// Helper to create a "not supported" error.
199pub fn not_supported(op: &str) -> FsError {
200    FsError::NotSupported(op.to_string())
201}
202
203/// Helper to create a "not found" error.
204pub fn not_found(what: &str) -> FsError {
205    FsError::NotFound(what.to_string())
206}