diff --git a/header/src/lib.rs b/header/src/lib.rs index 7a53fb2d7..bac3be9cd 100644 --- a/header/src/lib.rs +++ b/header/src/lib.rs @@ -90,6 +90,7 @@ pub mod syscalls { TimerGetTime, TimerSetTime, TimerGetOverrun, + Rename, LastNR, } } diff --git a/kernel/src/syscall_handlers/mod.rs b/kernel/src/syscall_handlers/mod.rs index 1cd3e6254..d281cfbb3 100644 --- a/kernel/src/syscall_handlers/mod.rs +++ b/kernel/src/syscall_handlers/mod.rs @@ -69,6 +69,9 @@ mod vfs_syscalls { pub fn link(_oldpath: *const c_char, _newpath: *const c_char) -> i32 { -libc::ENOTSUP } + pub fn rename(_oldpath: *const c_char, _newpath: *const c_char) -> i32 { + -libc::ENOTSUP + } pub fn unlink(_path: *const c_char) -> i32 { -libc::ENOTSUP } @@ -541,6 +544,11 @@ define_syscall_handler!( vfs_syscalls::link(oldpath, newpath) } ); +define_syscall_handler!( + rename(oldpath: *const c_char, newpath: *const c_char) -> c_int { + vfs_syscalls::rename(oldpath, newpath) + } +); define_syscall_handler!( unlink(path: *const c_char) -> c_int { vfs_syscalls::unlink(path) @@ -868,6 +876,7 @@ syscall_table! { (Rmdir, rmdir), (Link, link), (Unlink, unlink), + (Rename, rename), (Fcntl, fcntl), (Stat, stat), (FStat, fstat), diff --git a/kernel/src/vfs/dcache.rs b/kernel/src/vfs/dcache.rs index 5093e52b0..45562bec1 100644 --- a/kernel/src/vfs/dcache.rs +++ b/kernel/src/vfs/dcache.rs @@ -368,6 +368,10 @@ impl Dcache { return Err(code::EBUSY); } + if ptr::addr_eq(self, Arc::as_ptr(new_dir)) && old_name == new_name { + return Ok(()); + } + // rename in the same directory if ptr::addr_eq(self, Arc::as_ptr(new_dir)) && old_name != new_name { if children.contains_key(new_name) { @@ -376,6 +380,7 @@ impl Dcache { } self.inode.rename(old_name, &self.inode, new_name)?; children.remove(old_name); + child.set_name_and_parent(new_name, self.this.clone()); if child.is_dcacheable() { children.insert(String::from(new_name), child); } diff --git a/kernel/src/vfs/fatfs.rs b/kernel/src/vfs/fatfs.rs index 171b54d9c..e821d3207 100644 --- a/kernel/src/vfs/fatfs.rs +++ b/kernel/src/vfs/fatfs.rs @@ -322,11 +322,11 @@ impl core::fmt::Debug for FatFileData { struct FatFile { _parent: Weak, - internal_file: InternalFsLock, + internal_file: InternalFsLock>, } impl FatFile { - fn new(parent: &Weak, internal_file: InternalFsLock) -> Self { + fn new(parent: &Weak, internal_file: InternalFsLock>) -> Self { Self { _parent: parent.clone(), internal_file, @@ -400,7 +400,7 @@ impl FatInode { attr, data: FatFileData::File(FatFile::new( parent, - internal_fs_wrapper.wrap(internal_file), + internal_fs_wrapper.wrap(Some(internal_file)), )), }), this: weak_inode.clone(), @@ -561,11 +561,17 @@ impl InodeOps for FatInode { #[cfg(debug)] { let inner = self.inner.read(); - let (file, _) = inner.as_file().unwrap().internal_file.get(); + let (file, _) = inner.as_file().ok_or(code::EIO)?.internal_file.get(); + let file = file.as_ref().ok_or(code::EIO)?; assert_eq!(file.size().unwrap(), inner.attr.size.try_into().unwrap()); } let mut inner = self.inner.write(); - let (file, _) = inner.as_file_mut().unwrap().internal_file.get_mut(); + let (file, _) = inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .get_mut(); + let file = file.as_mut().ok_or(code::EIO)?; let expected_read_size = buf.len(); let mut offset = offset; let mut total_read_size = 0; @@ -591,7 +597,12 @@ impl InodeOps for FatInode { } let (write_size, new_size, extents) = { let mut inner = self.inner.write(); - let (file, _) = inner.as_file_mut().unwrap().internal_file.get_mut(); + let (file, _) = inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .get_mut(); + let file = file.as_mut().ok_or(code::EIO)?; let mut offset = offset; let mut total_write_size = 0; let expected_write_size = buf.len(); @@ -677,6 +688,139 @@ impl InodeOps for FatInode { Ok(()) } + fn rename( + &self, + old_name: &str, + target: &Arc, + new_name: &str, + ) -> Result<(), Error> { + if old_name == "." || old_name == ".." || new_name == "." || new_name == ".." { + return Err(code::EINVAL); + } + let target = target.downcast_ref::().ok_or(code::EXDEV)?; + let source_fs = self.fs.upgrade().ok_or(code::EAGAIN)?; + let target_fs = target.fs.upgrade().ok_or(code::EAGAIN)?; + if !Arc::ptr_eq(&source_fs, &target_fs) { + return Err(code::EXDEV); + } + if self.type_() != InodeFileType::Directory || target.type_() != InodeFileType::Directory { + return Err(code::ENOTDIR); + } + if core::ptr::eq(self, target) { + let mut inner = self.inner.write(); + let dir = inner.as_dir_mut().ok_or(code::ENOTDIR)?; + let child = dir.find(old_name).ok_or(code::ENOENT)?; + if old_name == new_name { + return Ok(()); + } + if dir.find(new_name).is_some() { + return Err(code::EEXIST); + } + let mut child_inner = child.inner.write(); + let is_file = child_inner.attr.type_() == InodeFileType::Regular; + if is_file { + let file = child_inner.as_file_mut().ok_or(code::EIO)?; + let (slot, guard) = file.internal_file.get_mut(); + let old_file = slot.take().ok_or(code::EIO)?; + drop(old_file); + drop(guard); + } + + let (internal_dir, guard) = dir.internal_dir.get(); + if let Err(error) = internal_dir.rename(old_name, internal_dir, new_name) { + if is_file { + child_inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .content = Some(internal_dir.open_file(old_name)?); + } + return Err(error.into()); + } + if is_file { + match internal_dir.open_file(new_name) { + Ok(file) => { + child_inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .content = Some(file); + } + Err(error) => { + let _ = internal_dir.rename(new_name, internal_dir, old_name); + child_inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .content = Some(internal_dir.open_file(old_name)?); + return Err(error.into()); + } + } + } + drop(guard); + dir.remove(old_name); + dir.insert(new_name, &child); + return Ok(()); + } + + let mut source_inner = self.inner.write(); + let mut target_inner = target.inner.write(); + let source_dir = source_inner.as_dir_mut().ok_or(code::ENOTDIR)?; + let target_dir = target_inner.as_dir_mut().ok_or(code::ENOTDIR)?; + if target_dir.find(new_name).is_some() { + return Err(code::EEXIST); + } + let child = source_dir.find(old_name).ok_or(code::ENOENT)?; + let mut child_inner = child.inner.write(); + let is_file = child_inner.attr.type_() == InodeFileType::Regular; + if is_file { + let file = child_inner.as_file_mut().ok_or(code::EIO)?; + let (slot, guard) = file.internal_file.get_mut(); + let old_file = slot.take().ok_or(code::EIO)?; + drop(old_file); + drop(guard); + } + + let (source_internal, guard) = source_dir.internal_dir.get(); + let target_internal = &target_dir.internal_dir.content; + if let Err(error) = source_internal.rename(old_name, target_internal, new_name) { + if is_file { + child_inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .content = Some(source_internal.open_file(old_name)?); + } + return Err(error.into()); + } + if is_file { + match target_internal.open_file(new_name) { + Ok(file) => { + child_inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .content = Some(file); + } + Err(error) => { + let _ = target_internal.rename(new_name, source_internal, old_name); + child_inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .content = Some(source_internal.open_file(old_name)?); + return Err(error.into()); + } + } + } else { + child_inner.as_dir_mut().ok_or(code::EIO)?.parent = target.this.clone(); + } + drop(guard); + source_dir.remove(old_name); + target_dir.insert(new_name, &child); + Ok(()) + } + fn getdents_at(&self, offset: usize, reader: &mut DirBufferReader) -> Result { if self.type_() != InodeFileType::Directory { error!("[FatInode] getdents_at: not a directory"); @@ -747,7 +891,12 @@ impl InodeOps for FatInode { } let (new_size, extents) = { let mut inner = self.inner.write(); - let (file, _) = inner.as_file_mut().unwrap().internal_file.get_mut(); + let (file, _) = inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .get_mut(); + let file = file.as_mut().ok_or(code::EIO)?; file.seek(SeekFrom::Start(size as u64))?; file.truncate()?; let new_size = file.size().unwrap() as usize; @@ -804,7 +953,12 @@ impl InodeOps for FatInode { return Err(code::ENOTSUP); } let mut inner = self.inner.write(); - let (file, _) = inner.as_file_mut().unwrap().internal_file.get_mut(); + let (file, _) = inner + .as_file_mut() + .ok_or(code::EIO)? + .internal_file + .get_mut(); + let file = file.as_mut().ok_or(code::EIO)?; file.flush()?; Ok(()) } diff --git a/kernel/src/vfs/syscalls.rs b/kernel/src/vfs/syscalls.rs index ec9c96f99..0dd09c3d3 100644 --- a/kernel/src/vfs/syscalls.rs +++ b/kernel/src/vfs/syscalls.rs @@ -433,6 +433,36 @@ pub fn link(old_path: *const c_char, new_path: *const c_char) -> c_int { } } +pub fn rename(old_path: *const c_char, new_path: *const c_char) -> c_int { + if old_path.is_null() || new_path.is_null() { + return -libc::EINVAL; + } + + let old_path = match unsafe { CStr::from_ptr(old_path).to_str() } { + Ok(path) => path, + Err(_) => return -libc::EINVAL, + }; + let new_path = match unsafe { CStr::from_ptr(new_path).to_str() } { + Ok(path) => path, + Err(_) => return -libc::EINVAL, + }; + + let (old_dir, old_name) = match path::find_parent_and_name(old_path) { + Some(result) => result, + None => return -libc::ENOENT, + }; + let (new_dir, new_name) = match path::find_parent_and_name(new_path) { + Some(result) => result, + None => return -libc::ENOENT, + }; + + debug!("[rename] {} -> {}", old_path, new_path); + match old_dir.rename(old_name, &new_dir, new_name) { + Ok(()) => 0, + Err(error) => error.to_errno(), + } +} + pub fn unlink(path: *const c_char) -> c_int { if path.is_null() { return -libc::EINVAL; @@ -930,6 +960,24 @@ mod tests { assert_eq!(result, code::ENOENT.to_errno()); } + #[test] + fn test_rename_invalid_path() { + assert_eq!( + rename(core::ptr::null(), TEST_PATH), + code::EINVAL.to_errno() + ); + assert_eq!( + rename(TEST_PATH, core::ptr::null()), + code::EINVAL.to_errno() + ); + } + + #[test] + fn test_rename_missing_source() { + let new_path = c"/test/new.txt".as_ptr() as *const c_char; + assert_eq!(rename(TEST_PATH, new_path), code::ENOENT.to_errno()); + } + #[test] fn test_dir() { let result = open(TEST_DIR, libc::O_RDONLY, 0o755); diff --git a/kernel/src/vfs/tmpfs.rs b/kernel/src/vfs/tmpfs.rs index 21d7f6fea..8dc44752b 100644 --- a/kernel/src/vfs/tmpfs.rs +++ b/kernel/src/vfs/tmpfs.rs @@ -567,6 +567,63 @@ impl InodeOps for TmpInode { Ok(()) } + fn rename( + &self, + old_name: &str, + target: &Arc, + new_name: &str, + ) -> Result<(), Error> { + if old_name == "." || old_name == ".." || new_name == "." || new_name == ".." { + return Err(code::EINVAL); + } + let target = target.downcast_ref::().ok_or(code::EXDEV)?; + let source_fs = self.fs.upgrade().ok_or(code::EAGAIN)?; + let target_fs = target.fs.upgrade().ok_or(code::EAGAIN)?; + if !Arc::ptr_eq(&source_fs, &target_fs) { + return Err(code::EXDEV); + } + if self.type_() != InodeFileType::Directory || target.type_() != InodeFileType::Directory { + return Err(code::ENOTDIR); + } + + if core::ptr::eq(self, target) { + let mut inner = self.inner.write(); + let dir = inner.as_dir_mut().ok_or(code::ENOTDIR)?; + let child = dir.find(old_name).ok_or(code::ENOENT)?; + if old_name == new_name { + return Ok(()); + } + if dir.find(new_name).is_some() { + return Err(code::EEXIST); + } + dir.remove(old_name); + dir.insert(new_name, &child); + return Ok(()); + } + + let mut source_inner = self.inner.write(); + let mut target_inner = target.inner.write(); + let source_dir = source_inner.as_dir_mut().ok_or(code::ENOTDIR)?; + let target_dir = target_inner.as_dir_mut().ok_or(code::ENOTDIR)?; + let child = source_dir.find(old_name).ok_or(code::ENOENT)?; + if target_dir.find(new_name).is_some() { + return Err(code::EEXIST); + } + + source_dir.remove(old_name); + target_dir.insert(new_name, &child); + source_inner.dec_size(); + target_inner.inc_size(); + + let mut child_inner = child.inner.write(); + if child_inner.attr.type_() == InodeFileType::Directory { + child_inner.as_dir_mut().ok_or(code::EIO)?.parent = target.this.clone(); + source_inner.dec_nlinks(); + target_inner.inc_nlinks(); + } + Ok(()) + } + fn getdents_at(&self, offset: usize, reader: &mut DirBufferReader) -> Result { let inner = self.inner.read(); let Some(dir) = inner.as_dir() else {