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