1use serde::{Deserialize, Serialize};
20use std::time::SystemTime;
21
22pub type InodeId = u64;
24
25pub type BlockId = u64;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[repr(u8)]
31pub enum FileType {
32 Regular = 1,
34 Directory = 2,
36 Symlink = 3,
38 SochDocument = 4,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44pub struct Permissions {
45 pub read: bool,
46 pub write: bool,
47 pub execute: bool,
48}
49
50impl Permissions {
51 pub fn new(read: bool, write: bool, execute: bool) -> Self {
52 Self {
53 read,
54 write,
55 execute,
56 }
57 }
58
59 pub fn all() -> Self {
60 Self {
61 read: true,
62 write: true,
63 execute: true,
64 }
65 }
66
67 pub fn read_only() -> Self {
68 Self {
69 read: true,
70 write: false,
71 execute: false,
72 }
73 }
74
75 pub fn read_write() -> Self {
76 Self {
77 read: true,
78 write: true,
79 execute: false,
80 }
81 }
82
83 pub fn to_mode(&self) -> u8 {
84 let mut mode = 0u8;
85 if self.read {
86 mode |= 0b100;
87 }
88 if self.write {
89 mode |= 0b010;
90 }
91 if self.execute {
92 mode |= 0b001;
93 }
94 mode
95 }
96
97 pub fn from_mode(mode: u8) -> Self {
98 Self {
99 read: mode & 0b100 != 0,
100 write: mode & 0b010 != 0,
101 execute: mode & 0b001 != 0,
102 }
103 }
104}
105
106impl Default for Permissions {
107 fn default() -> Self {
108 Self::read_write()
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct Inode {
115 pub id: InodeId,
117 pub file_type: FileType,
119 pub size: u64,
121 pub blocks: Vec<BlockId>,
123 pub permissions: Permissions,
125 pub created_us: u64,
127 pub modified_us: u64,
129 pub accessed_us: u64,
131 pub nlink: u32,
133 pub symlink_target: Option<String>,
135 pub soch_schema: Option<String>,
137}
138
139impl Inode {
140 pub fn new_file(id: InodeId) -> Self {
141 let now = now_micros();
142 Self {
143 id,
144 file_type: FileType::Regular,
145 size: 0,
146 blocks: Vec::new(),
147 permissions: Permissions::read_write(),
148 created_us: now,
149 modified_us: now,
150 accessed_us: now,
151 nlink: 1,
152 symlink_target: None,
153 soch_schema: None,
154 }
155 }
156
157 pub fn new_directory(id: InodeId) -> Self {
158 let now = now_micros();
159 Self {
160 id,
161 file_type: FileType::Directory,
162 size: 0,
163 blocks: Vec::new(),
164 permissions: Permissions::all(),
165 created_us: now,
166 modified_us: now,
167 accessed_us: now,
168 nlink: 2, symlink_target: None,
170 soch_schema: None,
171 }
172 }
173
174 pub fn new_symlink(id: InodeId, target: String) -> Self {
175 let now = now_micros();
176 Self {
177 id,
178 file_type: FileType::Symlink,
179 size: target.len() as u64,
180 blocks: Vec::new(),
181 permissions: Permissions::all(),
182 created_us: now,
183 modified_us: now,
184 accessed_us: now,
185 nlink: 1,
186 symlink_target: Some(target),
187 soch_schema: None,
188 }
189 }
190
191 pub fn new_toon(id: InodeId, schema: String) -> Self {
192 let now = now_micros();
193 Self {
194 id,
195 file_type: FileType::SochDocument,
196 size: 0,
197 blocks: Vec::new(),
198 permissions: Permissions::read_write(),
199 created_us: now,
200 modified_us: now,
201 accessed_us: now,
202 nlink: 1,
203 symlink_target: None,
204 soch_schema: Some(schema),
205 }
206 }
207
208 pub fn is_dir(&self) -> bool {
209 self.file_type == FileType::Directory
210 }
211
212 pub fn is_file(&self) -> bool {
213 self.file_type == FileType::Regular
214 }
215
216 pub fn is_symlink(&self) -> bool {
217 self.file_type == FileType::Symlink
218 }
219
220 pub fn is_toon(&self) -> bool {
221 self.file_type == FileType::SochDocument
222 }
223
224 pub fn touch(&mut self) {
225 self.modified_us = now_micros();
226 self.accessed_us = self.modified_us;
227 }
228
229 pub fn update_access_time(&mut self) {
230 self.accessed_us = now_micros();
231 }
232
233 pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
235 bincode::serialize(self).map_err(|e| e.to_string())
236 }
237
238 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
240 bincode::deserialize(data).map_err(|e| e.to_string())
241 }
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct DirEntry {
247 pub name: String,
249 pub inode: InodeId,
251 pub file_type: FileType,
253}
254
255impl DirEntry {
256 pub fn new(name: impl Into<String>, inode: InodeId, file_type: FileType) -> Self {
257 Self {
258 name: name.into(),
259 inode,
260 file_type,
261 }
262 }
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct Directory {
268 pub parent: InodeId,
270 pub entries: Vec<DirEntry>,
272}
273
274impl Directory {
275 pub fn new(parent: InodeId) -> Self {
276 Self {
277 parent,
278 entries: Vec::new(),
279 }
280 }
281
282 pub fn add_entry(&mut self, entry: DirEntry) {
283 self.entries.push(entry);
284 }
285
286 pub fn remove_entry(&mut self, name: &str) -> Option<DirEntry> {
287 if let Some(pos) = self.entries.iter().position(|e| e.name == name) {
288 Some(self.entries.remove(pos))
289 } else {
290 None
291 }
292 }
293
294 pub fn find_entry(&self, name: &str) -> Option<&DirEntry> {
295 self.entries.iter().find(|e| e.name == name)
296 }
297
298 pub fn contains(&self, name: &str) -> bool {
299 self.entries.iter().any(|e| e.name == name)
300 }
301
302 pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
304 bincode::serialize(self).map_err(|e| e.to_string())
305 }
306
307 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
309 bincode::deserialize(data).map_err(|e| e.to_string())
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct Superblock {
316 pub magic: [u8; 4],
318 pub version: u32,
320 pub root_inode: InodeId,
322 pub next_inode: InodeId,
324 pub next_block: BlockId,
326 pub total_inodes: u64,
328 pub total_blocks: u64,
330 pub block_size: u32,
332 pub created_us: u64,
334 pub mounted_us: u64,
336 pub label: String,
338}
339
340impl Superblock {
341 pub const MAGIC: [u8; 4] = *b"TOON";
342 pub const VERSION: u32 = 1;
343 pub const DEFAULT_BLOCK_SIZE: u32 = 4096;
344
345 pub fn new(label: impl Into<String>) -> Self {
346 let now = now_micros();
347 Self {
348 magic: Self::MAGIC,
349 version: Self::VERSION,
350 root_inode: 1, next_inode: 2,
352 next_block: 0,
353 total_inodes: 1,
354 total_blocks: 0,
355 block_size: Self::DEFAULT_BLOCK_SIZE,
356 created_us: now,
357 mounted_us: now,
358 label: label.into(),
359 }
360 }
361
362 pub fn alloc_inode(&mut self) -> InodeId {
364 let id = self.next_inode;
365 self.next_inode += 1;
366 self.total_inodes += 1;
367 id
368 }
369
370 pub fn alloc_blocks(&mut self, count: u64) -> Vec<BlockId> {
372 let start = self.next_block;
373 self.next_block += count;
374 self.total_blocks += count;
375 (start..start + count).collect()
376 }
377
378 pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
380 bincode::serialize(self).map_err(|e| e.to_string())
381 }
382
383 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
385 let sb: Self = bincode::deserialize(data).map_err(|e| e.to_string())?;
386 if sb.magic != Self::MAGIC {
387 return Err("Invalid magic number".into());
388 }
389 Ok(sb)
390 }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395pub enum VfsOp {
396 CreateFile {
398 parent: InodeId,
399 name: String,
400 inode: Inode,
401 },
402 CreateDir {
404 parent: InodeId,
405 name: String,
406 inode: Inode,
407 },
408 Delete {
410 parent: InodeId,
411 name: String,
412 inode: InodeId,
413 },
414 Rename {
416 old_parent: InodeId,
417 old_name: String,
418 new_parent: InodeId,
419 new_name: String,
420 inode: InodeId,
421 },
422 WriteBlock {
424 inode: InodeId,
425 block: BlockId,
426 data: Vec<u8>,
427 },
428 Truncate { inode: InodeId, new_size: u64 },
430 UpdateInode { inode: Inode },
432 UpdateSuperblock { superblock: Superblock },
434 CreateSymlink {
436 parent: InodeId,
437 name: String,
438 inode: Inode,
439 target: String,
440 },
441}
442
443#[derive(Debug, Clone)]
445pub struct FileStat {
446 pub inode: InodeId,
447 pub file_type: FileType,
448 pub size: u64,
449 pub blocks: u64,
450 pub block_size: u32,
451 pub nlink: u32,
452 pub permissions: Permissions,
453 pub created: SystemTime,
454 pub modified: SystemTime,
455 pub accessed: SystemTime,
456}
457
458impl From<&Inode> for FileStat {
459 fn from(inode: &Inode) -> Self {
460 Self {
461 inode: inode.id,
462 file_type: inode.file_type,
463 size: inode.size,
464 blocks: inode.blocks.len() as u64,
465 block_size: Superblock::DEFAULT_BLOCK_SIZE,
466 nlink: inode.nlink,
467 permissions: inode.permissions,
468 created: micros_to_system_time(inode.created_us),
469 modified: micros_to_system_time(inode.modified_us),
470 accessed: micros_to_system_time(inode.accessed_us),
471 }
472 }
473}
474
475fn now_micros() -> u64 {
477 SystemTime::now()
478 .duration_since(SystemTime::UNIX_EPOCH)
479 .map(|d| d.as_micros() as u64)
480 .unwrap_or(0)
481}
482
483fn micros_to_system_time(micros: u64) -> SystemTime {
485 SystemTime::UNIX_EPOCH + std::time::Duration::from_micros(micros)
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_inode_serialization() {
494 let inode = Inode::new_file(42);
495 let bytes = inode.to_bytes().expect("Failed to serialize inode");
496 let parsed = Inode::from_bytes(&bytes).unwrap();
497 assert_eq!(parsed.id, 42);
498 assert!(parsed.is_file());
499 }
500
501 #[test]
502 fn test_directory_operations() {
503 let mut dir = Directory::new(1);
504 dir.add_entry(DirEntry::new("file1.txt", 10, FileType::Regular));
505 dir.add_entry(DirEntry::new("subdir", 11, FileType::Directory));
506
507 assert!(dir.contains("file1.txt"));
508 assert!(dir.contains("subdir"));
509 assert!(!dir.contains("nonexistent"));
510
511 let entry = dir.find_entry("file1.txt").unwrap();
512 assert_eq!(entry.inode, 10);
513
514 let removed = dir.remove_entry("file1.txt").unwrap();
515 assert_eq!(removed.inode, 10);
516 assert!(!dir.contains("file1.txt"));
517 }
518
519 #[test]
520 fn test_superblock() {
521 let mut sb = Superblock::new("test-fs");
522 assert_eq!(sb.magic, Superblock::MAGIC);
523 assert_eq!(sb.root_inode, 1);
524
525 let inode1 = sb.alloc_inode();
526 let inode2 = sb.alloc_inode();
527 assert_eq!(inode1, 2);
528 assert_eq!(inode2, 3);
529
530 let blocks = sb.alloc_blocks(5);
531 assert_eq!(blocks.len(), 5);
532 assert_eq!(blocks[0], 0);
533 assert_eq!(blocks[4], 4);
534 }
535
536 #[test]
537 fn test_permissions() {
538 let perms = Permissions::new(true, true, false);
539 assert_eq!(perms.to_mode(), 0b110);
540
541 let from_mode = Permissions::from_mode(0b101);
542 assert!(from_mode.read);
543 assert!(!from_mode.write);
544 assert!(from_mode.execute);
545 }
546}