/* * MetaStoreRenameHelper.cpp * * These methods belong to class MetaStore, but are all related to rename() */ #include #include #include #include #include #include "MetaStore.h" #include #include #include "MetaStore.h" #include /** * 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, const std::string& fromName, const std::string& toName, std::unique_ptr* outUnlinkInode, DirEntry*& outOverWrittenEntry, bool& outUnlinkedWasInlined) { 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 = FhgfsOpsErr_SUCCESS; // initialize just to please compiler outOverWrittenEntry = NULL; retVal = performRenameEntryInSameDir(parentDir, fromName, toName, &outOverWrittenEntry); if (retVal != FhgfsOpsErr_SUCCESS) { fromMutexLock.unlock(); safeLock.unlock(); SAFE_DELETE(outOverWrittenEntry); return retVal; } EntryInfo unlinkEntryInfo; // unlink for non-inlined inode will be handled later if (outOverWrittenEntry) { const std::string& parentDirID = parentDir.getID(); outOverWrittenEntry->getEntryInfo(parentDirID, 0, &unlinkEntryInfo); outUnlinkedWasInlined = outOverWrittenEntry->getIsInodeInlined(); if (outOverWrittenEntry->getIsInodeInlined()) { unlinkRes = unlinkOverwrittenEntryUnlocked(parentDir, outOverWrittenEntry, outUnlinkInode); } else { outUnlinkInode->reset(); unlinkRes = FhgfsOpsErr_SUCCESS; } } /* 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; const 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 (outOverWrittenEntry) { if (unlinkRes == FhgfsOpsErr_INUSE) { unlinkRes = unlinkInodeLater(&unlinkEntryInfo, outUnlinkedWasInlined ); if (unlinkRes == FhgfsOpsErr_AGAIN) { unlinkRes = unlinkOverwrittenEntry(parentDir, outOverWrittenEntry, outUnlinkInode); } } if (unlinkRes != FhgfsOpsErr_SUCCESS && unlinkRes != FhgfsOpsErr_PATHNOTEXISTS) { LogContext(logContext).logErr("Failed to unlink overwritten entry:" " FileName: " + toName + " ParentEntryID: " + parentDir.getID() + " entryID: " + outOverWrittenEntry->getEntryID() + " Error: " + boost::lexical_cast(unlinkRes)); // TODO: Restore the dentry } } return retVal; } /** * Unlink an overwritten dentry. From this dentry either the #fsid# entry or its inode is left. * * Locking: * We lock everything ourself */ FhgfsOpsErr MetaStore::unlinkOverwrittenEntry(DirInode& parentDir, DirEntry* overWrittenEntry, std::unique_ptr* outInode) { SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K SafeRWLock parentLock(&parentDir.rwlock, SafeRWLock_WRITE); FhgfsOpsErr unlinkRes = unlinkOverwrittenEntryUnlocked(parentDir, overWrittenEntry, outInode); parentLock.unlock(); safeLock.unlock(); return unlinkRes; } /** * See unlinkOverwrittenEntry() for details * * Locking: * MetaStore rwlock: Read-lock * parentDir : Write-lock */ FhgfsOpsErr MetaStore::unlinkOverwrittenEntryUnlocked(DirInode& parentDir, DirEntry* overWrittenEntry, std::unique_ptr* outInode) { FhgfsOpsErr unlinkRes; unsigned outNumHardlinks; // Not used here! if (overWrittenEntry->getIsInodeInlined() ) { /* We advise the calling code not to try to delete the entryName dentry, * as renameEntryUnlocked() already did that */ unlinkRes = unlinkDirEntryWithInlinedInodeUnlocked("", parentDir, overWrittenEntry, DirEntry_UNLINK_ID, outInode, outNumHardlinks); } else { // And also do not try to delete the dir-entry-by-name here. unlinkRes = unlinkDentryAndInodeUnlocked("", parentDir, overWrittenEntry, DirEntry_UNLINK_ID, outInode, outNumHardlinks); } return unlinkRes; } /** * Perform the rename action here. * * In constrast to the moving...()-methods, this method performs a simple rename of an entry, * where no moving is involved. * * Rules: Files can overwrite existing files, but not existing dirs. Dirs cannot overwrite any * existing entry. * * @param dir needs to write-locked already * @param outOverwrittenEntry the caller is responsible for the deletion of the local file; * accoring to the rules, this can only be an overwritten file, not a dir; may not be NULL. * Also, we only overwrite the entryName dentry, but not the ID dentry. * * Note: MetaStore is ReadLocked, dir is WriteLocked */ FhgfsOpsErr MetaStore::performRenameEntryInSameDir(DirInode& dir, const std::string& fromName, const std::string& toName, DirEntry** outOverwrittenEntry) { *outOverwrittenEntry = NULL; FhgfsOpsErr retVal; // load DirInode on demand if required, we need it now bool loadSuccess = dir.loadIfNotLoadedUnlocked(); if (!loadSuccess) return FhgfsOpsErr_PATHNOTEXISTS; // of the file being renamed DirEntry* fromEntry = dir.dirEntryCreateFromFileUnlocked(fromName); if (!fromEntry) { return FhgfsOpsErr_PATHNOTEXISTS; } EntryInfo fromEntryInfo; const std::string& parentEntryID = dir.getID(); fromEntry->getEntryInfo(parentEntryID, 0, &fromEntryInfo); // reference the inode MetaFileHandle fromFileInode; // DirInode* fromDirInode = NULL; if (DirEntryType_ISDIR(fromEntryInfo.getEntryType() ) ) { // TODO, exclusive lock } else { // for nonInlined inode(s) - inode may not be present on local meta server // only try to referece file inode for inlined inode(s) if (fromEntry->getIsInodeInlined()) { FhgfsOpsErr referenceRes; std::tie(fromFileInode, referenceRes) = referenceFileUnlocked(dir, &fromEntryInfo); if (!fromFileInode) { /* Note: The inode might be exclusively locked and a remote rename op might be in progress. * If that fails we should actually continue with our rename. That will be solved * in the future by using hardlinks for remote renaming. */ return referenceRes; } } } DirEntry* overWriteEntry = dir.dirEntryCreateFromFileUnlocked(toName); if (overWriteEntry) { // sanity checks if we really shall overwrite the entry const std::string& parentID = dir.getID(); EntryInfo fromEntryInfo; fromEntry->getEntryInfo(parentID , 0, &fromEntryInfo); EntryInfo overWriteEntryInfo; overWriteEntry->getEntryInfo(parentID, 0, &overWriteEntryInfo); bool isSameInode; retVal = checkRenameOverwrite(&fromEntryInfo, &overWriteEntryInfo, isSameInode); if (isSameInode) { delete(overWriteEntry); overWriteEntry = NULL; goto out; // nothing to do then, rename request will be silently ignored } if (retVal != FhgfsOpsErr_SUCCESS) goto out; // not allowed for some reasons, return it to the user } // eventually rename here retVal = dir.renameDirEntryUnlocked(fromName, toName, overWriteEntry); /* Note: If rename faild and and an existing toName was to be overwritten, we don't need to care * about it, the underlying file system has to handle it. */ /* Note2: Do not decrease directory link count here, even if we overwrote an entry. We will do * that later on in common unlink code, when we going to unlink the entry from * the #fsIDs# dir. */ if (fromFileInode) releaseFileUnlocked(dir, fromFileInode); else { // TODO dir } out: if (retVal == FhgfsOpsErr_SUCCESS) *outOverwrittenEntry = overWriteEntry; else SAFE_DELETE(overWriteEntry); SAFE_DELETE(fromEntry); // always exists when we are here return retVal; } /** * Check if overwriting an entry on rename is allowed. */ FhgfsOpsErr MetaStore::checkRenameOverwrite(EntryInfo* fromEntry, EntryInfo* overWriteEntry, bool& outIsSameInode) { outIsSameInode = false; // check if we are going to rename to a dentry with the same inode if (fromEntry->getEntryID() == overWriteEntry->getEntryID() ) { // According to posix we must not do anything and return success. outIsSameInode = true; return FhgfsOpsErr_SUCCESS; } if (overWriteEntry->getEntryType() == DirEntryType_DIRECTORY) { return FhgfsOpsErr_EXISTS; } /* TODO: We should allow this if overWriteEntry->getEntryType() == DirEntryType_DIRECTORY * and overWriteEntry is empty. */ if (fromEntry->getEntryType() == DirEntryType_DIRECTORY) { return FhgfsOpsErr_EXISTS; } return FhgfsOpsErr_SUCCESS; } /** * 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 * @param overWriteInfo entryInfo of overwritten (and possibly unlinked inode if it was inlined) file * 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, DirInode& toParent, const std::string& newEntryName, const char* buf, uint32_t bufLen, std::unique_ptr* outUnlinkedInode, EntryInfo* overWriteInfo, EntryInfo& newFileInfo) { // 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->reset(); SafeRWLock safeMetaStoreLock(&rwlock, SafeRWLock_READ); // L O C K SafeRWLock toParentMutexLock(&toParent.rwlock, SafeRWLock_WRITE); // L O C K ( T O ) std::unique_ptr overWrittenEntry(toParent.dirEntryCreateFromFileUnlocked(newEntryName)); if (overWrittenEntry) { const 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.get(), 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 Deserializer des(buf, bufLen); inode->deserializeMetaData(des); if (!des.good()) { 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; } // ensure that the buddyMirrored flag of the created inode is set correctly. the source could // check this as well, but since we already have the destination dir inode, we are in a better // position to do this. if (toParent.getIsBuddyMirrored()) inode->setIsBuddyMirrored(); else inode->setIsBuddyMirrored(false); // deserialize RSTs and set in inode object if (inode->getIsRstAvailable()) { RemoteStorageTarget rstInfo; des % rstInfo; inode->setRemoteStorageTargetUnpersistent(rstInfo); } // destructs inode retVal = mkMetaFileUnlocked(toParent, newEntryName, fromFileInfo, inode); } if (retVal == FhgfsOpsErr_SUCCESS) { if (!toParent.entries.getFileEntryInfo(newEntryName, newFileInfo)) retVal = FhgfsOpsErr_INTERNAL; } if (overWrittenEntry && overWrittenEntry->getIsInodeInlined() && (retVal == FhgfsOpsErr_SUCCESS)) { // unlink overwritten entry if it had an inlined inode (non-inlined inodes will be unlinked later) bool unlinkedWasInlined = overWrittenEntry->getIsInodeInlined(); FhgfsOpsErr unlinkRes = unlinkOverwrittenEntryUnlocked(toParent, overWrittenEntry.get(), outUnlinkedInode); EntryInfo unlinkEntryInfo; overWrittenEntry->getEntryInfo(toParent.getID(), 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.get(), 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: " + boost::lexical_cast(unlinkRes)); } return retVal; } else if (overWrittenEntry) { // TODO: Restore the overwritten entry } outUnlock: toParentMutexLock.unlock(); // U N L O C K ( T O ) safeMetaStoreLock.unlock(); return retVal; } /** * Copies (serializes) the original file object to a buffer. * * Note: This works by inserting a temporary placeholder and returning the original, so remember to * call movingComplete() * * @param buf target buffer for serialization * @param bufLen must be at least META_SERBUF_SIZE */ FhgfsOpsErr MetaStore::moveRemoteFileBegin(DirInode& dir, EntryInfo* entryInfo, char* buf, size_t bufLen, size_t* outUsedBufLen) { FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; SafeRWLock safeLock(&this->rwlock, SafeRWLock_READ); // L O C K // lock the dir to make sure no renameInSameDir is going on SafeRWLock safeDirLock(&dir.rwlock, SafeRWLock_READ); if (entryInfo->getIsInlined()) { if (this->fileStore.isInStore(entryInfo->getEntryID())) retVal = this->fileStore.moveRemoteBegin(entryInfo, buf, bufLen, outUsedBufLen); else retVal = dir.fileStore.moveRemoteBegin(entryInfo, buf, bufLen, outUsedBufLen); } else { // handle dentry for a non-inlined inode DirEntry fileDentry(entryInfo->getFileName()); if (!dir.getDentryUnlocked(entryInfo->getFileName(), fileDentry)) return FhgfsOpsErr_PATHNOTEXISTS; else retVal = FhgfsOpsErr_SUCCESS; Serializer ser(buf, bufLen); fileDentry.serializeDentry(ser); *outUsedBufLen = ser.size(); } safeDirLock.unlock(); safeLock.unlock(); // U N L O C K return retVal; } void MetaStore::moveRemoteFileComplete(DirInode& dir, const std::string& entryID) { SafeRWLock safeLock(&this->rwlock, SafeRWLock_WRITE); // L O C K if (this->fileStore.isInStore(entryID) ) this->fileStore.moveRemoteComplete(entryID); else { SafeRWLock safeDirLock(&dir.rwlock, SafeRWLock_READ); dir.fileStore.moveRemoteComplete(entryID); safeDirLock.unlock(); } safeLock.unlock(); // U N L O C K }