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.
                */

Reply via email to