|
1 | 1 | // Windows passthrough filesystem implementation |
2 | 2 | // Phase 1: Core data structures and basic read-only operations (completed) |
3 | 3 | // Phase 2: File read operations (completed) |
| 4 | +// Phase 3: Write operations (completed) |
4 | 5 |
|
5 | 6 | use std::collections::BTreeMap; |
6 | 7 | use std::ffi::CStr; |
@@ -506,19 +507,51 @@ impl FileSystem for PassthroughFs { |
506 | 507 | fn mkdir( |
507 | 508 | &self, |
508 | 509 | _ctx: Context, |
509 | | - _parent: Self::Inode, |
510 | | - _name: &CStr, |
| 510 | + parent: Self::Inode, |
| 511 | + name: &CStr, |
511 | 512 | _mode: u32, |
512 | 513 | _umask: u32, |
513 | 514 | _extensions: Extensions, |
514 | 515 | ) -> io::Result<Entry> { |
515 | | - // TODO: Implement mkdir |
516 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 516 | + let parent_path = self.get_path(parent)?; |
| 517 | + let name_str = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| 518 | + let new_path = parent_path.join(name_str); |
| 519 | + |
| 520 | + // Create the directory |
| 521 | + fs::create_dir(&new_path)?; |
| 522 | + |
| 523 | + // Get or create inode for the new directory |
| 524 | + let inode = self.get_or_create_inode(&new_path)?; |
| 525 | + |
| 526 | + // Get metadata |
| 527 | + let metadata = fs::metadata(&new_path)?; |
| 528 | + let st = self.metadata_to_stat(&metadata, inode); |
| 529 | + |
| 530 | + Ok(Entry { |
| 531 | + inode, |
| 532 | + generation: 0, |
| 533 | + attr: st, |
| 534 | + attr_flags: 0, |
| 535 | + attr_timeout: self.cfg.attr_timeout, |
| 536 | + entry_timeout: self.cfg.entry_timeout, |
| 537 | + }) |
517 | 538 | } |
518 | 539 |
|
519 | | - fn rmdir(&self, _ctx: Context, _parent: Self::Inode, _name: &CStr) -> io::Result<()> { |
520 | | - // TODO: Implement rmdir |
521 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 540 | + fn rmdir(&self, _ctx: Context, parent: Self::Inode, name: &CStr) -> io::Result<()> { |
| 541 | + let parent_path = self.get_path(parent)?; |
| 542 | + let name_str = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| 543 | + let dir_path = parent_path.join(name_str); |
| 544 | + |
| 545 | + // Remove the directory |
| 546 | + fs::remove_dir(&dir_path)?; |
| 547 | + |
| 548 | + // Remove from inode tracking |
| 549 | + let inode_opt = self.path_to_inode.write().unwrap().remove(&dir_path); |
| 550 | + if let Some(inode) = inode_opt { |
| 551 | + self.inodes.write().unwrap().remove(&inode); |
| 552 | + } |
| 553 | + |
| 554 | + Ok(()) |
522 | 555 | } |
523 | 556 |
|
524 | 557 | fn open( |
@@ -582,20 +615,81 @@ impl FileSystem for PassthroughFs { |
582 | 615 | fn create( |
583 | 616 | &self, |
584 | 617 | _ctx: Context, |
585 | | - _parent: Self::Inode, |
586 | | - _name: &CStr, |
| 618 | + parent: Self::Inode, |
| 619 | + name: &CStr, |
587 | 620 | _mode: u32, |
588 | | - _flags: u32, |
| 621 | + flags: u32, |
589 | 622 | _umask: u32, |
590 | 623 | _extensions: Extensions, |
591 | 624 | ) -> io::Result<(Entry, Option<Self::Handle>, OpenOptions)> { |
592 | | - // TODO: Implement create |
593 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 625 | + let parent_path = self.get_path(parent)?; |
| 626 | + let name_str = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| 627 | + let new_path = parent_path.join(name_str); |
| 628 | + |
| 629 | + // Create the file |
| 630 | + use std::fs::File; |
| 631 | + File::create(&new_path)?; |
| 632 | + |
| 633 | + // Get or create inode for the new file |
| 634 | + let inode = self.get_or_create_inode(&new_path)?; |
| 635 | + |
| 636 | + // Create a handle for the new file |
| 637 | + let handle = self.next_handle.fetch_add(1, Ordering::SeqCst); |
| 638 | + |
| 639 | + // Store handle data |
| 640 | + let handle_data = Arc::new(HandleData { |
| 641 | + inode, |
| 642 | + path: new_path.clone(), |
| 643 | + }); |
| 644 | + |
| 645 | + self.handles.write().unwrap().insert(handle, handle_data); |
| 646 | + |
| 647 | + // Get metadata |
| 648 | + let metadata = fs::metadata(&new_path)?; |
| 649 | + let st = self.metadata_to_stat(&metadata, inode); |
| 650 | + |
| 651 | + // Determine open options based on flags |
| 652 | + let mut opts = OpenOptions::empty(); |
| 653 | + |
| 654 | + const O_DIRECT: u32 = 0x4000; |
| 655 | + if flags & O_DIRECT != 0 { |
| 656 | + opts |= OpenOptions::DIRECT_IO; |
| 657 | + } |
| 658 | + |
| 659 | + const O_SYNC: u32 = 0x101000; |
| 660 | + if flags & O_SYNC == 0 { |
| 661 | + opts |= OpenOptions::KEEP_CACHE; |
| 662 | + } |
| 663 | + |
| 664 | + Ok(( |
| 665 | + Entry { |
| 666 | + inode, |
| 667 | + generation: 0, |
| 668 | + attr: st, |
| 669 | + attr_flags: 0, |
| 670 | + attr_timeout: self.cfg.attr_timeout, |
| 671 | + entry_timeout: self.cfg.entry_timeout, |
| 672 | + }, |
| 673 | + Some(handle), |
| 674 | + opts, |
| 675 | + )) |
594 | 676 | } |
595 | 677 |
|
596 | | - fn unlink(&self, _ctx: Context, _parent: Self::Inode, _name: &CStr) -> io::Result<()> { |
597 | | - // TODO: Implement unlink |
598 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 678 | + fn unlink(&self, _ctx: Context, parent: Self::Inode, name: &CStr) -> io::Result<()> { |
| 679 | + let parent_path = self.get_path(parent)?; |
| 680 | + let name_str = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| 681 | + let file_path = parent_path.join(name_str); |
| 682 | + |
| 683 | + // Remove the file |
| 684 | + fs::remove_file(&file_path)?; |
| 685 | + |
| 686 | + // Remove from inode tracking |
| 687 | + let inode_opt = self.path_to_inode.write().unwrap().remove(&file_path); |
| 688 | + if let Some(inode) = inode_opt { |
| 689 | + self.inodes.write().unwrap().remove(&inode); |
| 690 | + } |
| 691 | + |
| 692 | + Ok(()) |
599 | 693 | } |
600 | 694 |
|
601 | 695 | fn read<W: io::Write + ZeroCopyWriter>( |
@@ -642,42 +736,131 @@ impl FileSystem for PassthroughFs { |
642 | 736 | &self, |
643 | 737 | _ctx: Context, |
644 | 738 | _inode: Self::Inode, |
645 | | - _handle: Self::Handle, |
646 | | - _r: R, |
647 | | - _size: u32, |
648 | | - _offset: u64, |
| 739 | + handle: Self::Handle, |
| 740 | + mut r: R, |
| 741 | + size: u32, |
| 742 | + offset: u64, |
649 | 743 | _lock_owner: Option<u64>, |
650 | 744 | _delayed_write: bool, |
651 | 745 | _kill_priv: bool, |
652 | 746 | _flags: u32, |
653 | 747 | ) -> io::Result<usize> { |
654 | | - // TODO: Implement write |
655 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 748 | + // Get the path from the handle |
| 749 | + let handles = self.handles.read().unwrap(); |
| 750 | + let handle_data = handles |
| 751 | + .get(&handle) |
| 752 | + .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?; |
| 753 | + |
| 754 | + let path = &handle_data.path; |
| 755 | + |
| 756 | + // Open the file for writing |
| 757 | + use std::fs::OpenOptions as StdOpenOptions; |
| 758 | + use std::io::{Seek, SeekFrom, Write}; |
| 759 | + |
| 760 | + let mut file = StdOpenOptions::new() |
| 761 | + .write(true) |
| 762 | + .open(path)?; |
| 763 | + |
| 764 | + // Seek to the requested offset |
| 765 | + file.seek(SeekFrom::Start(offset))?; |
| 766 | + |
| 767 | + // Read data from the input reader and write to file |
| 768 | + let mut buffer = vec![0u8; size as usize]; |
| 769 | + let bytes_read = r.read(&mut buffer)?; |
| 770 | + |
| 771 | + if bytes_read > 0 { |
| 772 | + file.write_all(&buffer[..bytes_read])?; |
| 773 | + } |
| 774 | + |
| 775 | + Ok(bytes_read) |
656 | 776 | } |
657 | 777 |
|
658 | 778 | fn setattr( |
659 | 779 | &self, |
660 | 780 | _ctx: Context, |
661 | | - _inode: Self::Inode, |
662 | | - _attr: bindings::stat64, |
| 781 | + inode: Self::Inode, |
| 782 | + attr: bindings::stat64, |
663 | 783 | _handle: Option<Self::Handle>, |
664 | | - _valid: SetattrValid, |
| 784 | + valid: SetattrValid, |
665 | 785 | ) -> io::Result<(bindings::stat64, Duration)> { |
666 | | - // TODO: Implement setattr |
667 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 786 | + let path = self.get_path(inode)?; |
| 787 | + |
| 788 | + // Handle size changes (truncate) |
| 789 | + if valid.contains(SetattrValid::SIZE) { |
| 790 | + use std::fs::OpenOptions as StdOpenOptions; |
| 791 | + let file = StdOpenOptions::new() |
| 792 | + .write(true) |
| 793 | + .open(&path)?; |
| 794 | + file.set_len(attr.st_size as u64)?; |
| 795 | + } |
| 796 | + |
| 797 | + // Handle time changes |
| 798 | + if valid.contains(SetattrValid::ATIME) || valid.contains(SetattrValid::MTIME) { |
| 799 | + use std::fs::File; |
| 800 | + use std::time::UNIX_EPOCH; |
| 801 | + |
| 802 | + let file = File::open(&path)?; |
| 803 | + |
| 804 | + // Windows doesn't support setting atime/mtime separately via std::fs |
| 805 | + // We would need to use Windows API (SetFileTime) for full support |
| 806 | + // For now, just update the modification time if MTIME is set |
| 807 | + if valid.contains(SetattrValid::MTIME) { |
| 808 | + let mtime = UNIX_EPOCH + Duration::from_secs(attr.st_mtime as u64); |
| 809 | + file.set_modified(mtime)?; |
| 810 | + } |
| 811 | + } |
| 812 | + |
| 813 | + // Note: Windows doesn't support POSIX permissions (mode) or ownership (uid/gid) |
| 814 | + // These would require mapping to Windows ACLs, which is complex |
| 815 | + // For now, we ignore MODE, UID, GID changes |
| 816 | + |
| 817 | + // Get updated metadata |
| 818 | + let metadata = fs::metadata(&path)?; |
| 819 | + let st = self.metadata_to_stat(&metadata, inode); |
| 820 | + |
| 821 | + Ok((st, self.cfg.attr_timeout)) |
668 | 822 | } |
669 | 823 |
|
670 | 824 | fn rename( |
671 | 825 | &self, |
672 | 826 | _ctx: Context, |
673 | | - _olddir: Self::Inode, |
674 | | - _oldname: &CStr, |
675 | | - _newdir: Self::Inode, |
676 | | - _newname: &CStr, |
| 827 | + olddir: Self::Inode, |
| 828 | + oldname: &CStr, |
| 829 | + newdir: Self::Inode, |
| 830 | + newname: &CStr, |
677 | 831 | _flags: u32, |
678 | 832 | ) -> io::Result<()> { |
679 | | - // TODO: Implement rename |
680 | | - Err(io::Error::from_raw_os_error(libc::ENOSYS)) |
| 833 | + let olddir_path = self.get_path(olddir)?; |
| 834 | + let newdir_path = self.get_path(newdir)?; |
| 835 | + |
| 836 | + let oldname_str = oldname.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| 837 | + let newname_str = newname.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; |
| 838 | + |
| 839 | + let old_path = olddir_path.join(oldname_str); |
| 840 | + let new_path = newdir_path.join(newname_str); |
| 841 | + |
| 842 | + // Perform the rename |
| 843 | + fs::rename(&old_path, &new_path)?; |
| 844 | + |
| 845 | + // Update inode tracking |
| 846 | + let mut path_to_inode = self.path_to_inode.write().unwrap(); |
| 847 | + if let Some(inode) = path_to_inode.remove(&old_path) { |
| 848 | + path_to_inode.insert(new_path.clone(), inode); |
| 849 | + |
| 850 | + // Update the path in InodeData |
| 851 | + if let Some(inode_data) = self.inodes.write().unwrap().get_mut(&inode) { |
| 852 | + // We need to update the path, but InodeData.path is not mutable |
| 853 | + // For now, we'll remove and re-insert with updated path |
| 854 | + let new_inode_data = Arc::new(InodeData { |
| 855 | + inode, |
| 856 | + path: new_path, |
| 857 | + refcount: AtomicU64::new(inode_data.refcount.load(Ordering::SeqCst)), |
| 858 | + }); |
| 859 | + self.inodes.write().unwrap().insert(inode, new_inode_data); |
| 860 | + } |
| 861 | + } |
| 862 | + |
| 863 | + Ok(()) |
681 | 864 | } |
682 | 865 |
|
683 | 866 | fn mknod( |
|
0 commit comments