/**
 * Create a new file on this (remote) meta-server. This is the 'toFile' on a rename() client call.
 *
 * Note: Replaces existing entry.
 *
 * @param buf serialized inode object
 * @param outUnlinkedInode the unlinked (owned) file (in case a file was overwritten
 * by the move operation); the caller is responsible for the deletion of the local file and the
 * corresponding object; may not be NULL
 */
FhgfsOpsErr MetaStore::moveRemoteFileInsert(EntryInfo* fromFileInfo, std::string toParentID,
   std::string newEntryName, const char* buf, FileInode** outUnlinkedInode)
{
   // note: we do not allow newEntry to be a file if the old entry was a directory (and vice versa)
   const char* logContext = "rename(): Insert remote entry";

   FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL;

   *outUnlinkedInode = NULL;

   SafeRWLock safeMetaStoreLock(&rwlock, SafeRWLock_READ); // L O C K

   DirInode* toParent = referenceDirUnlocked(toParentID, true);
   if(!toParent)
   {
      retVal = FhgfsOpsErr_PATHNOTEXISTS;
      safeMetaStoreLock.unlock(); // U N L O C K
      return retVal;
   }

   // toParent exists
   SafeRWLock toParentMutexLock(&toParent->rwlock, SafeRWLock_WRITE); // L O C K ( T O )


   DirEntry* overWrittenEntry = toParent->dirEntryCreateFromFileUnlocked(newEntryName);
   if (overWrittenEntry)
   {
      EntryInfo overWriteInfo;
      std::string parentID = overWrittenEntry->getID();
      overWrittenEntry->getEntryInfo(parentID, 0, &overWriteInfo);
      bool isSameInode;

      FhgfsOpsErr checkRes = checkRenameOverwrite(fromFileInfo, &overWriteInfo, isSameInode);

      if ((checkRes != FhgfsOpsErr_SUCCESS)  || ((checkRes == FhgfsOpsErr_SUCCESS) && isSameInode) )
      {
         retVal = checkRes;
         goto outUnlock;
      }

      // only unlink the dir-entry-name here, so we can still restore it from dir-entry-id
      FhgfsOpsErr unlinkRes = toParent->unlinkDirEntryUnlocked(newEntryName, overWrittenEntry,
         DirEntry_UNLINK_FILENAME);
      if (unlikely(unlinkRes != FhgfsOpsErr_SUCCESS) )
      {
         if (unlikely (unlinkRes == FhgfsOpsErr_PATHNOTEXISTS) )
            LogContext(logContext).log(Log_WARNING, "Unexpectedly failed to unlink file: " +
               toParent->entries.getDirEntryPathUnlocked() + newEntryName + ". ");
         else
         {
            LogContext(logContext).logErr("Failed to unlink existing file. Aborting rename().");
            retVal = unlinkRes;
            goto outUnlock;
         }
      }
   }

   { // create new dirEntry with inlined inode
      FileInode* inode = new FileInode(); // the deserialized inode
      bool deserializeRes = inode->deserializeMetaData(buf);
      if (deserializeRes == false)
      {
         LogContext("File rename").logErr("Bug: Deserialization of remote buffer failed. Are all "
            "meta servers running with the same version?" );
         retVal = FhgfsOpsErr_INTERNAL;

         delete inode;
         goto outUnlock;
      }

      // destructs inode
      retVal = mkMetaFileUnlocked(toParent, newEntryName, fromFileInfo->getEntryType(), inode);
   }

   if (overWrittenEntry && retVal == FhgfsOpsErr_SUCCESS)
   { // unlink the overwritten entry, will unlock, release and return
      bool unlinkedWasInlined = overWrittenEntry->getIsInodeInlined();

      FhgfsOpsErr unlinkRes = unlinkOverwrittenEntryUnlocked(toParent, overWrittenEntry,
         outUnlinkedInode);

      EntryInfo unlinkEntryInfo;
      overWrittenEntry->getEntryInfo(toParentID, 0, &unlinkEntryInfo);

      // unlock everything here, but do not release toParent yet.
      toParentMutexLock.unlock(); // U N L O C K ( T O )
      safeMetaStoreLock.unlock();

      // unlinkInodeLater() requires that everything was unlocked!
      if (unlinkRes == FhgfsOpsErr_INUSE)
      {
         unlinkRes = unlinkInodeLater(&unlinkEntryInfo, unlinkedWasInlined );
         if (unlinkRes == FhgfsOpsErr_AGAIN)
            unlinkRes = unlinkOverwrittenEntry(toParent, overWrittenEntry, outUnlinkedInode);

         if (unlinkRes != FhgfsOpsErr_SUCCESS && unlinkRes != FhgfsOpsErr_PATHNOTEXISTS)
            LogContext(logContext).logErr("Failed to unlink overwritten entry:"
               " FileName: "      + newEntryName                   +
               " ParentEntryID: " + toParent->getID()              +
               " entryID: "       + overWrittenEntry->getEntryID() +
               " Error: "         + FhgfsOpsErrTk::toErrString(unlinkRes) );
      }

      delete overWrittenEntry;

      releaseDir(toParentID);

      return retVal;
   }
   else
   if (overWrittenEntry)
   {
      // TODO: Restore the overwritten entry
   }

outUnlock:

   toParentMutexLock.unlock(); // U N L O C K ( T O )
   dirStore.releaseDir(toParent->getID() );

   safeMetaStoreLock.unlock();

   SAFE_DELETE(overWrittenEntry);

   return retVal;
}
/**
 * Simple rename on the same server in the same directory.
 *
 * @param outUnlinkInode is the inode of a dirEntry being possibly overwritten (toName already
 *    existed).
 */
