Skip to main content

strate_fs_abstraction/
types.rs

1//! Common VFS data types.
2//!
3//! This module defines the core data structures used across the VFS layer,
4//! including file information, directory entries, and volume information.
5
6#[cfg(feature = "alloc")]
7extern crate alloc;
8
9#[cfg(feature = "alloc")]
10use alloc::string::String;
11#[cfg(feature = "std")]
12use std::time::SystemTime;
13
14/// File type enumeration.
15///
16/// Maps to both Unix mode bits and Windows file attributes.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[repr(u8)]
19pub enum VfsFileType {
20    /// Regular file
21    RegularFile = 1,
22    /// Directory
23    Directory = 2,
24    /// Symbolic link
25    Symlink = 3,
26    /// Block device
27    BlockDevice = 4,
28    /// Character device
29    CharDevice = 5,
30    /// Named pipe (FIFO)
31    Fifo = 6,
32    /// Unix domain socket
33    Socket = 7,
34    /// Unknown or unsupported type
35    Unknown = 0,
36}
37
38impl VfsFileType {
39    /// Create from Unix mode bits (S_IFMT field).
40    pub const fn from_mode(mode: u32) -> Self {
41        match mode & 0o170000 {
42            0o100000 => VfsFileType::RegularFile,
43            0o040000 => VfsFileType::Directory,
44            0o120000 => VfsFileType::Symlink,
45            0o060000 => VfsFileType::BlockDevice,
46            0o020000 => VfsFileType::CharDevice,
47            0o010000 => VfsFileType::Fifo,
48            0o140000 => VfsFileType::Socket,
49            _ => VfsFileType::Unknown,
50        }
51    }
52
53    /// Convert to Unix mode bits (S_IFMT field).
54    pub const fn to_mode_bits(self) -> u32 {
55        match self {
56            VfsFileType::RegularFile => 0o100000,
57            VfsFileType::Directory => 0o040000,
58            VfsFileType::Symlink => 0o120000,
59            VfsFileType::BlockDevice => 0o060000,
60            VfsFileType::CharDevice => 0o020000,
61            VfsFileType::Fifo => 0o010000,
62            VfsFileType::Socket => 0o140000,
63            VfsFileType::Unknown => 0,
64        }
65    }
66
67    /// Check if this is a regular file.
68    pub const fn is_file(self) -> bool {
69        matches!(self, VfsFileType::RegularFile)
70    }
71
72    /// Check if this is a directory.
73    pub const fn is_dir(self) -> bool {
74        matches!(self, VfsFileType::Directory)
75    }
76
77    /// Check if this is a symbolic link.
78    pub const fn is_symlink(self) -> bool {
79        matches!(self, VfsFileType::Symlink)
80    }
81}
82
83impl Default for VfsFileType {
84    /// Implements default.
85    fn default() -> Self {
86        VfsFileType::Unknown
87    }
88}
89
90/// Unix timestamp representation (seconds since epoch).
91///
92/// Used when `std` feature is not available.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
94pub struct VfsTimestamp {
95    /// Seconds since Unix epoch (1970-01-01 00:00:00 UTC)
96    pub secs: i64,
97    /// Nanoseconds (0-999_999_999)
98    pub nsecs: u32,
99}
100
101impl VfsTimestamp {
102    /// Create a new timestamp.
103    pub const fn new(secs: i64, nsecs: u32) -> Self {
104        Self { secs, nsecs }
105    }
106
107    /// Create from seconds only.
108    pub const fn from_secs(secs: i64) -> Self {
109        Self { secs, nsecs: 0 }
110    }
111
112    /// Convert to Windows FILETIME (100-nanosecond intervals since 1601-01-01).
113    pub const fn to_filetime(&self) -> u64 {
114        // Windows epoch is 11644473600 seconds before Unix epoch
115        const WINDOWS_EPOCH_OFFSET: i64 = 11644473600;
116        let windows_secs = self.secs + WINDOWS_EPOCH_OFFSET;
117        if windows_secs < 0 {
118            return 0;
119        }
120        (windows_secs as u64) * 10_000_000 + (self.nsecs / 100) as u64
121    }
122
123    /// Create from Windows FILETIME.
124    pub const fn from_filetime(ft: u64) -> Self {
125        const WINDOWS_EPOCH_OFFSET: i64 = 11644473600;
126        let secs = (ft / 10_000_000) as i64 - WINDOWS_EPOCH_OFFSET;
127        let nsecs = ((ft % 10_000_000) * 100) as u32;
128        Self { secs, nsecs }
129    }
130}
131
132#[cfg(feature = "std")]
133impl From<SystemTime> for VfsTimestamp {
134    /// Implements from.
135    fn from(time: SystemTime) -> Self {
136        match time.duration_since(SystemTime::UNIX_EPOCH) {
137            Ok(duration) => Self {
138                secs: duration.as_secs() as i64,
139                nsecs: duration.subsec_nanos(),
140            },
141            Err(e) => {
142                let duration = e.duration();
143                Self {
144                    secs: -(duration.as_secs() as i64),
145                    nsecs: duration.subsec_nanos(),
146                }
147            }
148        }
149    }
150}
151
152#[cfg(feature = "std")]
153impl From<VfsTimestamp> for SystemTime {
154    /// Implements from.
155    fn from(ts: VfsTimestamp) -> Self {
156        use std::time::Duration;
157        if ts.secs >= 0 {
158            SystemTime::UNIX_EPOCH + Duration::new(ts.secs as u64, ts.nsecs)
159        } else {
160            SystemTime::UNIX_EPOCH - Duration::new((-ts.secs) as u64, ts.nsecs)
161        }
162    }
163}
164
165/// File metadata structure.
166///
167/// Contains all metadata about a file or directory that can be
168/// retrieved without reading file contents.
169#[derive(Debug, Clone)]
170pub struct VfsFileInfo {
171    /// Inode number (unique file identifier within filesystem)
172    pub ino: u64,
173    /// File size in bytes
174    pub size: u64,
175    /// Number of 512-byte blocks allocated
176    pub blocks: u64,
177    /// Preferred I/O block size
178    pub block_size: u32,
179    /// Unix permission mode (including file type bits)
180    pub mode: u32,
181    /// File type (derived from mode, but cached for convenience)
182    pub file_type: VfsFileType,
183    /// Number of hard links
184    pub nlink: u32,
185    /// Owner user ID
186    pub uid: u32,
187    /// Owner group ID
188    pub gid: u32,
189    /// Device ID (for device files)
190    pub rdev: u64,
191    /// Last access time
192    pub atime: VfsTimestamp,
193    /// Last modification time
194    pub mtime: VfsTimestamp,
195    /// Last status change time (inode change)
196    pub ctime: VfsTimestamp,
197    /// Creation/birth time (if supported by filesystem)
198    pub crtime: Option<VfsTimestamp>,
199}
200
201impl VfsFileInfo {
202    /// Check if this is a regular file.
203    pub const fn is_file(&self) -> bool {
204        self.file_type.is_file()
205    }
206
207    /// Check if this is a directory.
208    pub const fn is_dir(&self) -> bool {
209        self.file_type.is_dir()
210    }
211
212    /// Check if this is a symbolic link.
213    pub const fn is_symlink(&self) -> bool {
214        self.file_type.is_symlink()
215    }
216
217    /// Get Unix permission bits only (without file type).
218    pub const fn permissions(&self) -> u32 {
219        self.mode & 0o7777
220    }
221}
222
223impl Default for VfsFileInfo {
224    /// Implements default.
225    fn default() -> Self {
226        Self {
227            ino: 0,
228            size: 0,
229            blocks: 0,
230            block_size: 4096,
231            mode: 0,
232            file_type: VfsFileType::Unknown,
233            nlink: 1,
234            uid: 0,
235            gid: 0,
236            rdev: 0,
237            atime: VfsTimestamp::default(),
238            mtime: VfsTimestamp::default(),
239            ctime: VfsTimestamp::default(),
240            crtime: None,
241        }
242    }
243}
244
245/// Directory entry structure.
246///
247/// Represents a single entry within a directory.
248#[derive(Debug, Clone)]
249#[cfg(feature = "alloc")]
250pub struct VfsDirEntry {
251    /// Entry name (filename only, not full path)
252    pub name: String,
253    /// Inode number of the target file/directory
254    pub ino: u64,
255    /// File type (may be Unknown if filesystem doesn't store it)
256    pub file_type: VfsFileType,
257    /// Offset cookie for readdir continuation
258    pub offset: u64,
259}
260
261#[cfg(feature = "alloc")]
262impl VfsDirEntry {
263    /// Create a new directory entry.
264    pub fn new(name: impl Into<String>, ino: u64, file_type: VfsFileType) -> Self {
265        Self {
266            name: name.into(),
267            ino,
268            file_type,
269            offset: 0,
270        }
271    }
272
273    /// Create with an explicit offset.
274    pub fn with_offset(
275        name: impl Into<String>,
276        ino: u64,
277        file_type: VfsFileType,
278        offset: u64,
279    ) -> Self {
280        Self {
281            name: name.into(),
282            ino,
283            file_type,
284            offset,
285        }
286    }
287}
288
289/// Volume/filesystem information.
290///
291/// Contains information about the filesystem as a whole.
292#[derive(Debug, Clone)]
293#[cfg(feature = "alloc")]
294pub struct VfsVolumeInfo {
295    /// Total size of the filesystem in bytes
296    pub total_bytes: u64,
297    /// Free space in bytes
298    pub free_bytes: u64,
299    /// Available space for non-privileged users
300    pub available_bytes: u64,
301    /// Total number of inodes
302    pub total_inodes: u64,
303    /// Number of free inodes
304    pub free_inodes: u64,
305    /// Filesystem block size
306    pub block_size: u32,
307    /// Maximum filename length
308    pub max_filename_len: u32,
309    /// Filesystem type name (e.g., "xfs", "ext4")
310    pub fs_type: String,
311    /// Volume label (if any)
312    pub volume_label: Option<String>,
313    /// Filesystem UUID (if available)
314    pub uuid: Option<[u8; 16]>,
315}
316
317#[cfg(feature = "alloc")]
318impl Default for VfsVolumeInfo {
319    /// Implements default.
320    fn default() -> Self {
321        Self {
322            total_bytes: 0,
323            free_bytes: 0,
324            available_bytes: 0,
325            total_inodes: 0,
326            free_inodes: 0,
327            block_size: 4096,
328            max_filename_len: 255,
329            fs_type: String::new(),
330            volume_label: None,
331            uuid: None,
332        }
333    }
334}
335
336/// Rename operation flags.
337#[derive(Debug, Clone, Copy, Default)]
338pub struct RenameFlags {
339    /// Replace target if it exists
340    pub replace_if_exists: bool,
341    /// Atomically exchange source and target
342    pub exchange: bool,
343    /// Fail if target exists (mutually exclusive with replace_if_exists)
344    pub no_replace: bool,
345}
346
347/// File open flags.
348#[derive(Debug, Clone, Copy, Default)]
349pub struct OpenFlags {
350    /// Open for reading
351    pub read: bool,
352    /// Open for writing
353    pub write: bool,
354    /// Create if doesn't exist
355    pub create: bool,
356    /// Fail if exists (with create)
357    pub exclusive: bool,
358    /// Truncate to zero length
359    pub truncate: bool,
360    /// Append mode (writes go to end)
361    pub append: bool,
362    /// Open directory
363    pub directory: bool,
364}