From bd7d3a92f5b7836ebe6d7298f0ef0090f5afa484 Mon Sep 17 00:00:00 2001
From: Gregory Nutt <gnutt@nuttx.org>
Date: Sat, 11 Feb 2017 11:18:30 -0600
Subject: [PATCH] Add logic to VFS rename:  If target of rename exists and is a
 directory, then the source file should be moved 'under' the target directory.
  POSIX also requires that if the target is a file, then that old file must be
 deleted.

---
 fs/fat/fs_fat32.c          |   5 +-
 fs/smartfs/smartfs_smart.c |  90 +++-------------------------
 fs/vfs/fs_rename.c         | 120 +++++++++++++++++++++++++++++++------
 3 files changed, 116 insertions(+), 99 deletions(-)

diff --git a/fs/fat/fs_fat32.c b/fs/fat/fs_fat32.c
index 774db3c276..4ddf1e5f8e 100644
--- a/fs/fat/fs_fat32.c
+++ b/fs/fat/fs_fat32.c
@@ -2419,7 +2419,10 @@ int fat_rename(FAR struct inode *mountpt, FAR const char *oldrelpath,
     {
       if (ret == OK)
         {
-          /* It is an error if the object at newrelpath already exists */
+          /* It is an error if the directory entry at newrelpath already
+           * exists.  The necessary steps to avoid this case should have
+           * been handled by higher level logic in the VFS.
+           */
 
           ret = -EEXIST;
         }
diff --git a/fs/smartfs/smartfs_smart.c b/fs/smartfs/smartfs_smart.c
index c269031d27..64ab2eeb1f 100644
--- a/fs/smartfs/smartfs_smart.c
+++ b/fs/smartfs/smartfs_smart.c
@@ -1890,9 +1890,6 @@ int smartfs_rename(struct inode *mountpt, const char *oldrelpath,
   const char               *newfilename;
   mode_t                    mode;
   uint16_t                  type;
-  uint16_t                  sector;
-  uint16_t                  offset;
-  uint16_t                  entrysize;
   struct smartfs_entry_header_s *direntry;
   struct smart_read_write_s readwrite;
 
@@ -1911,94 +1908,25 @@ int smartfs_rename(struct inode *mountpt, const char *oldrelpath,
   oldentry.name = NULL;
   newentry.name = NULL;
   ret = smartfs_finddirentry(fs, &oldentry, oldrelpath, &oldparentdirsector,
-          &oldfilename);
+                             &oldfilename);
   if (ret < 0)
     {
       goto errout_with_semaphore;
     }
 
-  /* Search for the new entry and validate it DOESN'T exist, unless we
-   * are copying to a directory and keeping the same filename, such as:
-   *
-   *    mv /mntpoint/somefile.txt /mntpoint/somedir
-   *
-   * in which case, we need to validate the /mntpoint/somedir/somefile.txt
-   * doens't exsit.
-   */
+  /* Search for the new entry and validate it DOESN'T exist. */
 
   ret = smartfs_finddirentry(fs, &newentry, newrelpath, &newparentdirsector,
-          &newfilename);
+                             &newfilename);
   if (ret == OK)
     {
-      /* Test if it's a file.  If it is, then it's an error */
-
-      if ((newentry.flags & SMARTFS_DIRENT_TYPE) == SMARTFS_DIRENT_TYPE_FILE)
-        {
-          ret = -EEXIST;
-          goto errout_with_semaphore;
-        }
-
-      /* Nope, it's a directory.  Now search the directory for oldfilename */
-
-      sector = newentry.firstsector;
-      while (sector != SMARTFS_ERASEDSTATE_16BIT)
-        {
-          /* Read the next sector of diretory entries */
-
-          readwrite.logsector = sector;
-          readwrite.offset = 0;
-          readwrite.count = fs->fs_llformat.availbytes;
-          readwrite.buffer = (uint8_t *) fs->fs_rwbuffer;
-          ret = FS_IOCTL(fs, BIOC_READSECT, (unsigned long) &readwrite);
-          if (ret < 0)
-            {
-              ferr("ERROR: Error %d reading sector %d data\n",
-                   ret, oldentry.dsector);
-              goto errout_with_semaphore;
-            }
-
-          /* Search for newfilename in this sector */
-
-          offset = sizeof(struct smartfs_chain_header_s);
-          entrysize = sizeof(struct smartfs_entry_header_s) + fs->fs_llformat.namesize;
-          while (offset + entrysize < fs->fs_llformat.availbytes)
-            {
-              /* Test the next entry */
-
-              direntry = (struct smartfs_entry_header_s *) &fs->fs_rwbuffer[offset];
-              if (((direntry->flags & SMARTFS_DIRENT_EMPTY) ==
-                  (SMARTFS_ERASEDSTATE_16BIT & SMARTFS_DIRENT_EMPTY)) ||
-                  ((direntry->flags & SMARTFS_DIRENT_ACTIVE) !=
-                  (SMARTFS_ERASEDSTATE_16BIT & SMARTFS_DIRENT_ACTIVE)))
-                {
-                  /* This entry isn't valid, skip it */
-
-                  offset += entrysize;
-                  continue;
-                }
-
-              /* Test if this entry matches newfilename */
-
-              if (strcmp(newfilename, direntry->name) == 0)
-                {
-                  /* Uh-oh, looks like the entry already exists */
-
-                  ret = -EEXIST;
-                  goto errout_with_semaphore;
-                }
-
-              offset += entrysize;
-            }
-
-          /* Chain to next sector */
-
-          sector = SMARTFS_NEXTSECTOR(((struct smartfs_chain_header_s *)fs->fs_rwbuffer));
-        }
-
-      /* Okay, we will create newfilename in the newentry directory */
+      /* It is an error if the directory entry at newrelpath already
+       * exists.  The necessary steps to avoid this case should have been
+       * handled by higher level logic in the VFS.
+       */
 
-      newparentdirsector = newentry.firstsector;
-      newfilename = oldfilename;
+      ret = -EEXIST;
+      goto errout_with_semaphore;
     }
 
   /* Test if the new parent directory is valid */
diff --git a/fs/vfs/fs_rename.c b/fs/vfs/fs_rename.c
index b29b0deb14..162220d86d 100644
--- a/fs/vfs/fs_rename.c
+++ b/fs/vfs/fs_rename.c
@@ -39,6 +39,7 @@
 
 #include <nuttx/config.h>
 
+#include <sys/stat.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <libgen.h>
@@ -95,6 +96,7 @@ static int pseudorename(FAR const char *oldpath, FAR struct inode *oldinode,
    * first, provided that it is not a directory.
    */
 
+next_subdir:
   SETUP_SEARCH(&newdesc, newpath, true);
   ret = inode_find(&newdesc);
   if (ret >= 0)
@@ -121,6 +123,7 @@ static int pseudorename(FAR const char *oldpath, FAR struct inode *oldinode,
       if (newinode->i_child != NULL)
         {
           FAR char *subdirname;
+          FAR char *tmp;
 
           /* Yes.. In this case, the target of the rename must be a
            * subdirectory of newinode, not the newinode itself.  For
@@ -128,7 +131,16 @@ static int pseudorename(FAR const char *oldpath, FAR struct inode *oldinode,
            */
 
           subdirname = basename((FAR char *)oldpath);
+          tmp        = subdir;
+          subdir     = NULL;
+
           (void)asprintf(&subdir, "%s/%s", newpath, subdirname);
+
+          if (tmp != NULL)
+            {
+              kmm_free(tmp);
+            }
+
           if (subdir == NULL)
             {
               ret = -ENOMEM;
@@ -137,10 +149,12 @@ static int pseudorename(FAR const char *oldpath, FAR struct inode *oldinode,
 
           newpath = subdir;
 
-          /* REVISIT:  This can be a recursive case, another inode may
-           * already exist at oldpth/subdirname.  In that case, we need
-           * to do this all over again.
+          /* This can be a recursive case, another inode may already exist
+           * at oldpth/subdirname.  In that case, we need to do this all
+           * over again.  A nasty goto is used because I am lazy.
            */
+
+          goto next_subdir;
         }
       else
         {
@@ -252,10 +266,19 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode,
 {
   struct inode_search_s newdesc;
   FAR struct inode *newinode;
+  FAR const char *newrelpath;
+  FAR char *subdir = NULL;
   int ret;
 
   DEBUGASSERT(oldinode->u.i_mops);
 
+  /* If the file system does not support the rename() method, then bail now. */
+
+  if (oldinode->u.i_mops->rename == NULL)
+    {
+      return -ENOSYS;
+    }
+
   /* Get an inode for the new relpath -- it should lie on the same
    * mountpoint
    */
@@ -272,7 +295,8 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode,
 
   /* Get the search results */
 
-  newinode = newdesc.node;
+  newinode   = newdesc.node;
+  newrelpath = newdesc.relpath;
   DEBUGASSERT(newinode != NULL);
 
   /* Verify that the two paths lie on the same mountpoint inode */
@@ -283,27 +307,84 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode,
       goto errout_with_newinode;
     }
 
-  /* Perform the rename operation using the relative paths at the common 
-   * mountpoint.
+  /* Does a directory entry already exist at the 'rewrelpath'?
+   *
+   * If the directy entry at the newrelpath is a regular file, then that
+   * file should remove removed first.
+   *
+   * If the directory entry at the target is a directory, then the source
+   * file should be moved "under" the directory, i.e., if newrelpath is a
+   * directory, then rename(b,a) should use move the olrelpath should be
+   * moved as if rename(b,a/basename(b)) had been called.
    */
 
-  if (oldinode->u.i_mops->rename)
+  if (oldinode->u.i_mops->stat != NULL)
     {
-      ret = oldinode->u.i_mops->rename(oldinode, oldrelpath, newdesc.relpath);
-      if (ret < 0)
+      struct stat buf;
+
+next_subdir:
+      ret = oldinode->u.i_mops->stat(oldinode, newrelpath, &buf);
+      if (ret >= 0)
         {
-          goto errout_with_newinode;
+          /* Something exists for this directory entry. Is it a directory? */
+
+          if (S_ISDIR(buf.st_mode))
+            {
+              FAR char *subdirname;
+              FAR char *tmp;
+
+              /* Yes.. In this case, the target of the rename must be a
+               * subdirectory of newinode, not the newinode itself.  For
+               * example: mv b a/ must move b to a/b.
+               */
+
+              subdirname = basename((FAR char *)oldrelpath);
+              tmp        = subdir;
+              subdir     = NULL;
+
+              (void)asprintf(&subdir, "%s/%s", newrelpath, subdirname);
+
+              if (tmp != NULL)
+                {
+                  kmm_free(tmp);
+                }
+
+              if (subdir == NULL)
+                {
+                  ret = -ENOMEM;
+                  goto errout_with_newinode;
+                }
+
+              newrelpath = subdir;
+
+              /* This can be a recursive case, another inode may already
+               * exist at oldpth/subdirname.  In that case, we need to
+               * do this all over again.  A nasty goto is used because
+               * I am lazy.
+               */
+
+              goto next_subdir;
+            }
+          else if (oldinode->u.i_mops->unlink)
+            {
+              /* No.. newrelpath must refer to a regular file.  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.
+               */
+
+               (void)oldinode->u.i_mops->unlink(oldinode, newrelpath);
+            }
         }
     }
-  else
-    {
-      ret = -ENOSYS;
-      goto errout_with_newinode;
-    }
 
-  /* Successfully renamed */
+  /* Perform the rename operation using the relative paths at the common
+   * mountpoint.
+   */
 
-  ret = OK;
+  ret = oldinode->u.i_mops->rename(oldinode, oldrelpath, newrelpath);
 
 errout_with_newinode:
   inode_release(newinode);
@@ -311,6 +392,11 @@ errout_with_newinode:
 errout_with_newsearch:
   RELEASE_SEARCH(&newdesc);
 
+  if (subdir != NULL)
+    {
+      kmm_free(subdir);
+    }
+
   return ret;
 }
 #endif /* CONFIG_DISABLE_MOUNTPOINT */
-- 
GitLab