This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 13ceae9eae182a8f6557c85f684a394a1fed73f4 Author: Marco Casaroli <[email protected]> AuthorDate: Mon Oct 19 00:02:58 2026 +0200 fs/vfs: Let the file system replace the target of rename(). When the new path existed on a mounted volume, mountptrename() removed it and then called the rename method of the file system. Between the two calls, another task could find no file at the new path. POSIX requires that the new path always refers to the old file or to the new file. mountptrename() now calls the rename method first. v9fs, hostfs, NFS, littlefs, rpmsgfs and tmpfs replace the target in one step. If the rename method fails, mountptrename() removes the target and calls the rename method again, as before. FAT, SMARTFS, SPIFFS, mnemofs and userfs use this second path. If the removal fails, rename() returns the error of the first rename call. The pseudo file system path holds the inode tree lock for the whole operation and does not change. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- fs/vfs/fs_rename.c | 73 +++++++++++++++++++++++++-------------------------- include/nuttx/fs/fs.h | 5 ++++ 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/fs/vfs/fs_rename.c b/fs/vfs/fs_rename.c index 7bfb580c2c2..21c594fd4f8 100644 --- a/fs/vfs/fs_rename.c +++ b/fs/vfs/fs_rename.c @@ -286,6 +286,7 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, FAR struct inode *newinode; FAR const char *newrelpath; FAR char *subdir = NULL; + bool newexists = false; bool newisdir = false; bool oldisdir = false; int ret; @@ -347,14 +348,8 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, /* Does a directory entry already exist at the 'newrelpath'? And is it * not the same directory entry that we are moving? * - * If the directory entry at the newrelpath is a regular file, then that - * file should be removed first. - * - * If the directory entry at the newrelpath is an empty directory, then it - * can be removed without issue. - * - * If the directory entry at the newrelpath is a non-empty directory, - * then the rename should fail with the error ENOTEMPTY. + * The rename replaces a regular file or an empty directory at the + * newrelpath. It fails with ENOTEMPTY for a non-empty directory. */ #ifdef CONFIG_FS_LINKS @@ -447,19 +442,6 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, ret = -EINVAL; goto errout_with_newinode; } - - /* Remove the newrelpath which already exists. - * rmdir will handle the error cases. - */ - - if (oldinode->u.i_mops->rmdir) - { - ret = oldinode->u.i_mops->rmdir(oldinode, newrelpath); - if (ret < 0) - { - goto errout_with_newinode; - } - } } else { @@ -472,30 +454,47 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, ret = -ENOTDIR; goto errout_with_newinode; } - - if (oldinode->u.i_mops->unlink) - { - /* Attempt to remove the file before doing the rename. - * - * NOTE that errors are not handled here. If we failed - * to remove the file, then the file system 'rename' - * method should check that. - */ - - oldinode->u.i_mops->unlink(oldinode, newrelpath); -#ifdef CONFIG_FS_NOTIFY - notify_unlink(newrelpath); -#endif - } } + + newexists = true; } } /* Perform the rename operation using the relative paths at the common - * mountpoint. + * mountpoint. The file system replaces an existing newrelpath in one + * step if it can. */ ret = oldinode->u.i_mops->rename(oldinode, oldrelpath, newrelpath); + if (ret < 0 && newexists) + { + int ret2 = ret; + + /* It cannot. Remove newrelpath first, then rename again. If the + * removal fails, return the error of the rename. + */ + + if (newisdir && oldinode->u.i_mops->rmdir) + { + ret2 = oldinode->u.i_mops->rmdir(oldinode, newrelpath); + } + else if (!newisdir && oldinode->u.i_mops->unlink) + { + ret2 = oldinode->u.i_mops->unlink(oldinode, newrelpath); +#ifdef CONFIG_FS_NOTIFY + if (ret2 >= 0) + { + notify_unlink(newrelpath); + } +#endif + } + + if (ret2 >= 0) + { + ret = oldinode->u.i_mops->rename(oldinode, oldrelpath, + newrelpath); + } + } #ifdef CONFIG_FS_NOTIFY if (ret >= 0) diff --git a/include/nuttx/fs/fs.h b/include/nuttx/fs/fs.h index 0fd791509fe..7a33f4dea2a 100644 --- a/include/nuttx/fs/fs.h +++ b/include/nuttx/fs/fs.h @@ -352,6 +352,11 @@ struct mountpt_operations CODE int (*mkdir)(FAR struct inode *mountpt, FAR const char *relpath, mode_t mode); CODE int (*rmdir)(FAR struct inode *mountpt, FAR const char *relpath); + + /* rename() replaces an existing newrelpath in one step, or fails (for + * example with -EEXIST). The VFS then removes newrelpath and retries. + */ + CODE int (*rename)(FAR struct inode *mountpt, FAR const char *oldrelpath, FAR const char *newrelpath);
