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 e396baf06f982d278b61f800c573a19f298d558c Author: yukangzhi <[email protected]> AuthorDate: Fri Jul 3 11:47:29 2026 +0800 fs/vfs/rename: fix rename to same file and rename to subdirectory Fix two POSIX compliance issues in mountptrename(): 1. When old and new are hard links to the same file (same st_dev and st_ino), POSIX requires rename() to succeed without removing either link. Previously, NuttX would unlink(new) then rename(old, new), effectively losing one link. Fix by comparing inode identity before any destructive operation. 2. When new is a subdirectory of old (e.g., rename('a', 'a/b')), POSIX requires EINVAL. Previously, NuttX would rmdir(new) first, then the filesystem's rename() would fail -- but new was already deleted, causing data loss. Fix by detecting the subdirectory relationship (newrelpath starts with oldrelpath + '/') before any rmdir/unlink. Signed-off-by: yukangzhi <[email protected]> --- fs/vfs/fs_rename.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/fs/vfs/fs_rename.c b/fs/vfs/fs_rename.c index c461124157c..571320420c0 100644 --- a/fs/vfs/fs_rename.c +++ b/fs/vfs/fs_rename.c @@ -405,12 +405,33 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, if (ret >= 0) { + /* If old and new refer to the same file (same st_dev and + * st_ino), POSIX requires rename() to return success without + * doing anything. Both names must remain intact. + * + * Guard: only trust st_ino when it is non-zero. Several + * filesystems (tmpfs, littlefs, fat) do not populate st_ino, + * leaving it 0 for every file. Without this guard, any two + * distinct files would be mistaken for hard links to the same + * inode, causing rename() to skip the actual operation. + */ + + if (oldbuf.st_ino != 0 && + oldbuf.st_dev == newbuf.st_dev && + oldbuf.st_ino == newbuf.st_ino) + { + ret = OK; + goto errout_with_newinode; + } + newisdir = S_ISDIR(newbuf.st_mode); /* Is the new path a directory? */ if (newisdir) { + size_t oldlen; + /* It is an error to rename a file to a directory */ if (!oldisdir) @@ -419,6 +440,18 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, goto errout_with_newinode; } + /* It is an error to rename a directory into one of its + * own subdirectories (new is below old). + */ + + oldlen = strlen(oldrelpath); + if (strncmp(newrelpath, oldrelpath, oldlen) == 0 && + newrelpath[oldlen] == '/') + { + ret = -EINVAL; + goto errout_with_newinode; + } + /* Remove the newrelpath which already exists. * rmdir will handle the error cases. */
