#include #include #include #include #include #include #include #include #include "HardlinkMsgEx.h" std::tuple HardlinkMsgEx::lock( EntryLockStore& store) { // NOTE: normally we'd need to also lock on the MDS holding the destination file, // but we don't support hardlinks to different servers, yet ParentNameLock fromLock; ParentNameLock toLock; FileIDLock fileLock; FileIDLock dirLock(&store, getToDirInfo()->getEntryID(), true); // take care about lock ordering! see MirroredMessage::lock() if (getFromDirInfo()->getEntryID() < getToDirInfo()->getEntryID() || (getFromDirInfo()->getEntryID() == getToDirInfo()->getEntryID() && getFromName() < getToName())) { fromLock = {&store, getFromDirInfo()->getEntryID(), getFromName()}; toLock = {&store, getToDirInfo()->getEntryID(), getToName()}; } else if (getFromDirInfo()->getEntryID() == getToDirInfo()->getEntryID() && getFromName() == getToName()) { fromLock = {&store, getFromDirInfo()->getEntryID(), getFromName()}; } else { toLock = {&store, getToDirInfo()->getEntryID(), getToName()}; fromLock = {&store, getFromDirInfo()->getEntryID(), getFromName()}; } // We need to lock the inode because hardlinking modifies the link count. // If the file inode is present on the current meta node or buddy-group, // then acquire the inodeLock. App* app = Program::getApp(); NumNodeID ownerNodeID = getFromInfo()->getOwnerNodeID(); bool isLocalInode = ((!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || (isMirrored() && ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID())); if (isLocalInode) fileLock = {&store, getFromInfo()->getEntryID(), true}; return std::make_tuple( std::move(dirLock), std::move(fromLock), std::move(toLock), std::move(fileLock)); } bool HardlinkMsgEx::processIncoming(ResponseContext& ctx) { LOG_DBG(GENERAL, DEBUG, "", ("fromDirID", getFromDirInfo()->getEntryID()), ("fromID", getFromInfo()->getEntryID()), ("fromName", getFromName()), ("toDirID", getToDirInfo()->getEntryID()), ("toName", getToName()), ("buddyMirrored", getToDirInfo()->getIsBuddyMirrored())); BaseType::processIncoming(ctx); updateNodeOp(ctx, MetaOpCounter_HARDLINK); return true; } std::unique_ptr HardlinkMsgEx::executeLocally(ResponseContext& ctx, bool isSecondary) { const char* logContext = "create hard link"; App* app = Program::getApp(); MetaStore* metaStore = app->getMetaStore(); FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; unsigned updatedLinkCount = 0; // reference directory where hardlink needs to be created DirInode* toDir = metaStore->referenceDir(getToDirInfo()->getEntryID(), getToDirInfo()->getIsBuddyMirrored(), true); // check if file dentry already exists with same name as hardlink // return error if link already exists if (likely(toDir)) { DirEntry newLink(getToName()); if (toDir->getFileDentry(getToName(), newLink)) { metaStore->releaseDir(toDir->getID()); return boost::make_unique(FhgfsOpsErr_EXISTS); } } else { LogContext(logContext).logErr(std::string("target directory for hard-link doesn't exist," "dirname: " + getToDirInfo()->getFileName())); return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); } // Prevent hardlinks across directories with different buddy mirror configurations if (getFromDirInfo()->getIsBuddyMirrored() != getToDirInfo()->getIsBuddyMirrored()) { LogContext(logContext).logErr("Hardlinks not supported across directories with different " "buddy mirror config setting. SourceDirID: " + getFromDirInfo()->getEntryID() + "; TargetDirID: " + getToDirInfo()->getEntryID()); metaStore->releaseDir(toDir->getID()); return boost::make_unique(FhgfsOpsErr_NOTSUPP); } // check whether local node/group owns source file's inode or not NumNodeID ownerNodeID = getFromInfo()->getOwnerNodeID(); bool isLocalOwner = ((!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || (isMirrored() && ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID())); if (isLocalOwner) { std::tie(retVal, updatedLinkCount) = metaStore->makeNewHardlink(getFromInfo()); } else if (!isSecondary) { MoveFileInodeMsg deinlineMsg(getFromInfo(), MODE_DEINLINE, true); RequestResponseArgs rrArgs(NULL, &deinlineMsg, NETMSGTYPE_MoveFileInodeResp); RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); rrNode.setTargetStates(app->getMetaStateStore()); if (getFromInfo()->getIsBuddyMirrored()) { rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); } do { // send request to other MDs and receive response FhgfsOpsErr resp = MessagingTk::requestResponseNode(&rrNode, &rrArgs); if (unlikely(resp != FhgfsOpsErr_SUCCESS)) { // error LogContext(logContext).logErr("Communication with metadata server failed. " "nodeID: " + ownerNodeID.str()); retVal = resp; break; } // response received const auto moveFileInodeRespMsg = (MoveFileInodeRespMsg*) rrArgs.outRespMsg.get(); FhgfsOpsErr res = moveFileInodeRespMsg->getResult(); if (res != FhgfsOpsErr_SUCCESS) { // error: either source file not exists or deinline operation failed LogContext(logContext).logErr("verify and move file inode failed! nodeID: " + ownerNodeID.str() + "; entryID: " + getFromInfo()->getEntryID()); retVal = res; break; } // success retVal = res; updatedLinkCount = moveFileInodeRespMsg->getHardlinkCount(); } while (false); } // If verify/deinline inode and link count update is successful - only then create new // dentry in "toDir" with entryID and ownerNodeID of "fromFile" which might reside // on another meta node/buddy-group if (retVal == FhgfsOpsErr_SUCCESS) { DirEntry newHardlink(DirEntryType_REGULARFILE, getToName(), getFromInfo()->getEntryID(), getFromInfo()->getOwnerNodeID()); newHardlink.removeDentryFeatureFlag(DENTRY_FEATURE_INODE_INLINE); // buddy mirroring is inherited from parent directory if (toDir->getIsBuddyMirrored()) newHardlink.setBuddyMirrorFeatureFlag(); FhgfsOpsErr makeRes = toDir->makeDirEntry(newHardlink); if (makeRes != FhgfsOpsErr_SUCCESS) { // compensate link count if dentry create fails // // for a remote inode, Use SetAttrMsg to decrease link count // since its a mirrored message so it will update link count on both primary and // secondary hence don't send this message from secondary buddy if (!isSecondary && !isLocalOwner) incDecRemoteLinkCount(ownerNodeID, false); else metaStore->incDecLinkCount(getFromInfo(), -1); retVal = makeRes; } } if (retVal == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) { fixInodeTimestamp(*toDir, dirTimestamps); if (isLocalOwner) { auto [file, referenceRes] = metaStore->referenceFile(getFromInfo()); if (file) { fixInodeTimestamp(*file, fileTimestamps, getFromInfo()); metaStore->releaseFile(getFromInfo()->getParentEntryID(), file); } } } metaStore->releaseDir(toDir->getID()); if (!isSecondary && retVal == FhgfsOpsErr_SUCCESS && app->getFileEventLogger() && getFileEvent()) { EventContext eventCtx = makeEventContext(getFromInfo(), getFromInfo()->getParentEntryID(), getMsgHeaderUserID(), "", updatedLinkCount, isSecondary); logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); } return boost::make_unique(retVal); } FhgfsOpsErr HardlinkMsgEx::incDecRemoteLinkCount(NumNodeID const& ownerNodeID, bool increment) { const char* logContext = "incDecRemoteLinkCount"; App* app = Program::getApp(); FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; SettableFileAttribs dummyAttrib = {}; SetAttrMsg msg(getFromInfo(), 0, &dummyAttrib); if (increment) { msg.addMsgHeaderFeatureFlag(SETATTRMSG_FLAG_INCR_NLINKCNT); } else { msg.addMsgHeaderFeatureFlag(SETATTRMSG_FLAG_DECR_NLINKCNT); } RequestResponseArgs rrArgs(NULL, &msg, NETMSGTYPE_SetAttrResp); RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); rrNode.setTargetStates(app->getMetaStateStore()); if (getFromInfo()->getIsBuddyMirrored()) { rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); } do { // send request to other MDs and receive response FhgfsOpsErr resp = MessagingTk::requestResponseNode(&rrNode, &rrArgs); if (unlikely(resp != FhgfsOpsErr_SUCCESS)) { // error LogContext(logContext).log(Log_WARNING, "Communication with metadata server failed. " "nodeID: " + ownerNodeID.str()); retVal = resp; break; } // response received const auto incLinkCountResp = (SetAttrRespMsg*) rrArgs.outRespMsg.get(); FhgfsOpsErr res = static_cast(incLinkCountResp->getValue()); if (res != FhgfsOpsErr_SUCCESS) { // error: either source file not exists or nlink count increment failed std::string operationType = (increment ? "increase": "decrease"); LogContext(logContext).logErr("nLink count " + operationType + " failed! nodeID: " + ownerNodeID.str() + "; " + "entryID: " + getFromInfo()->getEntryID()); retVal = res; break; } // success retVal = res; } while (false); return retVal; } void HardlinkMsgEx::forwardToSecondary(ResponseContext& ctx) { sendToSecondary(ctx, *this, NETMSGTYPE_HardlinkResp); }