Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions header/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub mod syscalls {
TimerGetTime,
TimerSetTime,
TimerGetOverrun,
Rename,
LastNR,
}
}
Expand Down
9 changes: 9 additions & 0 deletions kernel/src/syscall_handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -868,6 +876,7 @@ syscall_table! {
(Rmdir, rmdir),
(Link, link),
(Unlink, unlink),
(Rename, rename),
(Fcntl, fcntl),
(Stat, stat),
(FStat, fstat),
Expand Down
5 changes: 5 additions & 0 deletions kernel/src/vfs/dcache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down
170 changes: 162 additions & 8 deletions kernel/src/vfs/fatfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,11 @@ impl core::fmt::Debug for FatFileData {

struct FatFile {
_parent: Weak<FatInode>,
internal_file: InternalFsLock<File>,
internal_file: InternalFsLock<Option<File>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Option there?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rust-fatfs::Dir::rename requires that no live File instance references the entry being renamed; otherwise the old File may later update the original directory entry and corrupt the filesystem.

BlueOS keeps a fatfs::File persistently inside FatInode, so Option<File> allows the rename path to take() and drop that handle before calling Dir::rename, then reopen the file under its new name and store it back afterward. If rename fails, the original file is reopened.

The None state exists only during the rename critical section while the inode write lock is held. Other file operations return EIO if this invariant is unexpectedly violated.

Using mem::replace would still require a dummy live File, so it would not satisfy the library’s rename requirement.

You can see it in https://github.com/rafalh/rust-fatfs/blob/c4b88477b22ca7e5131fbd8891f62a5deaa88e6e/src/dir.rs#L388

}

impl FatFile {
fn new(parent: &Weak<FatInode>, internal_file: InternalFsLock<File>) -> Self {
fn new(parent: &Weak<FatInode>, internal_file: InternalFsLock<Option<File>>) -> Self {
Self {
_parent: parent.clone(),
internal_file,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -677,6 +688,139 @@ impl InodeOps for FatInode {
Ok(())
}

fn rename(
&self,
old_name: &str,
target: &Arc<dyn InodeOps>,
new_name: &str,
) -> Result<(), Error> {
if old_name == "." || old_name == ".." || new_name == "." || new_name == ".." {
return Err(code::EINVAL);
}
let target = target.downcast_ref::<FatInode>().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<usize, Error> {
if self.type_() != InodeFileType::Directory {
error!("[FatInode] getdents_at: not a directory");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(())
}
Expand Down
48 changes: 48 additions & 0 deletions kernel/src/vfs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a file with new_path, how to handle this case


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;
Expand Down Expand Up @@ -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);
Expand Down
Loading