From f6c26c956c97da2506c4e69b9dc4f121da39af0b Mon Sep 17 00:00:00 2001 From: Heinz Date: Wed, 23 Jul 2025 23:40:26 +0800 Subject: [PATCH 01/17] perf: replace the annoyed ext4 crate with a new choice (called another ext4 crate --- Cargo.lock | 41 +++++++++++----------- Cargo.toml | 2 +- src/fs/ext4.rs | 94 ++++++++++++++++++++++++++++++-------------------- 3 files changed, 78 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8b8e1b9..04b25a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,15 +19,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c330e503236d0b06386ae6cc42a513ef1ccc23c52b603c1b52f018564faf44" +[[package]] +name = "another_ext4" +version = "0.1.0" +source = "git+https://github.com/SMS-Derfflinger/another_ext4?branch=main#ed6d91718db721eb4a744483c289cc44a6f34bf4" +dependencies = [ + "bitflags", + "log", +] + [[package]] name = "atomic_unique_refcell" version = "0.1.0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bit_field" @@ -51,9 +60,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "critical-section" @@ -133,6 +142,7 @@ version = "0.1.0" dependencies = [ "acpi", "align_ext", + "another_ext4", "atomic_unique_refcell", "bitflags", "buddy_allocator", @@ -144,7 +154,6 @@ dependencies = [ "eonix_preempt", "eonix_runtime", "eonix_sync", - "ext4_rs", "intrusive-collections", "intrusive_list", "itertools", @@ -247,16 +256,6 @@ dependencies = [ "intrusive-collections", ] -[[package]] -name = "ext4_rs" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1a97344bde15b0ace15e265dab27228d4bdc37a0bfa8548c5645d7cfa6a144" -dependencies = [ - "bitflags", - "log", -] - [[package]] name = "fdt" version = "0.1.5" @@ -392,9 +391,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -459,18 +458,18 @@ checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784" [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index edc2c319..ceea1cf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ itertools = { version = "0.13.0", default-features = false } acpi = "5.2.0" align_ext = "0.1.0" xmas-elf = "0.10.0" -ext4_rs = "1.3.2" +another_ext4 = { git = "https://github.com/SMS-Derfflinger/another_ext4", branch = "main" } [target.'cfg(target_arch = "riscv64")'.dependencies] virtio-drivers = { version = "0.11.0" } diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 3ffc2fe0..56928623 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -22,10 +22,11 @@ use alloc::{ collections::btree_map::{BTreeMap, Entry}, sync::Arc, }; +use another_ext4::{ + Block, BlockDevice as Ext4BlockDeviceTrait, Ext4, FileType, InodeMode, PBlockId, +}; use eonix_runtime::task::Task; use eonix_sync::RwLock; -use ext4_rs::{BlockDevice as Ext4BlockDeviceTrait, Ext4Error}; -use ext4_rs::{Errno, Ext4}; pub struct Ext4BlockDevice { device: Arc, @@ -38,19 +39,22 @@ impl Ext4BlockDevice { } impl Ext4BlockDeviceTrait for Ext4BlockDevice { - fn read_offset(&self, offset: usize) -> Vec { - let mut buffer = vec![0u8; 4096]; + fn read_block(&self, block_id: PBlockId) -> Block { + let mut buffer = [0u8; 4096]; let mut byte_buffer = ByteBuffer::new(buffer.as_mut_slice()); let _ = self .device - .read_some(offset, &mut byte_buffer) + .read_some((block_id as usize) * 4096, &mut byte_buffer) .expect("Failed to read from block device"); - buffer + Block { + id: block_id, + data: buffer, + } } - fn write_offset(&self, _offset: usize, _data: &[u8]) { + fn write_block(&self, block: &another_ext4::Block) { todo!() } } @@ -115,7 +119,7 @@ impl Ext4Fs { impl Ext4Fs { pub fn create(device: Arc) -> KResult<(Arc, Arc)> { let ext4_device = Ext4BlockDevice::new(device.clone()); - let ext4 = Ext4::open(Arc::new(ext4_device)); + let ext4 = Ext4::load(Arc::new(ext4_device)).unwrap(); let ext4fs = Arc::new(Self { inner: ext4, @@ -125,28 +129,28 @@ impl Ext4Fs { let root_inode = { let mut icache = Task::block_on(ext4fs.icache.write()); - let root_inode = ext4fs.inner.get_inode_ref(2); + let root_inode = ext4fs.inner.read_root_inode(); ext4fs.get_or_insert( &mut icache, InodeData { - ino: root_inode.inode_num as Ino, + ino: root_inode.id as Ino, size: AtomicU64::new(root_inode.inode.size()), - nlink: AtomicNlink::new(root_inode.inode.links_count() as _), - uid: AtomicU32::new(root_inode.inode.uid() as _), - gid: AtomicU32::new(root_inode.inode.gid() as _), - mode: AtomicU32::new(root_inode.inode.mode() as _), + nlink: AtomicNlink::new(root_inode.inode.link_count() as u64), + uid: AtomicU32::new(root_inode.inode.uid()), + gid: AtomicU32::new(root_inode.inode.gid()), + mode: AtomicU32::new(root_inode.inode.mode().bits() as u32), atime: Spin::new(Instant::new( root_inode.inode.atime() as _, - root_inode.inode.i_atime_extra() as _, + root_inode.inode.atime_extra() as _, )), ctime: Spin::new(Instant::new( root_inode.inode.ctime() as _, - root_inode.inode.i_ctime_extra() as _, + root_inode.inode.ctime_extra() as _, )), mtime: Spin::new(Instant::new( root_inode.inode.mtime() as _, - root_inode.inode.i_mtime_extra() as _, + root_inode.inode.mtime_extra() as _, )), rwsem: RwLock::new(()), vfs: Arc::downgrade(&ext4fs) as _, @@ -187,12 +191,12 @@ impl Inode for FileInode { let ext4fs = vfs.as_any().downcast_ref::().unwrap(); let mut temp_buf = vec![0u8; buffer.total()]; - match ext4fs.inner.read_at(self.ino as u32, offset, &mut temp_buf) { + match ext4fs.inner.read(self.ino as u32, offset, &mut temp_buf) { Ok(bytes_read) => { let _ = buffer.fill(&temp_buf[..bytes_read])?; Ok(buffer.wrote()) } - Err(e) => Err(e.error() as u32), + Err(e) => Err(e.code() as u32), } } } @@ -204,13 +208,14 @@ impl Inode for DirInode { let name = dentry.get_name(); let name = String::from_utf8_lossy(&name); - let lookup_result = ext4fs.inner.fuse_lookup(self.ino, &name); + let lookup_result = ext4fs.inner.lookup(self.ino as u32, &name); - const EXT4_ERROR_ENOENT: Ext4Error = Ext4Error::new(Errno::ENOENT); + // TODO: wtf + //const EXT4_ERROR_ENOENT: Ext4Error_ = Ext4Error_::new(ErrCode::ENOENT); let attr = match lookup_result { - Ok(attr) => attr, - Err(EXT4_ERROR_ENOENT) => return Ok(None), - Err(error) => return Err(error.error() as u32), + Ok(inode_id) => ext4fs.inner.getattr(inode_id).unwrap(), + //Err(EXT4_ERROR_ENOENT) => return Ok(None), + Err(error) => return Err(error.code() as u32), }; // Fast path: if the inode is already in the cache, return it. @@ -219,9 +224,19 @@ impl Inode for DirInode { return Ok(Some(inode)); } - let extra_perm = attr.perm.bits() as u32 & 0o7000; - let perm = attr.perm.bits() as u32 & 0o0700; - let real_perm = extra_perm | perm | perm >> 3 | perm >> 6; + let file_type_bits = match attr.ftype { + FileType::RegularFile => InodeMode::FILE.bits(), + FileType::Directory => InodeMode::DIRECTORY.bits(), + FileType::CharacterDev => InodeMode::CHARDEV.bits(), + FileType::BlockDev => InodeMode::BLOCKDEV.bits(), + FileType::Fifo => InodeMode::FIFO.bits(), + FileType::Socket => InodeMode::SOCKET.bits(), + FileType::SymLink => InodeMode::SOFTLINK.bits(), + FileType::Unknown => 0, + }; + + let perm_bits = attr.perm.bits() & InodeMode::PERM_MASK.bits(); + let mode = file_type_bits | perm_bits; // Create a new inode based on the attributes. let mut icache = Task::block_on(ext4fs.icache.write()); @@ -230,10 +245,10 @@ impl Inode for DirInode { InodeData { ino: attr.ino as Ino, size: AtomicU64::new(attr.size), - nlink: AtomicNlink::new(attr.nlink as _), + nlink: AtomicNlink::new(attr.links as _), uid: AtomicU32::new(attr.uid), gid: AtomicU32::new(attr.gid), - mode: AtomicU32::new(attr.kind.bits() as u32 | real_perm), + mode: AtomicU32::new(mode as u32), atime: Spin::new(Instant::new(attr.atime as _, 0)), ctime: Spin::new(Instant::new(attr.ctime as _, 0)), mtime: Spin::new(Instant::new(attr.mtime as _, 0)), @@ -255,18 +270,23 @@ impl Inode for DirInode { let entries = ext4fs .inner - .fuse_readdir(self.ino as u64, 0, offset as i64) - .map_err(|err| err.error() as u32)?; - let mut current_offset = 0; + .listdir(self.ino as u32) + .map_err(|err| err.code() as u32)?; - for entry in entries { - let name_len = entry.name_len as usize; - let name = &entry.name[..name_len]; + let entries_to_process = if offset < entries.len() { + &entries[offset..] + } else { + &entries[0..0] + }; + let mut current_offset = 0; + for entry in entries_to_process { + let name_string = entry.name(); + let name = name_string.as_bytes(); + let inode = entry.inode() as Ino; - if callback(name, entry.inode as Ino)?.is_break() { + if callback(name, inode)?.is_break() { break; } - current_offset += 1; } Ok(current_offset) From f05037374c20c896a0249ffda6edebb7bd562d9c Mon Sep 17 00:00:00 2001 From: Heinz Date: Fri, 25 Jul 2025 22:47:02 +0800 Subject: [PATCH 02/17] feat(fs): impl write, create and mkdir for ext4 fs --- src/driver/virtio/virtio_blk.rs | 18 +++- src/fs/ext4.rs | 158 ++++++++++++++++++++++++++++++-- src/kernel/block.rs | 97 ++++++++++++++++++++ 3 files changed, 266 insertions(+), 7 deletions(-) diff --git a/src/driver/virtio/virtio_blk.rs b/src/driver/virtio/virtio_blk.rs index 57026d2a..3b15063d 100644 --- a/src/driver/virtio/virtio_blk.rs +++ b/src/driver/virtio/virtio_blk.rs @@ -18,7 +18,23 @@ impl BlockRequestQueue for Spin>> { fn submit(&self, req: BlockDeviceRequest) -> KResult<()> { match req { - BlockDeviceRequest::Write { .. } => todo!(), + BlockDeviceRequest::Write { + sector, + count, + buffer, + } => { + let mut dev = self.lock(); + for ((start, len), buffer_page) in + Chunks::new(sector as usize, count as usize, 8).zip(buffer.iter()) + { + let buffer = unsafe { + // SAFETY: Pages in `req.buffer` are guaranteed to be exclusively owned by us. + &buffer_page.as_memblk().as_bytes()[..len as usize * 512] + }; + + dev.write_blocks(start, buffer).map_err(|_| EIO)?; + } + } BlockDeviceRequest::Read { sector, count, diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 56928623..f7ca7578 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -1,14 +1,14 @@ -use core::sync::atomic::{AtomicU32, AtomicU64}; +use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use crate::{ - io::{Buffer, ByteBuffer}, + io::{Buffer, ByteBuffer, Stream}, kernel::{ block::BlockDevice, - constants::EIO, + constants::{EIO, S_IFDIR, S_IFREG}, timer::Instant, vfs::{ dentry::Dentry, - inode::{define_struct_inode, AtomicNlink, Ino, Inode, InodeData}, + inode::{define_struct_inode, AtomicNlink, Ino, Inode, InodeData, Mode, WriteOffset}, mount::{register_filesystem, Mount, MountCreator}, s_isdir, s_isreg, vfs::Vfs, @@ -20,7 +20,7 @@ use crate::{ }; use alloc::{ collections::btree_map::{BTreeMap, Entry}, - sync::Arc, + sync::{Arc, Weak}, }; use another_ext4::{ Block, BlockDevice as Ext4BlockDeviceTrait, Ext4, FileType, InodeMode, PBlockId, @@ -55,7 +55,9 @@ impl Ext4BlockDeviceTrait for Ext4BlockDevice { } fn write_block(&self, block: &another_ext4::Block) { - todo!() + let _ = self + .device + .write_some((block.id as usize) * 4096, &block.data); } } @@ -85,6 +87,20 @@ impl Ext4Fs { icache.get(&ino).cloned().map(Ext4Inode::into_inner) } + fn update_modify_inode(&self, ino: u64, size: u64, mtime: u32) { + let _ = self.inner.setattr( + ino as u32, + None, + None, + None, + Some(size), + None, + Some(mtime), + None, + None, + ); + } + fn get_or_insert( &self, icache: &mut BTreeMap, @@ -185,6 +201,21 @@ define_struct_inode! { struct DirInode; } +impl FileInode { + pub fn new(ino: Ino, vfs: Weak, mode: Mode) -> Arc { + Arc::new_cyclic(|_| FileInode { + idata: { + let inode_data = InodeData::new(ino, vfs); + inode_data + .mode + .store(S_IFREG | (mode & 0o777), Ordering::Relaxed); + inode_data.nlink.store(1, Ordering::Relaxed); + inode_data + }, + }) + } +} + impl Inode for FileInode { fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { let vfs = self.vfs.upgrade().ok_or(EIO)?; @@ -199,6 +230,68 @@ impl Inode for FileInode { Err(e) => Err(e.code() as u32), } } + + fn write(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult { + let _lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + let mut temp_buf = vec![0u8; 4096]; + let mut total_written = 0; + + let offset = match offset { + WriteOffset::Position(offset) => offset, + // TODO: here need to add some operate + WriteOffset::End(end) => *end, + }; + + while let Some(data) = stream.poll_data(&mut temp_buf)? { + let written = ext4fs + .inner + .write(self.ino as u32, offset + total_written, data) + .unwrap(); + total_written += written; + if written < data.len() { + break; + } + } + + let mtime = Instant::now(); + *self.mtime.lock() = mtime; + let new_size = (offset + total_written) as u64; + self.size + .store(offset as u64 + total_written as u64, Ordering::Relaxed); + ext4fs.update_modify_inode(self.ino, new_size, mtime.since_epoch().as_secs() as u32); + + Ok(total_written) + } + + // TODO + fn truncate(&self, length: usize) -> KResult<()> { + Ok(()) + } +} + +impl DirInode { + fn new(idata: InodeData) -> Arc { + let inode = Arc::new(Self { idata }); + + inode + } + + fn link(&self, file: &dyn Inode) { + let now = Instant::now(); + + // SAFETY: Only `unlink` will do something based on `nlink` count + // No need to synchronize here + file.nlink.fetch_add(1, Ordering::Relaxed); + *self.ctime.lock() = now; + + // SAFETY: `rwsem` has done the synchronization + self.size.fetch_add(1, Ordering::Relaxed); + *self.mtime.lock() = now; + } } impl Inode for DirInode { @@ -291,6 +384,59 @@ impl Inode for DirInode { } Ok(current_offset) } + + fn creat(&self, at: &Arc, mode: Mode) -> KResult<()> { + let _lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + let name = at.get_name(); + let name = String::from_utf8_lossy(&name); + + let new_ino = ext4fs + .inner + .create( + self.ino as u32, + &name, + InodeMode::from_bits_retain((mode | S_IFREG) as u16), + ) + .unwrap(); + + let file = FileInode::new(new_ino as u64, self.vfs.clone(), mode); + let now = Instant::now(); + + *self.ctime.lock() = now; + // SAFETY: `rwsem` has done the synchronization + self.size.fetch_add(1, Ordering::Relaxed); + *self.mtime.lock() = now; + + at.save_reg(file) + } + + fn mkdir(&self, at: &Dentry, mode: Mode) -> KResult<()> { + let _lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + let name = at.get_name(); + let name = String::from_utf8_lossy(&name); + + let new_ino = ext4fs + .inner + .mkdir( + self.ino as u32, + &name, + InodeMode::from_bits_retain((mode | S_IFDIR) as u16), + ) + .unwrap(); + + let newdir = DirInode::new(InodeData::new(new_ino as u64, self.vfs.clone())); + + self.link(newdir.as_ref()); + at.save_dir(newdir) + } } struct Ext4MountCreator; diff --git a/src/kernel/block.rs b/src/kernel/block.rs index 4a10e4c7..069ae4ca 100644 --- a/src/kernel/block.rs +++ b/src/kernel/block.rs @@ -285,6 +285,103 @@ impl BlockDevice { Ok(FillResult::Partial(nfilled)) } } + + /// Write some data to the block device, may involve some copy and fragmentation + /// + /// # Arguments + /// `offset` - offset in bytes + /// `data` - data to write + /// + pub fn write_some(&self, offset: usize, data: &[u8]) -> KResult { + let mut sector_start = offset as u64 / 512; + let mut first_sector_offset = offset as u64 % 512; + let mut remaining_data = data; + let mut nwritten = 0; + + while !remaining_data.is_empty() { + let pages: &[Page]; + let page: Option; + let page_vec: Option>; + + // Calculate sectors needed for this write + let write_end = first_sector_offset + remaining_data.len() as u64; + let sector_count = ((write_end + 511) / 512).min(self.queue().max_request_pages()); + + match sector_count { + count if count <= 8 => { + let _page = Page::alloc(); + page = Some(_page); + pages = core::slice::from_ref(page.as_ref().unwrap()); + } + count if count <= 16 => { + let _pages = Page::alloc_order(1); + page = Some(_pages); + pages = core::slice::from_ref(page.as_ref().unwrap()); + } + count => { + let npages = (count + 15) / 16; + let mut _page_vec = Vec::with_capacity(npages as usize); + for _ in 0..npages { + _page_vec.push(Page::alloc_order(1)); + } + page_vec = Some(_page_vec); + pages = page_vec.as_ref().unwrap().as_slice(); + } + } + + if first_sector_offset != 0 || remaining_data.len() < (sector_count * 512) as usize { + let read_req = BlockDeviceRequest::Read { + sector: sector_start, + count: sector_count, + buffer: pages, + }; + self.commit_request(read_req)?; + } + + let mut data_offset = 0; + let mut page_offset = first_sector_offset as usize; + + for page in pages.iter() { + // SAFETY: We own the page and can modify it + let page_data = unsafe { + let memblk = page.as_memblk(); + core::slice::from_raw_parts_mut(memblk.addr().get() as *mut u8, memblk.len()) + }; + + let copy_len = + (remaining_data.len() - data_offset).min(page_data.len() - page_offset); + + if copy_len == 0 { + break; + } + + page_data[page_offset..page_offset + copy_len] + .copy_from_slice(&remaining_data[data_offset..data_offset + copy_len]); + + data_offset += copy_len; + page_offset = 0; // Only first page has offset + + if data_offset >= remaining_data.len() { + break; + } + } + + let write_req = BlockDeviceRequest::Write { + sector: sector_start, + count: sector_count, + buffer: pages, + }; + self.commit_request(write_req)?; + + let bytes_written = data_offset; + nwritten += bytes_written; + remaining_data = &remaining_data[bytes_written..]; + sector_start += sector_count; + first_sector_offset = 0; + } + + Ok(nwritten) + } } pub enum BlockDeviceRequest<'lt> { From d59a550880297abbe5447b10e2214f4ad822cdec Mon Sep 17 00:00:00 2001 From: Heinz Date: Sat, 26 Jul 2025 16:33:52 +0800 Subject: [PATCH 03/17] feat(fs): impl remove file and dir. --- src/fs/ext4.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index f7ca7578..b321fbf1 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -4,10 +4,10 @@ use crate::{ io::{Buffer, ByteBuffer, Stream}, kernel::{ block::BlockDevice, - constants::{EIO, S_IFDIR, S_IFREG}, + constants::{EIO, EISDIR, S_IFDIR, S_IFREG}, timer::Instant, vfs::{ - dentry::Dentry, + dentry::{dcache, Dentry}, inode::{define_struct_inode, AtomicNlink, Ino, Inode, InodeData, Mode, WriteOffset}, mount::{register_filesystem, Mount, MountCreator}, s_isdir, s_isreg, @@ -26,7 +26,7 @@ use another_ext4::{ Block, BlockDevice as Ext4BlockDeviceTrait, Ext4, FileType, InodeMode, PBlockId, }; use eonix_runtime::task::Task; -use eonix_sync::RwLock; +use eonix_sync::{AsProofMut, ProofMut, RwLock}; pub struct Ext4BlockDevice { device: Arc, @@ -292,6 +292,37 @@ impl DirInode { self.size.fetch_add(1, Ordering::Relaxed); *self.mtime.lock() = now; } + + fn unlink( + &self, + file: &Arc, + decrease_size: bool, + _dir_lock: ProofMut<()>, + _file_lock: ProofMut<()>, + ) -> KResult<()> { + let now = Instant::now(); + + // SAFETY: `file_lock` has done the synchronization + if file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 { + return Err(EISDIR); + } + + if decrease_size { + // SAFETY: `dir_lock` has done the synchronization + self.size.fetch_sub(1, Ordering::Relaxed); + } + + *self.mtime.lock() = now; + + // The last reference to the inode is held by some dentry + // and will be released when the dentry is released + + // SAFETY: `file_lock` has done the synchronization + file.nlink.fetch_sub(1, Ordering::Relaxed); + *file.ctime.lock() = now; + + Ok(()) + } } impl Inode for DirInode { @@ -437,6 +468,30 @@ impl Inode for DirInode { self.link(newdir.as_ref()); at.save_dir(newdir) } + + fn unlink(&self, at: &Arc) -> KResult<()> { + let dir_lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + let file = at.get_inode()?; + + let name = at.get_name(); + let name = String::from_utf8_lossy(&name); + let file_lock = Task::block_on(file.rwsem.write()); + + if file.is_dir() { + let _ = ext4fs.inner.rmdir(self.ino as u32, &name); + } else { + let _ = ext4fs.inner.unlink(self.ino as u32, &name); + } + + self.unlink(&file, true, dir_lock.prove_mut(), file_lock.prove_mut())?; + dcache::d_remove(at); + + Ok(()) + } } struct Ext4MountCreator; From 5c4016615ac79f980c070514acada82ade8f188f Mon Sep 17 00:00:00 2001 From: Heinz Date: Sat, 26 Jul 2025 18:06:31 +0800 Subject: [PATCH 04/17] fix(fs): fix some informations --- src/fs/ext4.rs | 121 ++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 56 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index b321fbf1..cb4bc136 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -4,7 +4,7 @@ use crate::{ io::{Buffer, ByteBuffer, Stream}, kernel::{ block::BlockDevice, - constants::{EIO, EISDIR, S_IFDIR, S_IFREG}, + constants::{EIO, S_IFDIR, S_IFREG}, timer::Instant, vfs::{ dentry::{dcache, Dentry}, @@ -26,7 +26,7 @@ use another_ext4::{ Block, BlockDevice as Ext4BlockDeviceTrait, Ext4, FileType, InodeMode, PBlockId, }; use eonix_runtime::task::Task; -use eonix_sync::{AsProofMut, ProofMut, RwLock}; +use eonix_sync::RwLock; pub struct Ext4BlockDevice { device: Arc, @@ -87,18 +87,27 @@ impl Ext4Fs { icache.get(&ino).cloned().map(Ext4Inode::into_inner) } - fn update_modify_inode(&self, ino: u64, size: u64, mtime: u32) { + fn modify_inode_stat(&self, ino: u32, size: Option, mtime: u32) { + let _ = self + .inner + .setattr(ino, None, None, None, size, None, Some(mtime), None, None); + } + + fn create_inode_stat(&self, parent: u32, child: u32, mtime: u32) { let _ = self.inner.setattr( - ino as u32, + parent, + None, None, None, None, - Some(size), None, Some(mtime), None, None, ); + let _ = self + .inner + .setattr(child, None, None, None, None, None, Some(mtime), None, None); } fn get_or_insert( @@ -262,7 +271,11 @@ impl Inode for FileInode { let new_size = (offset + total_written) as u64; self.size .store(offset as u64 + total_written as u64, Ordering::Relaxed); - ext4fs.update_modify_inode(self.ino, new_size, mtime.since_epoch().as_secs() as u32); + ext4fs.modify_inode_stat( + self.ino as u32, + Some(new_size), + mtime.since_epoch().as_secs() as u32, + ); Ok(total_written) } @@ -274,54 +287,44 @@ impl Inode for FileInode { } impl DirInode { - fn new(idata: InodeData) -> Arc { - let inode = Arc::new(Self { idata }); - - inode + fn new(ino: Ino, vfs: Weak, mode: Mode) -> Arc { + Arc::new_cyclic(|_| DirInode { + idata: { + let inode_data = InodeData::new(ino, vfs); + inode_data + .mode + .store(S_IFDIR | (mode & 0o777), Ordering::Relaxed); + inode_data.nlink.store(2, Ordering::Relaxed); + inode_data.size.store(4096, Ordering::Relaxed); + inode_data + }, + }) } - fn link(&self, file: &dyn Inode) { - let now = Instant::now(); + fn update_time(&self, time: Instant) { + *self.ctime.lock() = time; + *self.mtime.lock() = time; + } - // SAFETY: Only `unlink` will do something based on `nlink` count - // No need to synchronize here - file.nlink.fetch_add(1, Ordering::Relaxed); - *self.ctime.lock() = now; + fn update_child_time(&self, child: &dyn Inode, time: Instant) { + self.update_time(time); + *child.ctime.lock() = time; + *child.mtime.lock() = time; + } - // SAFETY: `rwsem` has done the synchronization + fn link_file(&self) { + // TODO self.size.fetch_add(1, Ordering::Relaxed); - *self.mtime.lock() = now; } - fn unlink( - &self, - file: &Arc, - decrease_size: bool, - _dir_lock: ProofMut<()>, - _file_lock: ProofMut<()>, - ) -> KResult<()> { - let now = Instant::now(); - - // SAFETY: `file_lock` has done the synchronization - if file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 { - return Err(EISDIR); - } - - if decrease_size { - // SAFETY: `dir_lock` has done the synchronization - self.size.fetch_sub(1, Ordering::Relaxed); - } - - *self.mtime.lock() = now; - - // The last reference to the inode is held by some dentry - // and will be released when the dentry is released - - // SAFETY: `file_lock` has done the synchronization - file.nlink.fetch_sub(1, Ordering::Relaxed); - *file.ctime.lock() = now; + fn link_dir(&self) { + // TODO + self.nlink.fetch_add(1, Ordering::Relaxed); + self.size.fetch_add(1, Ordering::Relaxed); + } - Ok(()) + fn unlink_dir(&self) { + self.nlink.fetch_sub(1, Ordering::Relaxed); } } @@ -436,11 +439,10 @@ impl Inode for DirInode { let file = FileInode::new(new_ino as u64, self.vfs.clone(), mode); let now = Instant::now(); + self.update_child_time(file.as_ref(), now); + self.link_file(); - *self.ctime.lock() = now; - // SAFETY: `rwsem` has done the synchronization - self.size.fetch_add(1, Ordering::Relaxed); - *self.mtime.lock() = now; + ext4fs.create_inode_stat(self.ino as u32, new_ino, now.since_epoch().as_secs() as u32); at.save_reg(file) } @@ -463,14 +465,18 @@ impl Inode for DirInode { ) .unwrap(); - let newdir = DirInode::new(InodeData::new(new_ino as u64, self.vfs.clone())); + let new_dir = DirInode::new(new_ino as u64, self.vfs.clone(), mode); + let now = Instant::now(); + self.update_child_time(new_dir.as_ref(), now); + self.link_dir(); + + ext4fs.create_inode_stat(self.ino as u32, new_ino, now.since_epoch().as_secs() as u32); - self.link(newdir.as_ref()); - at.save_dir(newdir) + at.save_dir(new_dir) } fn unlink(&self, at: &Arc) -> KResult<()> { - let dir_lock = Task::block_on(self.rwsem.write()); + let _dir_lock = Task::block_on(self.rwsem.write()); let vfs = self.vfs.upgrade().ok_or(EIO)?; let ext4fs = vfs.as_any().downcast_ref::().unwrap(); @@ -479,15 +485,18 @@ impl Inode for DirInode { let name = at.get_name(); let name = String::from_utf8_lossy(&name); - let file_lock = Task::block_on(file.rwsem.write()); + let _file_lock = Task::block_on(file.rwsem.write()); if file.is_dir() { let _ = ext4fs.inner.rmdir(self.ino as u32, &name); + self.unlink_dir(); } else { let _ = ext4fs.inner.unlink(self.ino as u32, &name); } + let now = Instant::now(); + self.update_time(now); + ext4fs.modify_inode_stat(self.ino as u32, None, now.since_epoch().as_secs() as u32); - self.unlink(&file, true, dir_lock.prove_mut(), file_lock.prove_mut())?; dcache::d_remove(at); Ok(()) From 1d1a0257ba9c895b5eb54d15ddf7d3f397fb00df Mon Sep 17 00:00:00 2001 From: Heinz Date: Tue, 29 Jul 2025 23:12:06 +0800 Subject: [PATCH 05/17] feat(fs): impl rename --- src/fs/ext4.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index cb4bc136..39df849f 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -4,11 +4,14 @@ use crate::{ io::{Buffer, ByteBuffer, Stream}, kernel::{ block::BlockDevice, - constants::{EIO, S_IFDIR, S_IFREG}, + constants::{EEXIST, EINVAL, EIO, ENOSYS, S_IFDIR, S_IFREG}, timer::Instant, vfs::{ dentry::{dcache, Dentry}, - inode::{define_struct_inode, AtomicNlink, Ino, Inode, InodeData, Mode, WriteOffset}, + inode::{ + define_struct_inode, AtomicNlink, Ino, Inode, InodeData, Mode, RenameData, + WriteOffset, + }, mount::{register_filesystem, Mount, MountCreator}, s_isdir, s_isreg, vfs::Vfs, @@ -27,6 +30,7 @@ use another_ext4::{ }; use eonix_runtime::task::Task; use eonix_sync::RwLock; +use xmas_elf::dynamic::FLAG_1_NOW; pub struct Ext4BlockDevice { device: Arc, @@ -110,6 +114,20 @@ impl Ext4Fs { .setattr(child, None, None, None, None, None, Some(mtime), None, None); } + fn chmod_stat(&self, ino: u32, new_mode: u16, ctime: u32) { + let _ = self.inner.setattr( + ino, + Some(InodeMode::from_bits_retain(new_mode.try_into().unwrap())), + None, + None, + None, + None, + None, + Some(ctime), + None, + ); + } + fn get_or_insert( &self, icache: &mut BTreeMap, @@ -280,6 +298,28 @@ impl Inode for FileInode { Ok(total_written) } + fn chmod(&self, mode: Mode) -> KResult<()> { + let _lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + let old_mode = self.mode.load(Ordering::Relaxed); + let new_mode = (old_mode & !0o777) | (mode & 0o777); + + let now = Instant::now(); + ext4fs.chmod_stat( + self.ino as u32, + new_mode as u16, + now.since_epoch().as_secs() as u32, + ); + + // SAFETY: `rwsem` has done the synchronization + self.mode.store(new_mode, Ordering::Relaxed); + *self.ctime.lock() = now; + + Ok(()) + } + // TODO fn truncate(&self, length: usize) -> KResult<()> { Ok(()) @@ -313,12 +353,10 @@ impl DirInode { } fn link_file(&self) { - // TODO self.size.fetch_add(1, Ordering::Relaxed); } fn link_dir(&self) { - // TODO self.nlink.fetch_add(1, Ordering::Relaxed); self.size.fetch_add(1, Ordering::Relaxed); } @@ -501,6 +539,75 @@ impl Inode for DirInode { Ok(()) } + + fn chmod(&self, mode: Mode) -> KResult<()> { + let _lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + let old_mode = self.mode.load(Ordering::Relaxed); + let new_mode = (old_mode & !0o777) | (mode & 0o777); + + let now = Instant::now(); + ext4fs.chmod_stat( + self.ino as u32, + new_mode as u16, + now.since_epoch().as_secs() as u32, + ); + + // SAFETY: `rwsem` has done the synchronization + self.mode.store(new_mode, Ordering::Relaxed); + *self.ctime.lock() = now; + + Ok(()) + } + + fn rename(&self, rename_data: RenameData) -> KResult<()> { + let RenameData { + old_dentry, + new_dentry, + new_parent, + is_exchange, + no_replace, + vfs, + } = rename_data; + + if is_exchange { + println_warn!("Ext4Fs does not support exchange rename for now"); + return Err(ENOSYS); + } + + // TODO: may need another lock + let _lock = Task::block_on(self.rwsem.write()); + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + let old_file = old_dentry.get_inode()?; + let new_file = new_dentry.get_inode(); + if no_replace && new_file.is_ok() { + return Err(EEXIST); + } + + let name = old_dentry.name(); + let name = core::str::from_utf8(&*name).map_err(|_| EINVAL)?; + let new_name = new_dentry.name(); + let new_name = core::str::from_utf8(&*new_name).map_err(|_| EINVAL)?; + + ext4fs + .inner + .rename(self.ino as u32, name, new_parent.ino as u32, new_name) + .map_err(|err| err.code() as u32)?; + + // TODO: may need more operations + let now = Instant::now(); + *self.mtime.lock() = now; + *old_file.ctime.lock() = now; + self.size.fetch_sub(1, Ordering::Relaxed); + + Task::block_on(dcache::d_exchange(old_dentry, new_dentry)); + + Ok(()) + } } struct Ext4MountCreator; From 22458ed33cd225123a8a23b74ae86c32984d2943 Mon Sep 17 00:00:00 2001 From: Heinz Date: Tue, 29 Jul 2025 23:34:09 +0800 Subject: [PATCH 06/17] fix(fs): fix rename's metadata --- src/fs/ext4.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 39df849f..25810bdf 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -30,7 +30,6 @@ use another_ext4::{ }; use eonix_runtime::task::Task; use eonix_sync::RwLock; -use xmas_elf::dynamic::FLAG_1_NOW; pub struct Ext4BlockDevice { device: Arc, @@ -600,9 +599,24 @@ impl Inode for DirInode { // TODO: may need more operations let now = Instant::now(); - *self.mtime.lock() = now; *old_file.ctime.lock() = now; - self.size.fetch_sub(1, Ordering::Relaxed); + *self.mtime.lock() = now; + + let same_parent = Arc::as_ptr(&new_parent) == &raw const *self; + if !same_parent { + *new_parent.mtime.lock() = now; + if old_file.is_dir() { + self.nlink.fetch_sub(1, Ordering::Relaxed); + new_parent.nlink.fetch_add(1, Ordering::Relaxed); + } + } + + if let Ok(replaced_file) = new_dentry.get_inode() { + if !no_replace { + *replaced_file.ctime.lock() = now; + replaced_file.nlink.fetch_sub(1, Ordering::Relaxed); + } + } Task::block_on(dcache::d_exchange(old_dentry, new_dentry)); From 806c4fe0acfd8172c62e0111aee4544a8b6a05f5 Mon Sep 17 00:00:00 2001 From: Heinz Date: Thu, 31 Jul 2025 14:49:30 +0800 Subject: [PATCH 07/17] fix(fs): fix ext4's write offset update --- src/fs/ext4.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 25810bdf..1923d218 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -266,10 +266,14 @@ impl Inode for FileInode { let mut temp_buf = vec![0u8; 4096]; let mut total_written = 0; + let mut store_new_end = None; let offset = match offset { WriteOffset::Position(offset) => offset, // TODO: here need to add some operate - WriteOffset::End(end) => *end, + WriteOffset::End(end) => { + store_new_end = Some(end); + self.size.load(Ordering::Relaxed) as usize + } }; while let Some(data) = stream.poll_data(&mut temp_buf)? { @@ -283,6 +287,9 @@ impl Inode for FileInode { } } + if let Some(store_end) = store_new_end { + *store_end = offset + total_written; + } let mtime = Instant::now(); *self.mtime.lock() = mtime; let new_size = (offset + total_written) as u64; From 46f3a907ea8aa792150206f895cdad79c8cad7c0 Mon Sep 17 00:00:00 2001 From: Heinz Date: Thu, 31 Jul 2025 16:03:12 +0800 Subject: [PATCH 08/17] feat(syscall): impl copy_file_range's base function --- Cargo.lock | 2 +- src/kernel/syscall.rs | 6 ++ src/kernel/syscall/file_rw.rs | 122 +++++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04b25a21..5503571a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,7 +22,7 @@ checksum = "c1c330e503236d0b06386ae6cc42a513ef1ccc23c52b603c1b52f018564faf44" [[package]] name = "another_ext4" version = "0.1.0" -source = "git+https://github.com/SMS-Derfflinger/another_ext4?branch=main#ed6d91718db721eb4a744483c289cc44a6f34bf4" +source = "git+https://github.com/SMS-Derfflinger/another_ext4?branch=main#01a7fbaaf9135103b37cf82484f3a2cb1fa0d279" dependencies = [ "bitflags", "log", diff --git a/src/kernel/syscall.rs b/src/kernel/syscall.rs index b48ea2e2..d7dcc575 100644 --- a/src/kernel/syscall.rs +++ b/src/kernel/syscall.rs @@ -62,6 +62,12 @@ impl SyscallRetVal for usize { } } +impl SyscallRetVal for isize { + fn into_retval(self) -> Option { + Some(self as usize) + } +} + impl SyscallRetVal for SyscallNoReturn { fn into_retval(self) -> Option { None diff --git a/src/kernel/syscall/file_rw.rs b/src/kernel/syscall/file_rw.rs index 777e19c5..2836f912 100644 --- a/src/kernel/syscall/file_rw.rs +++ b/src/kernel/syscall/file_rw.rs @@ -1,5 +1,5 @@ use super::FromSyscallArg; -use crate::io::IntoStream; +use crate::io::{ByteBuffer, IntoStream}; use crate::kernel::constants::{ EBADF, EFAULT, EINVAL, ENOENT, ENOTDIR, SEEK_CUR, SEEK_END, SEEK_SET, S_IFBLK, S_IFCHR, }; @@ -90,6 +90,126 @@ fn openat(dirfd: FD, pathname: *const u8, flags: OpenFlags, mode: u32) -> KResul thread.files.open(&dentry, flags, mode) } +#[eonix_macros::define_syscall(SYS_FTRUNCATE64)] +fn truncate() -> KResult<()> { + Ok(()) +} + +/// TODO: +/// truncate syscall... haowuyu +/// off_in and off_out's not null case, kernel need access user's address +/// check input file's size +/// real read and write operation +/// update information +#[eonix_macros::define_syscall(SYS_COPY_FILE_RANGE)] +fn copy_file_range( + fd_in: FD, + off_in: *mut i64, + fd_out: FD, + off_out: *mut i64, + len: usize, + _flags: u32, +) -> KResult { + println!("copy_file_range, len: {}", len); + if len == 0 { + return Ok(0); + } + + let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); + let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); + + /*let (inode_in, inode_out) = match (&file_in.file_type, &file_out.file_type) { + (FileType::Inode(in_file), FileType::Inode(out_file)) => (in_file, out_file), + _ => return Err(EINVAL), + };*/ + + //let mut in_cursor = Task::block_on(inode_in.cursor.lock()); + //let mut out_cursor = Task::block_on(inode_out.cursor.lock()); + //let mut input_offset = *in_cursor; + //let mut output_offset = *out_cursor; + //let use_file_offset_in; + //let use_file_offset_out; + + /*if off_in.is_null() { + input_offset = *in_cursor; + use_file_offset_in = true; + } else { + //input_offset = copy_from_user(off_in)?; + use_file_offset_in = false; + } + + if off_out.is_null() { + output_offset = *out_cursor; + use_file_offset_out = true; + } else { + //output_offset = copy_from_user(off_out)?; + use_file_offset_out = false; + } + println!("off_in: {}", use_file_offset_in); + println!("off_out: {}", use_file_offset_out);*/ + + // 检查输入文件大小 + //let file_size = inode_in.dentry.size()?; + //if input_offset >= file_size as i64 { + // return Ok(0); + //} + + let mut total_copied = 0usize; + let mut remaining = len; + let buffer_size = 4096; + let mut buffer = vec![0u8; buffer_size]; + + while remaining > 0 + /*&& input_offset < file_size as i64*/ + { + let to_read = core::cmp::min(remaining, buffer_size); + //(file_size as i64 - input_offset) as usize, + + if to_read == 0 { + break; + } + + let mut byte_buffer = ByteBuffer::new(&mut buffer[..to_read]); + let read_bytes = Task::block_on(file_in.read(&mut byte_buffer))?; + if read_bytes == 0 { + break; + } + + //println!("copy_file_range try write, offset: {}", output_offset); + let mut stream = (&buffer[..read_bytes]).into_stream(); + let written_bytes = Task::block_on(file_out.write(&mut stream))?; + println!("copy_file_range write succeed"); + if written_bytes == 0 { + break; + } + + //input_offset += written_bytes; + //output_offset += written_bytes; + total_copied += written_bytes; + remaining -= written_bytes; + + if written_bytes < read_bytes { + break; + } + } + + /*if use_file_offset_in { + *in_cursor = input_offset; + } else { + //copy_to_user(off_in, &input_offset)?; + } + + if use_file_offset_out { + *out_cursor = output_offset; + } else { + //copy_to_user(off_out, &output_offset)?; + }*/ + println!("copy_file_range succeed, len: {}", len); + println!(); + + Ok(total_copied as isize) +} + #[cfg(target_arch = "x86_64")] #[eonix_macros::define_syscall(SYS_OPEN)] fn open(path: *const u8, flags: OpenFlags, mode: u32) -> KResult { From e15776170e28984dbdf85d5c1666a8154a0fcdc4 Mon Sep 17 00:00:00 2001 From: Heinz Date: Thu, 31 Jul 2025 21:04:09 +0800 Subject: [PATCH 09/17] feat(syscall): impl copy_file_range's boundary case check, and add ext4's truncate's temporary implement --- src/fs/ext4.rs | 9 ++++++++ src/kernel/syscall/file_rw.rs | 30 +++++++++++++++--------- src/kernel/vfs/dentry.rs | 4 ++++ src/kernel/vfs/file.rs | 43 +++++++++++++++++++++++++++++++++++ src/kernel/vfs/inode.rs | 4 ++++ 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 1923d218..5dde3c1e 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -328,6 +328,15 @@ impl Inode for FileInode { // TODO fn truncate(&self, length: usize) -> KResult<()> { + let _lock = Task::block_on(self.rwsem.write()); + + //let vfs = self.vfs.upgrade().ok_or(EIO)?; + //let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + //ext4fs.inner.truncate(); + + self.size.store(length as u64, Ordering::Relaxed); + *self.mtime.lock() = Instant::now(); Ok(()) } } diff --git a/src/kernel/syscall/file_rw.rs b/src/kernel/syscall/file_rw.rs index 2836f912..fd5b7ba2 100644 --- a/src/kernel/syscall/file_rw.rs +++ b/src/kernel/syscall/file_rw.rs @@ -21,6 +21,7 @@ use crate::{ prelude::*, }; use alloc::sync::Arc; +use eonix_log::println; use eonix_runtime::task::Task; use posix_types::ctypes::{Long, PtrT}; use posix_types::namei::RenameFlags; @@ -76,6 +77,21 @@ fn read(fd: FD, buffer: *mut u8, bufsize: usize) -> KResult { Task::block_on(thread.files.get(fd).ok_or(EBADF)?.read(&mut buffer)) } +#[eonix_macros::define_syscall(SYS_PREAD64)] +fn pread(fd: FD, buffer: *mut u8, bufsize: usize, offset: usize) -> KResult { + if thread.files.get(fd).ok_or(EBADF)?.size()? == 0 { + return Ok(0); + } + let mut buffer = UserBuffer::new(buffer, bufsize)?; + let nread = thread + .files + .get(fd) + .ok_or(EBADF)? + .read_at(&mut buffer, offset)?; + println!("pread offset: {}, nread: {}", offset, nread); + Ok(nread) +} + #[eonix_macros::define_syscall(SYS_WRITE)] fn write(fd: FD, buffer: *const u8, count: usize) -> KResult { let buffer = CheckedUserPointer::new(buffer, count)?; @@ -91,8 +107,8 @@ fn openat(dirfd: FD, pathname: *const u8, flags: OpenFlags, mode: u32) -> KResul } #[eonix_macros::define_syscall(SYS_FTRUNCATE64)] -fn truncate() -> KResult<()> { - Ok(()) +fn ftruncate(fd: FD, new_size: usize) -> KResult<()> { + thread.files.get(fd).ok_or(EBADF)?.truncate(new_size) } /// TODO: @@ -148,12 +164,6 @@ fn copy_file_range( println!("off_in: {}", use_file_offset_in); println!("off_out: {}", use_file_offset_out);*/ - // 检查输入文件大小 - //let file_size = inode_in.dentry.size()?; - //if input_offset >= file_size as i64 { - // return Ok(0); - //} - let mut total_copied = 0usize; let mut remaining = len; let buffer_size = 4096; @@ -178,7 +188,6 @@ fn copy_file_range( //println!("copy_file_range try write, offset: {}", output_offset); let mut stream = (&buffer[..read_bytes]).into_stream(); let written_bytes = Task::block_on(file_out.write(&mut stream))?; - println!("copy_file_range write succeed"); if written_bytes == 0 { break; } @@ -189,7 +198,7 @@ fn copy_file_range( remaining -= written_bytes; if written_bytes < read_bytes { - break; + return Err(u32::MAX); } } @@ -205,7 +214,6 @@ fn copy_file_range( //copy_to_user(off_out, &output_offset)?; }*/ println!("copy_file_range succeed, len: {}", len); - println!(); Ok(total_copied as isize) } diff --git a/src/kernel/vfs/dentry.rs b/src/kernel/vfs/dentry.rs index bd516bf2..ad1aeac4 100644 --- a/src/kernel/vfs/dentry.rs +++ b/src/kernel/vfs/dentry.rs @@ -405,6 +405,10 @@ impl Dentry { } impl Dentry { + pub fn size(&self) -> usize { + self.get_inode().unwrap().size() + } + pub fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { let inode = self.get_inode()?; diff --git a/src/kernel/vfs/file.rs b/src/kernel/vfs/file.rs index 741fa87a..03629334 100644 --- a/src/kernel/vfs/file.rs +++ b/src/kernel/vfs/file.rs @@ -11,6 +11,7 @@ use crate::{ task::Thread, terminal::{Terminal, TerminalIORequest}, user::{UserPointer, UserPointerMut}, + vfs::inode::Ino, CharDevice, }, prelude::*, @@ -344,6 +345,9 @@ impl InodeFile { } fn read(&self, buffer: &mut dyn Buffer) -> KResult { + if self.size() == 0 { + return Ok(0); + } if !self.read { return Err(EBADF); } @@ -356,6 +360,16 @@ impl InodeFile { Ok(nread) } + // TODO: error handle + fn read_at(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { + if !self.read { + return Err(EBADF); + } + + let nread = self.dentry.read(buffer, offset)?; + Ok(nread) + } + fn getdents64(&self, buffer: &mut dyn Buffer) -> KResult<()> { let mut cursor = Task::block_on(self.cursor.lock()); @@ -414,6 +428,14 @@ impl InodeFile { *cursor += nread; Ok(()) } + + fn size(&self) -> usize { + self.dentry.size() + } + + fn truncate(&self, new_size: usize) -> KResult<()> { + self.dentry.truncate(new_size) + } } impl TerminalFile { @@ -466,6 +488,13 @@ impl FileType { } } + pub fn read_at(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { + match self { + FileType::Inode(inode) => inode.read_at(buffer, offset), + _ => Err(EBADF), + } + } + // TODO // /// Read from the file into the given buffers. // /// @@ -498,6 +527,20 @@ impl FileType { } } + pub fn size(&self) -> KResult { + match self { + FileType::Inode(inode) => Ok(inode.size()), + _ => Err(EINVAL), + } + } + + pub fn truncate(&self, new_size: usize) -> KResult<()> { + match self { + FileType::Inode(inode) => inode.truncate(new_size), + _ => Err(EINVAL), + } + } + pub fn getdents(&self, buffer: &mut dyn Buffer) -> KResult<()> { match self { FileType::Inode(inode) => inode.getdents(buffer), diff --git a/src/kernel/vfs/inode.rs b/src/kernel/vfs/inode.rs index a70de927..986af030 100644 --- a/src/kernel/vfs/inode.rs +++ b/src/kernel/vfs/inode.rs @@ -94,6 +94,10 @@ pub struct RenameData<'a, 'b> { #[allow(unused_variables)] pub trait Inode: Send + Sync + InodeInner + Any { + fn size(&self) -> usize { + self.size.load(Ordering::Relaxed) as usize + } + fn is_dir(&self) -> bool { self.mode.load(Ordering::SeqCst) & S_IFDIR != 0 } From 86561a1ee6bc346c1916aead3bff4e9926f38d15 Mon Sep 17 00:00:00 2001 From: Heinz Date: Thu, 31 Jul 2025 22:40:35 +0800 Subject: [PATCH 10/17] feat(syscall): impl copy_file_range's complete functions, can pass os comp's all tests --- src/fs/ext4.rs | 10 ++-- src/kernel/syscall/file_rw.rs | 99 ++++++++++++++--------------------- src/kernel/user/dataflow.rs | 6 +++ src/kernel/vfs/file.rs | 19 +++++++ 4 files changed, 71 insertions(+), 63 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 5dde3c1e..752b26eb 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -330,10 +330,12 @@ impl Inode for FileInode { fn truncate(&self, length: usize) -> KResult<()> { let _lock = Task::block_on(self.rwsem.write()); - //let vfs = self.vfs.upgrade().ok_or(EIO)?; - //let ext4fs = vfs.as_any().downcast_ref::().unwrap(); - - //ext4fs.inner.truncate(); + if length == 0 { + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + let buffer = vec![0u8; 10]; + let _ = ext4fs.inner.write(self.ino as u32, 0, &buffer); + } self.size.store(length as u64, Ordering::Relaxed); *self.mtime.lock() = Instant::now(); diff --git a/src/kernel/syscall/file_rw.rs b/src/kernel/syscall/file_rw.rs index fd5b7ba2..2e08f34f 100644 --- a/src/kernel/syscall/file_rw.rs +++ b/src/kernel/syscall/file_rw.rs @@ -21,7 +21,6 @@ use crate::{ prelude::*, }; use alloc::sync::Arc; -use eonix_log::println; use eonix_runtime::task::Task; use posix_types::ctypes::{Long, PtrT}; use posix_types::namei::RenameFlags; @@ -88,7 +87,6 @@ fn pread(fd: FD, buffer: *mut u8, bufsize: usize, offset: usize) -> KResult KResult<()> { thread.files.get(fd).ok_or(EBADF)?.truncate(new_size) } -/// TODO: -/// truncate syscall... haowuyu -/// off_in and off_out's not null case, kernel need access user's address -/// check input file's size -/// real read and write operation -/// update information #[eonix_macros::define_syscall(SYS_COPY_FILE_RANGE)] fn copy_file_range( fd_in: FD, - off_in: *mut i64, + off_in: *mut usize, fd_out: FD, - off_out: *mut i64, + off_out: *mut usize, len: usize, _flags: u32, ) -> KResult { - println!("copy_file_range, len: {}", len); if len == 0 { return Ok(0); } @@ -134,66 +125,53 @@ fn copy_file_range( let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); - /*let (inode_in, inode_out) = match (&file_in.file_type, &file_out.file_type) { - (FileType::Inode(in_file), FileType::Inode(out_file)) => (in_file, out_file), - _ => return Err(EINVAL), - };*/ - - //let mut in_cursor = Task::block_on(inode_in.cursor.lock()); - //let mut out_cursor = Task::block_on(inode_out.cursor.lock()); - //let mut input_offset = *in_cursor; - //let mut output_offset = *out_cursor; - //let use_file_offset_in; - //let use_file_offset_out; - - /*if off_in.is_null() { - input_offset = *in_cursor; - use_file_offset_in = true; - } else { - //input_offset = copy_from_user(off_in)?; - use_file_offset_in = false; - } + let input_offset = match off_in.is_null() { + true => None, + false => { + let buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; + let offset = usize::from_le_bytes(buffer.as_slice().try_into().unwrap()); + Some(offset) + } + }; - if off_out.is_null() { - output_offset = *out_cursor; - use_file_offset_out = true; - } else { - //output_offset = copy_from_user(off_out)?; - use_file_offset_out = false; - } - println!("off_in: {}", use_file_offset_in); - println!("off_out: {}", use_file_offset_out);*/ + let output_offset = match off_out.is_null() { + true => None, + false => { + let buffer = UserBuffer::new(off_out as *mut u8, size_of::())?; + let offset = usize::from_le_bytes(buffer.as_slice().try_into().unwrap()); + Some(offset) + } + }; let mut total_copied = 0usize; let mut remaining = len; let buffer_size = 4096; let mut buffer = vec![0u8; buffer_size]; - while remaining > 0 - /*&& input_offset < file_size as i64*/ - { + while remaining > 0 { let to_read = core::cmp::min(remaining, buffer_size); - //(file_size as i64 - input_offset) as usize, - if to_read == 0 { break; } let mut byte_buffer = ByteBuffer::new(&mut buffer[..to_read]); - let read_bytes = Task::block_on(file_in.read(&mut byte_buffer))?; + let read_bytes = match input_offset { + Some(offset) => file_in.read_at(&mut byte_buffer, offset + total_copied)?, + None => Task::block_on(file_in.read(&mut byte_buffer))?, + }; if read_bytes == 0 { break; } - //println!("copy_file_range try write, offset: {}", output_offset); let mut stream = (&buffer[..read_bytes]).into_stream(); - let written_bytes = Task::block_on(file_out.write(&mut stream))?; + let written_bytes = match output_offset { + Some(offset) => file_out.write_at(&mut stream, offset + total_copied)?, + None => Task::block_on(file_out.write(&mut stream))?, + }; if written_bytes == 0 { break; } - //input_offset += written_bytes; - //output_offset += written_bytes; total_copied += written_bytes; remaining -= written_bytes; @@ -202,18 +180,21 @@ fn copy_file_range( } } - /*if use_file_offset_in { - *in_cursor = input_offset; - } else { - //copy_to_user(off_in, &input_offset)?; + match input_offset { + Some(offset) => { + let mut buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; + let _ = buffer.fill(&(offset + total_copied).to_le_bytes()); + } + None => (), } - if use_file_offset_out { - *out_cursor = output_offset; - } else { - //copy_to_user(off_out, &output_offset)?; - }*/ - println!("copy_file_range succeed, len: {}", len); + match output_offset { + Some(offset) => { + let mut buffer = UserBuffer::new(off_out as *mut u8, size_of::())?; + let _ = buffer.fill(&(offset + total_copied).to_le_bytes()); + } + None => (), + } Ok(total_copied as isize) } diff --git a/src/kernel/user/dataflow.rs b/src/kernel/user/dataflow.rs index 9e5ad9e3..e579afed 100644 --- a/src/kernel/user/dataflow.rs +++ b/src/kernel/user/dataflow.rs @@ -289,6 +289,12 @@ impl UserBuffer<'_> { fn remaining(&self) -> usize { self.size - self.cur } + + /// this is for comp test + pub fn as_slice(&self) -> &[u8] { + // SAFETY: the pointer's validity is checked in `new` + unsafe { core::slice::from_raw_parts(self.ptr.ptr, self.ptr.len) } + } } impl<'lt> Buffer for UserBuffer<'lt> { diff --git a/src/kernel/vfs/file.rs b/src/kernel/vfs/file.rs index 03629334..24dfc9f4 100644 --- a/src/kernel/vfs/file.rs +++ b/src/kernel/vfs/file.rs @@ -344,6 +344,15 @@ impl InodeFile { } } + fn write_at(&self, stream: &mut dyn Stream, offset: usize) -> KResult { + if !self.write { + return Err(EBADF); + } + + let nwrote = self.dentry.write(stream, WriteOffset::Position(offset))?; + Ok(nwrote) + } + fn read(&self, buffer: &mut dyn Buffer) -> KResult { if self.size() == 0 { return Ok(0); @@ -362,6 +371,9 @@ impl InodeFile { // TODO: error handle fn read_at(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { + if self.size() == 0 { + return Ok(0); + } if !self.read { return Err(EBADF); } @@ -520,6 +532,13 @@ impl FileType { } } + pub fn write_at(&self, stream: &mut dyn Stream, offset: usize) -> KResult { + match self { + FileType::Inode(inode) => inode.write_at(stream, offset), + _ => Err(EBADF), + } + } + pub fn seek(&self, option: SeekOption) -> KResult { match self { FileType::Inode(inode) => inode.seek(option), From 10df512d75193897a1a3bd383cc4334e828c13db Mon Sep 17 00:00:00 2001 From: Heinz Date: Thu, 31 Jul 2025 23:25:35 +0800 Subject: [PATCH 11/17] feat(syscall): add some syscall's temporary implement --- src/kernel/syscall.rs | 6 ++++++ src/kernel/syscall/procops.rs | 6 ++++++ src/kernel/syscall/sysinfo.rs | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/src/kernel/syscall.rs b/src/kernel/syscall.rs index d7dcc575..2bcfa853 100644 --- a/src/kernel/syscall.rs +++ b/src/kernel/syscall.rs @@ -56,6 +56,12 @@ impl SyscallRetVal for u32 { } } +impl SyscallRetVal for i32 { + fn into_retval(self) -> Option { + Some(self as usize) + } +} + impl SyscallRetVal for usize { fn into_retval(self) -> Option { Some(self) diff --git a/src/kernel/syscall/procops.rs b/src/kernel/syscall/procops.rs index 4b1189b0..9d4dc2bc 100644 --- a/src/kernel/syscall/procops.rs +++ b/src/kernel/syscall/procops.rs @@ -401,6 +401,12 @@ fn getgid() -> KResult { Ok(0) } +#[eonix_macros::define_syscall(SYS_GETEGID)] +fn getegid() -> KResult { + // All users are root for now. + Ok(0) +} + #[cfg(target_arch = "x86_64")] #[eonix_macros::define_syscall(SYS_GETGID32)] fn getgid32() -> KResult { diff --git a/src/kernel/syscall/sysinfo.rs b/src/kernel/syscall/sysinfo.rs index f954ef5a..0dc9e85f 100644 --- a/src/kernel/syscall/sysinfo.rs +++ b/src/kernel/syscall/sysinfo.rs @@ -163,3 +163,8 @@ fn times(tms: *mut TMS) -> KResult<()> { } pub fn keep_alive() {} + +#[eonix_macros::define_syscall(SYS_GETRANDOM)] +fn get_random() -> KResult { + Ok(114514) +} From dded5e184031ecedf0c2a064647102e19a9b506e Mon Sep 17 00:00:00 2001 From: Heinz Date: Sat, 2 Aug 2025 09:54:52 +0800 Subject: [PATCH 12/17] style(syscall): update copy file range syscall --- src/kernel/syscall/file_rw.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/kernel/syscall/file_rw.rs b/src/kernel/syscall/file_rw.rs index 2e08f34f..d929da7c 100644 --- a/src/kernel/syscall/file_rw.rs +++ b/src/kernel/syscall/file_rw.rs @@ -125,21 +125,21 @@ fn copy_file_range( let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); - let input_offset = match off_in.is_null() { - true => None, + let (input_offset, input_buffer) = match off_in.is_null() { + true => (None, None), false => { let buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; let offset = usize::from_le_bytes(buffer.as_slice().try_into().unwrap()); - Some(offset) + (Some(offset), Some(buffer)) } }; - let output_offset = match off_out.is_null() { - true => None, + let (output_offset, output_buffer) = match off_out.is_null() { + true => (None, None), false => { let buffer = UserBuffer::new(off_out as *mut u8, size_of::())?; let offset = usize::from_le_bytes(buffer.as_slice().try_into().unwrap()); - Some(offset) + (Some(offset), Some(buffer)) } }; @@ -180,20 +180,18 @@ fn copy_file_range( } } - match input_offset { - Some(offset) => { - let mut buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; + match (input_offset, input_buffer) { + (Some(offset), Some(mut buffer)) => { let _ = buffer.fill(&(offset + total_copied).to_le_bytes()); } - None => (), + _ => (), } - match output_offset { - Some(offset) => { - let mut buffer = UserBuffer::new(off_out as *mut u8, size_of::())?; + match (output_offset, output_buffer) { + (Some(offset), Some(mut buffer)) => { let _ = buffer.fill(&(offset + total_copied).to_le_bytes()); } - None => (), + _ => (), } Ok(total_copied as isize) From 2fedf6d43708a173bddfeeaf0965952e26a2af6d Mon Sep 17 00:00:00 2001 From: Heinz Date: Sat, 2 Aug 2025 11:24:35 +0800 Subject: [PATCH 13/17] feat(syscall): impl splice syscall for comp test --- src/kernel/syscall/file_rw.rs | 99 +++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/kernel/syscall/file_rw.rs b/src/kernel/syscall/file_rw.rs index d929da7c..5f0c0bb4 100644 --- a/src/kernel/syscall/file_rw.rs +++ b/src/kernel/syscall/file_rw.rs @@ -197,6 +197,105 @@ fn copy_file_range( Ok(total_copied as isize) } +#[eonix_macros::define_syscall(SYS_SPLICE)] +fn splice( + fd_in: FD, + off_in: *mut isize, + fd_out: FD, + off_out: *mut isize, + len: usize, + _flags: u32, +) -> KResult { + let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); + let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); + + let (input_offset, input_buffer) = match off_in.is_null() { + true => (None, None), + false => { + let buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; + let offset = isize::from_le_bytes(buffer.as_slice().try_into().unwrap()); + if offset > file_in.size()?.try_into().unwrap() { + println!("offset > size"); + return Ok(0); + } + if offset < 0 { + println!("offset < 0"); + return Ok(-1); + } + (Some(offset), Some(buffer)) + } + }; + + let (output_offset, output_buffer) = match off_out.is_null() { + true => (None, None), + false => { + let buffer = UserBuffer::new(off_out as *mut u8, size_of::())?; + let offset = isize::from_le_bytes(buffer.as_slice().try_into().unwrap()); + if offset < 0 { + return Ok(-1); + } + (Some(offset), Some(buffer)) + } + }; + + let mut total_copied = 0usize; + let mut remaining = len; + let buffer_size = 4096; + let mut buffer = vec![0u8; buffer_size]; + + while remaining > 0 { + let to_read = core::cmp::min(remaining, buffer_size); + if to_read == 0 { + break; + } + + let mut byte_buffer = ByteBuffer::new(&mut buffer[..to_read]); + let read_bytes = match input_offset { + Some(offset) => file_in.read_at(&mut byte_buffer, offset as usize + total_copied)?, + None => Task::block_on(file_in.read(&mut byte_buffer))?, + }; + if read_bytes == 0 { + break; + } + + let mut stream = (&buffer[..read_bytes]).into_stream(); + let written_bytes = match output_offset { + Some(offset) => file_out.write_at(&mut stream, offset as usize + total_copied)?, + None => Task::block_on(file_out.write(&mut stream))?, + }; + if written_bytes == 0 { + break; + } + + total_copied += written_bytes; + remaining -= written_bytes; + + if written_bytes < read_bytes { + return Ok(-1); + } + + if input_offset.is_none() { + break; + } + } + + match (input_offset, input_buffer) { + (Some(offset), Some(mut buffer)) => { + let _ = buffer.fill(&(offset + total_copied as isize).to_le_bytes()); + } + _ => (), + } + + match (output_offset, output_buffer) { + (Some(offset), Some(mut buffer)) => { + let _ = buffer.fill(&(offset + total_copied as isize).to_le_bytes()); + } + _ => (), + } + + Ok(total_copied as isize) +} + #[cfg(target_arch = "x86_64")] #[eonix_macros::define_syscall(SYS_OPEN)] fn open(path: *const u8, flags: OpenFlags, mode: u32) -> KResult { From ab85423ccfa98d2dafb33051779fab2f01efbd09 Mon Sep 17 00:00:00 2001 From: Heinz Date: Sat, 2 Aug 2025 11:39:50 +0800 Subject: [PATCH 14/17] refactor(syscall): refactor copy_file_range and splice syscall --- src/kernel/syscall/file_rw.rs | 143 +++++++++++----------------------- 1 file changed, 44 insertions(+), 99 deletions(-) diff --git a/src/kernel/syscall/file_rw.rs b/src/kernel/syscall/file_rw.rs index 5f0c0bb4..ee3557d3 100644 --- a/src/kernel/syscall/file_rw.rs +++ b/src/kernel/syscall/file_rw.rs @@ -4,6 +4,7 @@ use crate::kernel::constants::{ EBADF, EFAULT, EINVAL, ENOENT, ENOTDIR, SEEK_CUR, SEEK_END, SEEK_SET, S_IFBLK, S_IFCHR, }; use crate::kernel::task::Thread; +use crate::kernel::vfs::file::File; use crate::kernel::vfs::filearray::FD; use crate::{ io::{Buffer, BufferFill}, @@ -109,117 +110,23 @@ fn ftruncate(fd: FD, new_size: usize) -> KResult<()> { thread.files.get(fd).ok_or(EBADF)?.truncate(new_size) } -#[eonix_macros::define_syscall(SYS_COPY_FILE_RANGE)] -fn copy_file_range( - fd_in: FD, - off_in: *mut usize, - fd_out: FD, - off_out: *mut usize, - len: usize, - _flags: u32, -) -> KResult { - if len == 0 { - return Ok(0); - } - - let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); - let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); - - let (input_offset, input_buffer) = match off_in.is_null() { - true => (None, None), - false => { - let buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; - let offset = usize::from_le_bytes(buffer.as_slice().try_into().unwrap()); - (Some(offset), Some(buffer)) - } - }; - - let (output_offset, output_buffer) = match off_out.is_null() { - true => (None, None), - false => { - let buffer = UserBuffer::new(off_out as *mut u8, size_of::())?; - let offset = usize::from_le_bytes(buffer.as_slice().try_into().unwrap()); - (Some(offset), Some(buffer)) - } - }; - - let mut total_copied = 0usize; - let mut remaining = len; - let buffer_size = 4096; - let mut buffer = vec![0u8; buffer_size]; - - while remaining > 0 { - let to_read = core::cmp::min(remaining, buffer_size); - if to_read == 0 { - break; - } - - let mut byte_buffer = ByteBuffer::new(&mut buffer[..to_read]); - let read_bytes = match input_offset { - Some(offset) => file_in.read_at(&mut byte_buffer, offset + total_copied)?, - None => Task::block_on(file_in.read(&mut byte_buffer))?, - }; - if read_bytes == 0 { - break; - } - - let mut stream = (&buffer[..read_bytes]).into_stream(); - let written_bytes = match output_offset { - Some(offset) => file_out.write_at(&mut stream, offset + total_copied)?, - None => Task::block_on(file_out.write(&mut stream))?, - }; - if written_bytes == 0 { - break; - } - - total_copied += written_bytes; - remaining -= written_bytes; - - if written_bytes < read_bytes { - return Err(u32::MAX); - } - } - - match (input_offset, input_buffer) { - (Some(offset), Some(mut buffer)) => { - let _ = buffer.fill(&(offset + total_copied).to_le_bytes()); - } - _ => (), - } - - match (output_offset, output_buffer) { - (Some(offset), Some(mut buffer)) => { - let _ = buffer.fill(&(offset + total_copied).to_le_bytes()); - } - _ => (), - } - - Ok(total_copied as isize) -} - -#[eonix_macros::define_syscall(SYS_SPLICE)] -fn splice( - fd_in: FD, +fn do_copy_or_splice( + file_in: Arc, off_in: *mut isize, - fd_out: FD, + file_out: Arc, off_out: *mut isize, len: usize, - _flags: u32, + is_splice: bool, ) -> KResult { - let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); - let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); - let (input_offset, input_buffer) = match off_in.is_null() { true => (None, None), false => { let buffer = UserBuffer::new(off_in as *mut u8, size_of::())?; let offset = isize::from_le_bytes(buffer.as_slice().try_into().unwrap()); if offset > file_in.size()?.try_into().unwrap() { - println!("offset > size"); return Ok(0); } if offset < 0 { - println!("offset < 0"); return Ok(-1); } (Some(offset), Some(buffer)) @@ -274,7 +181,7 @@ fn splice( return Ok(-1); } - if input_offset.is_none() { + if is_splice && input_offset.is_none() { break; } } @@ -296,6 +203,44 @@ fn splice( Ok(total_copied as isize) } +#[eonix_macros::define_syscall(SYS_COPY_FILE_RANGE)] +fn copy_file_range( + fd_in: FD, + off_in: *mut isize, + fd_out: FD, + off_out: *mut isize, + len: usize, + _flags: u32, +) -> KResult { + if len == 0 { + return Ok(0); + } + + let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); + let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); + + do_copy_or_splice(file_in, off_in, file_out, off_out, len, false) +} + +#[eonix_macros::define_syscall(SYS_SPLICE)] +fn splice( + fd_in: FD, + off_in: *mut isize, + fd_out: FD, + off_out: *mut isize, + len: usize, + _flags: u32, +) -> KResult { + if len == 0 { + return Ok(0); + } + + let file_in = thread.files.get(fd_in).ok_or(EBADF)?.clone(); + let file_out = thread.files.get(fd_out).ok_or(EBADF)?.clone(); + + do_copy_or_splice(file_in, off_in, file_out, off_out, len, true) +} + #[cfg(target_arch = "x86_64")] #[eonix_macros::define_syscall(SYS_OPEN)] fn open(path: *const u8, flags: OpenFlags, mode: u32) -> KResult { From db1caebde5063a6cb0c2c5f93c4689d5ece5e0e5 Mon Sep 17 00:00:00 2001 From: Heinz Date: Mon, 4 Aug 2025 22:59:58 +0800 Subject: [PATCH 15/17] feat(fs): partial work for ext4's page cache Fix page cache's bug, add size check in read function. Add page cache's base operations for ext4, but the cachepage will not be dropped until kernel stop, so we need to call fsync function manually, consider use some strategy such as LRU. --- src/fs/ext4.rs | 53 +++++++++++++++++------------ src/fs/fat32.rs | 8 ++--- src/fs/tmpfs.rs | 10 +++--- src/kernel/mem.rs | 2 +- src/kernel/mem/page_cache.rs | 66 +++++++++++++++++++++++++++++++----- src/kernel/vfs/inode.rs | 2 +- 6 files changed, 101 insertions(+), 40 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 0853e69a..763f1caa 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -1,6 +1,6 @@ use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; -use crate::kernel::mem::{PageCache, PageCacheBackend}; +use crate::kernel::mem::{CachePage, CachePageStream, PageCache, PageCacheBackend}; use crate::{ io::{Buffer, ByteBuffer, Stream}, kernel::{ @@ -83,7 +83,7 @@ impl Vfs for Ext4Fs { } fn is_read_only(&self) -> bool { - true + false } } @@ -257,12 +257,12 @@ impl FileInode { } impl PageCacheBackend for FileInode { - fn read_page(&self, page: &mut crate::kernel::mem::CachePage, offset: usize) -> KResult { + fn read_page(&self, page: &mut CachePage, offset: usize) -> KResult { self.read_direct(page, offset) } - fn write_page(&self, page: &crate::kernel::mem::CachePage, offset: usize) -> KResult { - todo!() + fn write_page(&self, page: &mut CachePageStream, offset: usize) -> KResult { + self.write_direct(page, offset) } fn size(&self) -> usize { @@ -296,12 +296,6 @@ impl Inode for FileInode { fn write(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult { let _lock = Task::block_on(self.rwsem.write()); - let vfs = self.vfs.upgrade().ok_or(EIO)?; - let ext4fs = vfs.as_any().downcast_ref::().unwrap(); - - let mut temp_buf = vec![0u8; 4096]; - let mut total_written = 0; - let mut store_new_end = None; let offset = match offset { WriteOffset::Position(offset) => offset, @@ -312,6 +306,31 @@ impl Inode for FileInode { } }; + let total_written = Task::block_on(self.page_cache.write(stream, offset))?; + let cursor_end = offset + total_written; + if let Some(store_end) = store_new_end { + *store_end = cursor_end; + } + + let mtime = Instant::now(); + *self.mtime.lock() = mtime; + self.size.store(cursor_end as u64, Ordering::Relaxed); + + // TODO: change this with some update strategy such as LRU + let _ = Task::block_on(self.page_cache.fsync()); + + Ok(total_written) + } + + fn write_direct(&self, stream: &mut dyn Stream, offset: usize) -> KResult { + //let _lock = Task::block_on(self.rwsem.write()); + + let vfs = self.vfs.upgrade().ok_or(EIO)?; + let ext4fs = vfs.as_any().downcast_ref::().unwrap(); + + let mut temp_buf = vec![0u8; 4096]; + let mut total_written = 0; + while let Some(data) = stream.poll_data(&mut temp_buf)? { let written = ext4fs .inner @@ -323,18 +342,10 @@ impl Inode for FileInode { } } - if let Some(store_end) = store_new_end { - *store_end = offset + total_written; - } - let mtime = Instant::now(); - *self.mtime.lock() = mtime; - let new_size = (offset + total_written) as u64; - self.size - .store(offset as u64 + total_written as u64, Ordering::Relaxed); ext4fs.modify_inode_stat( self.ino as u32, - Some(new_size), - mtime.since_epoch().as_secs() as u32, + Some(self.size() as u64), + self.mtime.lock().since_epoch().as_secs() as u32, ); Ok(total_written) diff --git a/src/fs/fat32.rs b/src/fs/fat32.rs index 852d8673..fb4a3e2e 100644 --- a/src/fs/fat32.rs +++ b/src/fs/fat32.rs @@ -3,7 +3,7 @@ mod file; use crate::io::Stream; use crate::kernel::constants::EIO; -use crate::kernel::mem::AsMemoryBlock; +use crate::kernel::mem::{AsMemoryBlock, CachePageStream}; use crate::kernel::vfs::inode::WriteOffset; use crate::{ io::{Buffer, ByteBuffer, UninitBuffer}, @@ -308,11 +308,11 @@ impl Inode for FileInode { Ok(buffer.wrote()) } - fn write(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult { + fn write(&self, _stream: &mut dyn Stream, _offset: WriteOffset) -> KResult { todo!() } - fn write_direct(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult { + fn write_direct(&self, _stream: &mut dyn Stream, _offset: usize) -> KResult { todo!() } } @@ -322,7 +322,7 @@ impl PageCacheBackend for FileInode { self.read_direct(page, offset) } - fn write_page(&self, page: &CachePage, offset: usize) -> KResult { + fn write_page(&self, _page: &mut CachePageStream, _offset: usize) -> KResult { todo!() } diff --git a/src/fs/tmpfs.rs b/src/fs/tmpfs.rs index 334e2781..13a01de5 100644 --- a/src/fs/tmpfs.rs +++ b/src/fs/tmpfs.rs @@ -1,6 +1,6 @@ use crate::io::Stream; use crate::kernel::constants::{EEXIST, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, ENOTDIR}; -use crate::kernel::mem::{CachePage, PageCache, PageCacheBackend}; +use crate::kernel::mem::{CachePage, CachePageStream, PageCache, PageCacheBackend}; use crate::kernel::timer::Instant; use crate::kernel::vfs::inode::InodeData; use crate::kernel::vfs::inode::RenameData; @@ -496,7 +496,7 @@ impl PageCacheBackend for FileInode { Ok(PAGE_SIZE) } - fn write_page(&self, _page: &CachePage, _offset: usize) -> KResult { + fn write_page(&self, _page: &mut CachePageStream, _offset: usize) -> KResult { Ok(PAGE_SIZE) } @@ -511,13 +511,13 @@ impl Inode for FileInode { } fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { - let lock = Task::block_on(self.rwsem.write()); + let _lock = Task::block_on(self.rwsem.write()); Task::block_on(self.pages.read(buffer, offset)) } fn write(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult { // TODO: We don't need that strong guarantee, find some way to avoid locks - let lock = Task::block_on(self.rwsem.write()); + let _lock = Task::block_on(self.rwsem.write()); let mut store_new_end = None; let offset = match offset { @@ -545,7 +545,7 @@ impl Inode for FileInode { } fn truncate(&self, length: usize) -> KResult<()> { - let lock = Task::block_on(self.rwsem.write()); + let _lock = Task::block_on(self.rwsem.write()); Task::block_on(self.pages.resize(length))?; self.size.store(length as u64, Ordering::Relaxed); *self.mtime.lock() = Instant::now(); diff --git a/src/kernel/mem.rs b/src/kernel/mem.rs index ce705cff..efd06824 100644 --- a/src/kernel/mem.rs +++ b/src/kernel/mem.rs @@ -12,5 +12,5 @@ pub use access::{AsMemoryBlock, MemoryBlock, PhysAccess}; pub(self) use mm_area::MMArea; pub use mm_list::{handle_kernel_page_fault, FileMapping, MMList, Mapping, Permission}; pub use page_alloc::{GlobalPageAlloc, RawPage}; -pub use page_cache::{CachePage, PageCache, PageCacheBackend}; +pub use page_cache::{CachePage, CachePageStream, PageCache, PageCacheBackend}; pub use paging::{Page, PageBuffer}; diff --git a/src/kernel/mem/page_cache.rs b/src/kernel/mem/page_cache.rs index 863e538e..fa475f9b 100644 --- a/src/kernel/mem/page_cache.rs +++ b/src/kernel/mem/page_cache.rs @@ -125,27 +125,32 @@ impl PageCache { pub async fn read(&self, buffer: &mut dyn Buffer, mut offset: usize) -> KResult { let mut pages = self.pages.lock().await; + let size = self.backend.upgrade().unwrap().size(); loop { + if offset >= size { + break; + } let page_id = offset >> PAGE_SIZE_BITS; let page = pages.get(&page_id); match page { Some(page) => { let inner_offset = offset % PAGE_SIZE; + let available_in_file = size.saturating_sub(offset); // TODO: still cause unnecessary IO if valid_size < PAGESIZE // and fill result is Done - if page.valid_size() == 0 - || buffer - .fill(&page.valid_data()[inner_offset..])? - .should_stop() + let page_data = &page.valid_data()[inner_offset..]; + let read_size = page_data.len().min(available_in_file); + + if read_size == 0 + || buffer.fill(&page_data[..read_size])?.should_stop() || buffer.available() == 0 { break; } - - offset += PAGE_SIZE - inner_offset; + offset += read_size; } None => { let mut new_page = CachePage::new(); @@ -217,7 +222,7 @@ impl PageCache { self.backend .upgrade() .unwrap() - .write_page(page, page_id << PAGE_SIZE_BITS)?; + .write_page(&mut CachePageStream::new(*page), page_id << PAGE_SIZE_BITS)?; page.clear_dirty(); } } @@ -293,6 +298,51 @@ impl PageCache { } } +pub struct CachePageStream { + page: CachePage, + cur: usize, +} + +impl CachePageStream { + pub fn new(page: CachePage) -> Self { + Self { page, cur: 0 } + } + + pub fn remaining(&self) -> usize { + self.page.valid_size().saturating_sub(self.cur) + } + + pub fn is_drained(&self) -> bool { + self.cur >= self.page.valid_size() + } +} + +impl Stream for CachePageStream { + fn poll_data<'a>(&mut self, buf: &'a mut [u8]) -> KResult> { + if self.cur >= self.page.valid_size() { + return Ok(None); + } + + let page_data = &self.page.all()[self.cur..self.page.valid_size()]; + let to_read = buf.len().min(page_data.len()); + + buf[..to_read].copy_from_slice(&page_data[..to_read]); + self.cur += to_read; + + Ok(Some(&mut buf[..to_read])) + } + + fn ignore(&mut self, len: usize) -> KResult> { + if self.cur >= self.page.valid_size() { + return Ok(None); + } + + let to_ignore = len.min(self.page.valid_size() - self.cur); + self.cur += to_ignore; + Ok(Some(to_ignore)) + } +} + // with this trait, "page cache" and "block cache" are unified, // for fs, offset is file offset (floor algin to PAGE_SIZE) // for blkdev, offset is block idx (floor align to PAGE_SIZE / BLK_SIZE) @@ -300,7 +350,7 @@ impl PageCache { pub trait PageCacheBackend { fn read_page(&self, page: &mut CachePage, offset: usize) -> KResult; - fn write_page(&self, page: &CachePage, offset: usize) -> KResult; + fn write_page(&self, page: &mut CachePageStream, offset: usize) -> KResult; fn size(&self) -> usize; } diff --git a/src/kernel/vfs/inode.rs b/src/kernel/vfs/inode.rs index 2b52043d..0f73c910 100644 --- a/src/kernel/vfs/inode.rs +++ b/src/kernel/vfs/inode.rs @@ -136,7 +136,7 @@ pub trait Inode: Send + Sync + InodeInner + Any { Err(if self.is_dir() { EISDIR } else { EINVAL }) } - fn write_direct(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult { + fn write_direct(&self, stream: &mut dyn Stream, offset: usize) -> KResult { Err(if self.is_dir() { EISDIR } else { EINVAL }) } From a2c50b9a112948f448b5142dee375303c2e3e15a Mon Sep 17 00:00:00 2001 From: Heinz Date: Tue, 5 Aug 2025 22:13:54 +0800 Subject: [PATCH 16/17] feat(fs): temporary cache write back strategy for ext4 temporary write back by timer, when write function is called, check if the time since the last write back is greater than 10 seconds. If it is, then write back. --- src/fs/ext4.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 763f1caa..9d315980 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -1,6 +1,7 @@ use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use crate::kernel::mem::{CachePage, CachePageStream, PageCache, PageCacheBackend}; +use crate::kernel::timer::Ticks; use crate::{ io::{Buffer, ByteBuffer, Stream}, kernel::{ @@ -223,6 +224,7 @@ impl Ext4Inode { define_struct_inode! { struct FileInode { + last_sync: AtomicU64, page_cache: PageCache, } } @@ -235,6 +237,7 @@ impl FileInode { fn with_idata(idata: InodeData) -> Arc { let inode = Arc::new_cyclic(|weak_self: &Weak| Self { idata, + last_sync: AtomicU64::new(0), page_cache: PageCache::new(weak_self.clone()), }); @@ -251,9 +254,22 @@ impl FileInode { inode_data.nlink.store(1, Ordering::Relaxed); inode_data }, + last_sync: AtomicU64::new(0), page_cache: PageCache::new(weak_self.clone()), }) } + + fn sync_if_needed(&self) { + let now = Ticks::now().in_secs(); + let last = self.last_sync.load(Ordering::Relaxed); + + // TODO: this is a temporary implement, + // consider change this with some update strategy such as LRU future + if now - last > 10 { + self.last_sync.store(now, Ordering::Relaxed); + let _ = Task::block_on(self.page_cache.fsync()); + } + } } impl PageCacheBackend for FileInode { @@ -316,8 +332,7 @@ impl Inode for FileInode { *self.mtime.lock() = mtime; self.size.store(cursor_end as u64, Ordering::Relaxed); - // TODO: change this with some update strategy such as LRU - let _ = Task::block_on(self.page_cache.fsync()); + self.sync_if_needed(); Ok(total_written) } From ffbd955fc1f3fd3edcbbcb8bb4778854c9d93c43 Mon Sep 17 00:00:00 2001 From: Heinz Date: Tue, 5 Aug 2025 23:11:59 +0800 Subject: [PATCH 17/17] fix(fs): fix ext4's truncate cache update --- src/fs/ext4.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs index 0431b2b6..b857dab3 100644 --- a/src/fs/ext4.rs +++ b/src/fs/ext4.rs @@ -397,6 +397,7 @@ impl Inode for FileInode { let ext4fs = vfs.as_any().downcast_ref::().unwrap(); let buffer = vec![0u8; 10]; let _ = ext4fs.inner.write(self.ino as u32, 0, &buffer); + let _ = Task::block_on(self.page_cache.resize(0)); } self.size.store(length as u64, Ordering::Relaxed);