fix(fs): 修复 sys_rename 逻辑并支持 RENAME_NOREPLACE#1393
fix(fs): 修复 sys_rename 逻辑并支持 RENAME_NOREPLACE#1393fslongjin merged 17 commits intoDragonOS-Community:masterfrom
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR fixes critical logic bugs in the sys_rename implementation and adds support for the RENAME_NOREPLACE flag. The changes enhance rename operation safety by adding proper validation for file type compatibility, ancestor relationship checks, and special path component handling.
Key changes:
- Introduced
RenameFlagsbitflags structure with support forNOREPLACE,EXCHANGE, andWHITEOUTflags - Added ancestor relationship checking to prevent moving directories into their own subdirectories
- Fixed file/directory overwrite logic with proper type checking and page cache truncation
- Added validation to reject operations on "." and ".." path components
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| user/apps/tests/syscall/gvisor/whitelist.txt | Enables rename_test in gvisor test suite |
| user/apps/tests/syscall/gvisor/blocklists/rename_test | Defines blocklist for unimplemented rename test cases |
| kernel/src/filesystem/vfs/utils.rs | Adds is_ancestor_except_self helper for directory hierarchy validation |
| kernel/src/filesystem/vfs/syscall/rename_utils.rs | Implements NOREPLACE flag support and path validation logic |
| kernel/src/filesystem/vfs/syscall/mod.rs | Defines RenameFlags bitflags structure |
| kernel/src/filesystem/vfs/mount.rs | Updates MountFSInode to pass flags to move_to |
| kernel/src/filesystem/vfs/mod.rs | Updates IndexNode trait to include flags parameter |
| kernel/src/filesystem/ramfs/mod.rs | Updates ramfs move_to signature to accept flags |
| kernel/src/filesystem/procfs/mod.rs | Updates procfs move_to signature to accept flags |
| kernel/src/filesystem/kernfs/mod.rs | Updates kernfs move_to signature to accept flags |
| kernel/src/filesystem/fat/fs.rs | Implements rename logic with EXCHANGE flag handling and type checking |
| kernel/src/filesystem/fat/entry.rs | Implements file type validation, overwrite logic, and page cache truncation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return Err(SystemError::EBUSY); | ||
| } | ||
|
|
||
| if !flags.is_empty() { |
There was a problem hiding this comment.
The NOREPLACE flag check is performed, but then at line 78-81, there's a check that returns EINVAL for any non-empty flags. This means after the NOREPLACE handling (lines 65-72), if NOREPLACE is set, the flags will still be non-empty and will trigger the EINVAL error at line 80.
The check at lines 78-81 should exclude NOREPLACE from consideration, or be moved before the NOREPLACE handling. Consider:
if flags.intersects(RenameFlags::EXCHANGE | RenameFlags::WHITEOUT) {
log::warn!("renameat2 flags {flags:?} not supported yet");
return Err(SystemError::EINVAL);
}| if !flags.is_empty() { | |
| if flags.intersects(RenameFlags::EXCHANGE | RenameFlags::WHITEOUT) { |
kernel/src/filesystem/fat/entry.rs
Outdated
| if let Some(page_cache) = new_inode.unwrap().page_cache().clone() { | ||
| truncate_inode_pages(page_cache, 0); |
There was a problem hiding this comment.
Calling .unwrap() on new_inode will panic if new_inode is None. This can happen when the target doesn't exist (ShortName case at line 870).
Use pattern matching or safe handling:
if let Some(inode) = &new_inode {
if let Some(page_cache) = inode.page_cache().clone() {
truncate_inode_pages(page_cache, 0);
}
}| if let Some(page_cache) = new_inode.unwrap().page_cache().clone() { | |
| truncate_inode_pages(page_cache, 0); | |
| if let Some(inode) = &new_inode { | |
| if let Some(page_cache) = inode.page_cache().clone() { | |
| truncate_inode_pages(page_cache, 0); | |
| } |
There was a problem hiding this comment.
在target存在时才执行这个unwrap,不会解到None
kernel/src/filesystem/fat/fs.rs
Outdated
| let mut guard = self.0.lock(); | ||
| let old_inode = guard.find(old_name)?; | ||
| let new_inode = guard.find(new_name).ok(); | ||
| // do_rename2_at has checked NOREPLACE |
There was a problem hiding this comment.
Typo in comment: "do_rename2_at" should be "do_renameat2" to match the actual function name.
| // do_rename2_at has checked NOREPLACE | |
| // do_renameat2 has checked NOREPLACE |
kernel/src/filesystem/fat/fs.rs
Outdated
| let mut new_guard = other.0.lock(); | ||
| let old_inode: Arc<LockedFATInode> = old_guard.find(old_name)?; | ||
| let new_inode = new_guard.find(new_name); | ||
| // do_rename2_at has checked NOREPLACE |
There was a problem hiding this comment.
Typo in comment: "do_rename2_at" should be "do_renameat2" to match the actual function name.
| // do_rename2_at has checked NOREPLACE | |
| // do_renameat2 has checked NOREPLACE |
kernel/src/filesystem/fat/entry.rs
Outdated
| // 目录不能覆盖文件 | ||
| if old_is_dir && !new_is_dir { | ||
| return Err(SystemError::ENOTDIR); | ||
| } | ||
| // 文件不能覆盖目录 | ||
| if !old_is_dir && new_is_dir { | ||
| return Err(SystemError::EISDIR); | ||
| } |
There was a problem hiding this comment.
This file type checking logic is duplicated between the rename function (lines 876-883) and rename_across function (lines 940-947). Consider extracting this into a helper function to reduce code duplication and improve maintainability:
fn check_rename_type_compatibility(old_is_dir: bool, new_is_dir: bool) -> Result<(), SystemError> {
if old_is_dir && !new_is_dir {
return Err(SystemError::ENOTDIR);
}
if !old_is_dir && new_is_dir {
return Err(SystemError::EISDIR);
}
Ok(())
}| if old_meta.inode_id == new_parent_meta.inode_id | ||
| || is_ancestor_except_self(&old_inode, &new_parent_inode) |
There was a problem hiding this comment.
The logic here checks if old_inode (source directory) equals new_parent_inode OR if old_inode is an ancestor of new_parent_inode. However, the first condition is incorrect: when old_meta.inode_id == new_parent_meta.inode_id, it means the source directory IS the destination parent directory, which is a valid rename within the same directory (e.g., renaming dir1/file to dir1/newfile).
The correct check should only prevent moving a directory into its own subdirectory:
if is_ancestor_except_self(&old_inode, &new_parent_inode) {
return Err(SystemError::EINVAL);
}Remove the old_meta.inode_id == new_parent_meta.inode_id condition.
| if old_meta.inode_id == new_parent_meta.inode_id | |
| || is_ancestor_except_self(&old_inode, &new_parent_inode) | |
| if is_ancestor_except_self(&old_inode, &new_parent_inode) |
| if let Some(page_cache) = new_inode.unwrap().page_cache().clone() { | ||
| truncate_inode_pages(page_cache, 0); |
There was a problem hiding this comment.
Calling .unwrap() on new_inode will panic if it contains an Err value. The parameter type is Result<Arc<LockedFATInode>, SystemError>, so when the target doesn't exist, this will be an Err.
Use pattern matching or safe handling:
if let Ok(inode) = &new_inode {
if let Some(page_cache) = inode.page_cache().clone() {
truncate_inode_pages(page_cache, 0);
}
}| if let Some(page_cache) = new_inode.unwrap().page_cache().clone() { | |
| truncate_inode_pages(page_cache, 0); | |
| if let Ok(inode) = &new_inode { | |
| if let Some(page_cache) = inode.page_cache().clone() { | |
| truncate_inode_pages(page_cache, 0); | |
| } |
kernel/src/filesystem/fat/entry.rs
Outdated
| let old_is_dir = old_dentry.is_dir(); | ||
| let new_is_dir = e.is_dir(); | ||
|
|
||
| // 目录不能覆盖文件 | ||
| if old_is_dir && !new_is_dir { | ||
| return Err(SystemError::ENOTDIR); | ||
| } | ||
| // 文件不能覆盖目录 | ||
| if !old_is_dir && new_is_dir { | ||
| return Err(SystemError::EISDIR); | ||
| } | ||
| if e.is_dir() && !(e.to_dir().unwrap().is_empty(fs.clone())) { | ||
| return Err(SystemError::ENOTEMPTY); | ||
| } |
There was a problem hiding this comment.
This non-empty directory check logic is duplicated in both rename (line 884-886) and rename_across (lines 948-950). Consider extracting it into a helper function or consolidating with the file type checking to reduce duplication:
fn validate_rename_target(old_entry: &FATDirEntry, new_entry: &FATDirEntry, fs: Arc<FATFileSystem>) -> Result<(), SystemError> {
let old_is_dir = old_entry.is_dir();
let new_is_dir = new_entry.is_dir();
if old_is_dir && !new_is_dir {
return Err(SystemError::ENOTDIR);
}
if !old_is_dir && new_is_dir {
return Err(SystemError::EISDIR);
}
if new_entry.is_dir() && !(new_entry.to_dir().unwrap().is_empty(fs)) {
return Err(SystemError::ENOTEMPTY);
}
Ok(())
}| let old_is_dir = old_dentry.is_dir(); | |
| let new_is_dir = e.is_dir(); | |
| // 目录不能覆盖文件 | |
| if old_is_dir && !new_is_dir { | |
| return Err(SystemError::ENOTDIR); | |
| } | |
| // 文件不能覆盖目录 | |
| if !old_is_dir && new_is_dir { | |
| return Err(SystemError::EISDIR); | |
| } | |
| if e.is_dir() && !(e.to_dir().unwrap().is_empty(fs.clone())) { | |
| return Err(SystemError::ENOTEMPTY); | |
| } | |
| validate_rename_target(&old_dentry, &e, fs.clone())?; |
| if new_filename == "." || new_filename == ".." { | ||
| return Err(SystemError::EEXIST); | ||
| } |
There was a problem hiding this comment.
When NOREPLACE flag is set and new_filename is "." or "..", the function returns EEXIST. However, this check is redundant because these special filenames are already blocked at lines 74-76 with EBUSY error for all rename operations. The EEXIST check here should only apply when the target file actually exists, not for special path components. Consider removing this check or moving it after the general "." and ".." validation.
| if new_filename == "." || new_filename == ".." { | |
| return Err(SystemError::EEXIST); | |
| } |
There was a problem hiding this comment.
这里情况不一样,加这个为了通过测试
ef90258 to
edeaaf3
Compare
edeaaf3 to
ce10794
Compare

逻辑修复:
· 修正覆盖现有文件/目录时的逻辑,确保目标被正确截断和删除。
· 增加文件类型检查,禁止文件与目录之间的非法覆盖。
· 增加祖先关系检查,防止将目录移动到其子目录下。
· 修复同名重命名及非空目录覆盖的边界情况。
· 拦截源或目标路径以 . 或 .. 结尾的非法操作。
功能增强:
引入RenameFlags支持,实现了 RENAME_NOREPLACE 语义。
相关 Commits:
· 添加 RenameFlags 支持 (NOREPLACE)
· 完善路径拦截 (. / ..)
·修复覆盖
截断及类型检查逻辑
· 增加祖先关系判断
Note
Refactors rename/renameat2 across VFS and filesystems with RenameFlags (NOREPLACE), strict type/emptiness checks, subtree and special-name guards, and proper overwrite/truncation; enables rename tests.
RenameFlagsand plumb throughIndexNode::move_to(..., flags)and wrappers.do_renameat2semantics: reject/,./.., supportNOREPLACE, prevent moving a dir into its own subtree viais_ancestor, delegate ENOTEMPTY to FS.utils::is_ancestorhelper.rename/rename_acrossto allow atomic overwrite with validation viavalidate_rename_target(type match, empty dir), and delete target when allowed.move_tosignatures to acceptRenameFlags; implementNOREPLACEcheck in RAMFS same-dir rename.rename_test; add blocklist entries for unsupported/permission-related cases.Written by Cursor Bugbot for commit 762bfc3. This will update automatically on new commits. Configure here.