FhgfsOpsErr MetaStore::renameInSameDir(DirInode* parentDir, std::string fromName,
   std::string toName, FileInode** outUnlinkInode)
{
   const char* logContext = "Rename in dir";

   SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K
   SafeRWLock fromMutexLock(&parentDir->rwlock, SafeRWLock_WRITE); // L O C K ( F R O M )

   FhgfsOpsErr retVal;
   FhgfsOpsErr unlinkRes;

   DirEntry* overWrittenEntry = NULL;

   retVal = performRenameEntryInSameDir(parentDir, fromName, toName, &overWrittenEntry);

   if (retVal != FhgfsOpsErr_SUCCESS)
   {
      fromMutexLock.unlock();
      safeLock.unlock();

      SAFE_DELETE(overWrittenEntry);

      return retVal;
   }

   EntryInfo unlinkEntryInfo;
   bool unlinkedWasInlined;

   if (overWrittenEntry)
   {
      std::string parentDirID = parentDir->getID();
      overWrittenEntry->getEntryInfo(parentDirID, 0, &unlinkEntryInfo);
      unlinkedWasInlined = overWrittenEntry->getIsInodeInlined();

      unlinkRes = unlinkOverwrittenEntryUnlocked(parentDir, overWrittenEntry, outUnlinkInode);
   }
   else
   {
      *outUnlinkInode = NULL;

      // irrelevant values, just to please the compiler
      unlinkRes = FhgfsOpsErr_SUCCESS;
      unlinkedWasInlined = true;
   }

   /* Now update the ctime (attribChangeTime) of the renamed entry.
    * Only do that for Directory dentry after giving up the DirInodes (fromMutex) lock
    * as dirStore.setAttr() will aquire the InodeDirStore:: lock
    * and the lock order is InodeDirStore:: and then DirInode::  (risk of deadlock) */

   DirEntry* entry = parentDir->dirEntryCreateFromFileUnlocked(toName);
   if (likely(entry) ) // entry was just renamed to, so very likely it exists
   {
      EntryInfo entryInfo;
      std::string parentID = parentDir->getID();
      entry->getEntryInfo(parentID, 0, &entryInfo);

      fromMutexLock.unlock();
      setAttrUnlocked(&entryInfo, 0, NULL); /* This will fail if the DirInode is on another
                                             * meta server, but as updating the ctime is not
                                             * a real posix requirement (but filesystems usually
                                             * do it) we simply ignore this issue for now. */

      SAFE_DELETE(entry);
   }
   else
      fromMutexLock.unlock();

   safeLock.unlock();

   // unlink later must be called after releasing all locks

   if (overWrittenEntry)
   {
      if (unlinkRes == FhgfsOpsErr_INUSE)
      {
         unlinkRes = unlinkInodeLater(&unlinkEntryInfo, unlinkedWasInlined );
         if (unlinkRes == FhgfsOpsErr_AGAIN)
         {
            unlinkRes = unlinkOverwrittenEntry(parentDir, overWrittenEntry, outUnlinkInode);
         }
      }

      if (unlinkRes != FhgfsOpsErr_SUCCESS && unlinkRes != FhgfsOpsErr_PATHNOTEXISTS)
      {
         LogContext(logContext).logErr("Failed to unlink overwritten entry:"
            " FileName: "      + toName                         +
            " ParentEntryID: " + parentDir->getID()             +
            " entryID: "       + overWrittenEntry->getEntryID() +
            " Error: "         + FhgfsOpsErrTk::toErrString(unlinkRes) );


         // TODO: Restore the dentry
      }
   }

   SAFE_DELETE(overWrittenEntry);

   return retVal;
}