beegfs/meta/source/storage/FileInode.h
2025-08-10 01:34:16 +02:00

1010 lines
31 KiB
C++

#pragma once
#include <common/storage/striping/StripePattern.h>
#include <common/storage/RemoteStorageTarget.h>
#include <common/storage/StorageDefinitions.h>
#include <common/storage/StorageErrors.h>
#include <common/storage/PathInfo.h>
#include <common/storage/StatData.h>
#include <common/storage/striping/ChunkFileInfo.h>
#include <common/threading/UniqueRWLock.h>
#include <common/threading/SafeRWLock.h>
#include <common/threading/Condition.h>
#include <common/threading/PThread.h>
#include <common/toolkit/TimeAbs.h>
#include <common/app/log/LogContext.h>
#include <common/Common.h>
#include <session/LockingNotifier.h>
#include "Locking.h"
#include "MetadataEx.h"
#include "DiskMetaData.h"
#include "DentryStoreData.h"
#include "FileInodeStoreData.h"
typedef std::vector<DynamicFileAttribs> DynamicFileAttribsVec;
typedef DynamicFileAttribsVec::iterator DynamicFileAttribsVecIter;
typedef DynamicFileAttribsVec::const_iterator DynamicFileAttribsVecCIter;
typedef std::vector<ChunkFileInfo> ChunkFileInfoVec;
typedef ChunkFileInfoVec::iterator ChunkFileInfoVecIter;
typedef ChunkFileInfoVec::const_iterator ChunkFileInfoVecCIter;
/**
* Data are not really belonging to the inode, but we just need it to write inodes in the
* dentry-format. We need to read those from disk first or obtain it from the dentry.
*/
struct DentryCompatData
{
DentryCompatData()
: entryType(DirEntryType_INVALID), featureFlags(0)
{
}
DirEntryType entryType;
uint32_t featureFlags;
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::as<int32_t>(obj->entryType)
% obj->featureFlags;
}
bool operator==(const DentryCompatData& other) const
{
return entryType == other.entryType
&& featureFlags == other.featureFlags;
}
bool operator!=(const DentryCompatData& other) const { return !(*this == other); }
};
/**
* Our inode object, but for files only (so all file types except of directories).
* Directories are in class DirInode.
*/
class FileInode
{
friend class MetaStore;
friend class InodeFileStore;
friend class SessionFile; /* (needed for entry/range locking) */
public:
/**
* Should be rarely used and if required, only with an immediate following
* inode->deserialize(buf)
*/
FileInode();
/**
* The preferred inode initializer.
*/
FileInode(std::string entryID, FileInodeStoreData* inodeDiskData,
DirEntryType entryType, unsigned dentryFeatureFlags);
~FileInode()
{
LOG_DEBUG("Delete FileInode", Log_SPAM, std::string("Deleting inode: ") + getEntryID());
}
FhgfsOpsErr setRemoteStorageTarget(EntryInfo* entryInfo, const RemoteStorageTarget& rst);
FhgfsOpsErr clearRemoteStorageTarget(EntryInfo* entryInfo);
void decNumSessionsAndStore(EntryInfo* entryInfo, unsigned accessFlags);
static FileInode* createFromEntryInfo(EntryInfo* entryInfo);
void serializeMetaData(Serializer& ser);
std::pair<bool, LockEntryNotifyList> flockAppend(EntryLockDetails& lockDetails);
void flockAppendCancelAllWaiters();
LockEntryNotifyList flockAppendCancelByClientID(NumNodeID clientID);
std::string flockAppendGetAllAsStr();
std::pair<bool, LockEntryNotifyList> flockEntry(EntryLockDetails& lockDetails);
void flockEntryCancelAllWaiters();
LockEntryNotifyList flockEntryCancelByClientID(NumNodeID clientID);
std::string flockEntryGetAllAsStr();
std::pair<bool, LockRangeNotifyList> flockRange(RangeLockDetails& lockDetails);
void flockRangeCancelAllWaiters();
LockRangeNotifyList flockRangeCancelByClientID(NumNodeID clientNumID);
bool flockRangeGetConflictor(RangeLockDetails& lockDetails, RangeLockDetails* outConflictor);
std::string flockRangeGetAllAsStr();
void deserializeMetaData(Deserializer& des);
std::pair<FhgfsOpsErr, StringVector> listXAttr();
std::tuple<FhgfsOpsErr, std::vector<char>, ssize_t> getXAttr(const std::string& xAttrName,
size_t maxSize);
FhgfsOpsErr removeXAttr(EntryInfo* entryInfo, const std::string& xAttrName);
FhgfsOpsErr setXAttr(EntryInfo* entryInfo, const std::string& xAttrName,
const CharVector& xAttrValue, int flags);
bool operator==(const FileInode& other) const;
bool operator!=(const FileInode& other) const { return !(*this == other); }
/**
* only for unit tests
*/
void initLocksRandomForSerializationTests();
FhgfsOpsErr checkAccessAndOpen(unsigned openAccessFlags, bool bypassAccessCheck);
public:
template<typename InodeT>
class LockState
{
friend class FileInode;
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% obj->source->exclAppendLock
% obj->source->exclFLock
% obj->source->sharedFLocks
% obj->source->exclRangeFLocks
% obj->source->sharedRangeFLocks;
}
friend Deserializer& operator%(Deserializer& des, const LockState& state)
{
serialize(&state, des);
return des;
}
private:
LockState(InodeT* source) : source(source) {}
InodeT* source;
};
template<typename>
friend class LockState;
LockState<FileInode> lockState() { return {this}; }
LockState<const FileInode> lockState() const { return {this}; }
private:
FileInodeStoreData inodeDiskData;
RemoteStorageTarget rstInfo;
ChunkFileInfoVec fileInfoVec; // stores individual data for each node (e.g. file length),
// esp. the DYNAMIC ATTRIBS, that live only in memory and are not stored to disk (compared
// to static attribs)
pthread_t exclusiveTID; // ID of the thread, which has exclusiveTID access to the inode
uint32_t numSessionsRead; // open read-only
uint32_t numSessionsWrite; // open for writing or read-write
bool isInlined; // boolean if the inode inlined into the Dentry or a separate file
// append lock queues (exclusive only, entire file locking)
EntryLockDetails exclAppendLock; // current exclusiveTID lock
EntryLockDetailsList waitersExclAppendLock; // queue (append new to end, pop from top)
StringSet waitersLockIDsAppendLock; // currently enqueued lockIDs (for fast duplicate check)
// flock() queues (entire file locking, i.e. no ranges)
EntryLockDetails exclFLock; // current exclusiveTID lock
EntryLockDetailsSet sharedFLocks; // current shared locks (key is lock, value is dummy)
EntryLockDetailsList waitersExclFLock; // queue (append new to end, pop from top)
EntryLockDetailsList waitersSharedFLock; // queue (append new to end, pop from top)
StringSet waitersLockIDsFLock; // currently enqueued lockIDs (for fast duplicate check)
// fcntl() flock queues (range-based)
RangeLockExclSet exclRangeFLocks; // current exclusiveTID locks
RangeLockSharedSet sharedRangeFLocks; // current shared locks (key is lock, value is dummy)
RangeLockDetailsList waitersExclRangeFLock; // queue (append new to end, pop from top)
RangeLockDetailsList waitersSharedRangeFLock; // queue (append new to end, pop from top)
StringSet waitersLockIDsRangeFLock; // currently enqueued lockIDs (for fast duplicate check)
RWLock rwlock; // default inode lock
DentryCompatData dentryCompatData;
RWLock setGetReferenceParentLock; // just to set/get the referenceParentID
std::string referenceParentID;
AtomicSSizeT numParentRefs; /* counter how often parentDir is referenced, 0 if we
* are part of MetaStores FileStore */
static FileInode* createFromInlinedInode(EntryInfo* entryInfo);
static FileInode* createFromInodeFile(EntryInfo* entryInfo);
void initFileInfoVec();
void updateDynamicAttribs(void);
bool setAttrData(EntryInfo* entryInfo, int validAttribs, SettableFileAttribs* attribs);
bool incDecNumHardLinks(EntryInfo * entryInfo, int value);
bool storeUpdatedMetaDataBuf(char* buf, unsigned bufLen);
bool storeUpdatedMetaDataBufAsXAttr(char* buf, unsigned bufLen, std::string metaFilename);
bool storeUpdatedMetaDataBufAsContents(char* buf, unsigned bufLen, std::string metaFilename);
bool storeUpdatedMetaDataBufAsContentsInPlace(char* buf, unsigned bufLen,
std::string metaFilename);
bool storeUpdatedInodeUnlocked(EntryInfo* entryInfo,
StripePattern* updatedStripePattern = NULL);
FhgfsOpsErr storeUpdatedInlinedInodeUnlocked(EntryInfo* entryInfo,
StripePattern* updatedStripePattern = NULL);
std::string getMetaFilePath(EntryInfo* entryInfo);
bool storeRemoteStorageTargetUnlocked(EntryInfo* entryInfo);
bool storeRemoteStorageTargetBufAsXAttr(char* buf, unsigned bufLen, const std::string& metafilename);
static bool removeStoredMetaData(const std::string& id, bool isBuddyMirrored);
bool loadFromInodeFile(EntryInfo* entryInfo);
bool loadFromFileXAttr(const std::string& id, bool isBuddyMirrored);
bool loadFromFileContents(const std::string& id, bool isBuddyMirrored);
bool loadRstFromInodeFile(EntryInfo* entryInfo);
bool loadRstFromFileXAttr(EntryInfo* entryInfo);
std::pair<bool, LockEntryNotifyList> flockEntryUnlocked(EntryLockDetails& lockDetails,
EntryLockQueuesContainer* lockQs);
bool flockEntryCancelByHandle(EntryLockDetails& lockDetails,
EntryLockQueuesContainer* lockQs);
void flockEntryGenericCancelAllWaiters(EntryLockQueuesContainer* lockQs);
LockEntryNotifyList flockEntryGenericCancelByClientID(NumNodeID clientNumID,
EntryLockQueuesContainer* lockQs);
std::string flockEntryGenericGetAllAsStr(EntryLockQueuesContainer* lockQs);
bool flockEntryCheckConflicts(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs,
EntryLockDetails* outConflictor);
bool flockEntryIsGranted(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs);
bool flockEntryUnlock(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs);
void flockEntryShared(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs);
void flockEntryExclusive(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs);
LockEntryNotifyList flockEntryTryNextWaiters(EntryLockQueuesContainer* lockQs);
std::pair<bool, LockRangeNotifyList> flockRangeUnlocked(RangeLockDetails& lockDetails);
bool flockRangeCancelByHandle(RangeLockDetails& lockDetails);
bool flockRangeCheckConflicts(RangeLockDetails& lockDetails, RangeLockDetails* outConflictor);
bool flockRangeCheckConflictsEx(RangeLockDetails& lockDetails, int maxExclWaitersCheckNum,
RangeLockDetails* outConflictor);
bool flockRangeIsGranted(RangeLockDetails& lockDetails);
bool flockRangeUnlock(RangeLockDetails& lockDetails);
void flockRangeShared(RangeLockDetails& lockDetails);
void flockRangeExclusive(RangeLockDetails& lockDetails);
LockRangeNotifyList flockRangeTryNextWaiters();
public:
// inliners
void setRemoteStorageTargetUnpersistent(const RemoteStorageTarget& rst)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K
this->rstInfo.set(rst);
safeLock.unlock();
}
/**
* Save meta-data to disk
*/
bool updateInodeOnDisk(EntryInfo* entryInfo, StripePattern* updatedStripePattern = NULL)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K
bool retVal = storeUpdatedInodeUnlocked(entryInfo, updatedStripePattern);
safeLock.unlock(); // U N L O C K
return retVal;
}
bool updateInodeChangeTime(EntryInfo* entryInfo)
{
UniqueRWLock lock(rwlock, SafeRWLock_WRITE);
inodeDiskData.inodeStatData.setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec);
return storeUpdatedInodeUnlocked(entryInfo, nullptr);
}
/**
* Unlink the stored file-inode file on disk.
*/
static bool unlinkStoredInodeUnlocked(const std::string& id, bool isBuddyMirrored)
{
return removeStoredMetaData(id, isBuddyMirrored);
}
// getters & setters
std::string getEntryID()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
std::string entryID = getEntryIDUnlocked();
safeLock.unlock();
return entryID;
}
std::string getEntryIDUnlocked()
{
return this->inodeDiskData.getEntryID();
}
/**
* Note: Does not (immediately) update the persistent metadata.
* Note: Be careful with this!
*/
void setIDUnpersistent(std::string newID)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
this->inodeDiskData.setEntryID(newID);
safeLock.unlock();
}
RemoteStorageTarget* getRemoteStorageTargetInfo()
{
return &this->rstInfo;
}
StripePattern* getStripePatternUnlocked()
{
return this->inodeDiskData.getStripePattern();
}
StripePattern* getStripePattern()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
StripePattern* pattern = this->getStripePatternUnlocked();
safeLock.unlock();
return pattern;
}
/**
* Return the stripe pattern and set our value to NULL.
*
* Note: Usually this inode should not be further used anymore.
*/
StripePattern* getStripePatternAndSetToNull()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
StripePattern* pattern = this->inodeDiskData.getStripePatternAndSetToNull();
safeLock.unlock();
return pattern;
}
void setFeatureFlags(unsigned flags)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
this->inodeDiskData.setInodeFeatureFlags(flags);
safeLock.unlock();
}
void addFeatureFlag(unsigned flag)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
addFeatureFlagUnlocked(flag);
safeLock.unlock();
}
unsigned getFeatureFlags()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned flags = this->inodeDiskData.getInodeFeatureFlags();
safeLock.unlock();
return flags;
}
unsigned getUserID()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned retVal = this->inodeDiskData.getInodeStatData()->getUserID();
safeLock.unlock();
return retVal;
}
unsigned getGroupID()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned retVal = this->inodeDiskData.getInodeStatData()->getGroupID();
safeLock.unlock();
return retVal;
}
int getMode()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
int retVal = this->inodeDiskData.getInodeStatData()->getMode();
safeLock.unlock();
return retVal;
}
/* uint16_t getMirrorNodeID()
{
return this->inodeDiskData.getMirrorNodeID();
} */
/**
* Note: We do not set our class values here yet, because this method is called frequently and
* we do not need our class values here yet. So class values are only set on demand.
* this->updateDynamicAttribs() will set/update our class values·
*
* @param erroneousVec if errors occurred during vector retrieval from storage nodes
* (this parameter was used to set dynAttribsUptodate to false, but we do not have
* this value anymore since the switch to (post-)DB metadata format)
* @return false if error during save occurred (but we don't save dynAttribs currently)
*
*/
bool setDynAttribs(DynamicFileAttribsVec& newAttribsVec)
{
/* note: we cannot afford to make a disk write each time this is updated, so
we only update metadata on close currrently */
bool retVal = true;
bool anyAttribUpdated = false;
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
if(unlikely(newAttribsVec.size() != fileInfoVec.size() ) )
{ // should never happen
LOG(GENERAL, ERR, "Apply dynamic file attribs - Vector sizes do not match.");
retVal = false;
goto unlock_and_exit;
}
for(size_t i=0; i < newAttribsVec.size(); i++)
{
if(fileInfoVec[i].updateDynAttribs(newAttribsVec[i] ) )
anyAttribUpdated = true;
}
IGNORE_UNUSED_VARIABLE(anyAttribUpdated);
unlock_and_exit:
safeLock.unlock();
return retVal;
}
/**
* Note: Use this only if the file is not loaded already (because otherwise the dyn attribs
* will be outdated)
*/
static FhgfsOpsErr getStatData(EntryInfo* entryInfo, StatData& outStatData)
{
FileInode* inode = createFromEntryInfo(entryInfo);
if(!inode)
return FhgfsOpsErr_PATHNOTEXISTS;
FhgfsOpsErr retVal = inode->getStatData(outStatData);
delete inode;
return retVal;
}
/**
* Unlocked method to get statdata
*
* Note: Needs write-lock
*/
FhgfsOpsErr getStatDataUnlocked(StatData& outStatData)
{
FhgfsOpsErr statRes = FhgfsOpsErr_SUCCESS;
this->updateDynamicAttribs();
outStatData = *(this->inodeDiskData.getInodeStatData() );
/* note: we don't check numSessionsRead below (for dyn attribs outated statRes), because
that would be too much overhead; side-effect is that we don't update atime for read-only
files while they are open. */
if(numSessionsWrite)
statRes = FhgfsOpsErr_DYNAMICATTRIBSOUTDATED;
return statRes;
}
/**
* Return statData
*
* Note: Also updates dynamic attribs, so needs a write lock
*/
FhgfsOpsErr getStatData(StatData& outStatData)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
FhgfsOpsErr statRes = getStatDataUnlocked(outStatData);
safeLock.unlock();
return statRes;
}
void setStatData(StatData& statData)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
this->inodeDiskData.setInodeStatData(statData);
safeLock.unlock();
}
/**
* Note: Does not take a lock and is a very special use case. For example on moving a file
* to a remote server, when an inode object is created from a buffer and we want to
* have values from this inode object.
*/
StatData* getNotUpdatedStatDataUseCarefully()
{
return this->inodeDiskData.getInodeStatData();
}
void setNumHardlinksUnpersistent(unsigned numHardlinks)
{
/* note: this is the version for the quick on-close unlink check, so file is referenced by
* client sessions and we don't need to update persistent metadata
*/
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
this->inodeDiskData.setNumHardlinks(numHardlinks);
safeLock.unlock();
}
unsigned getNumHardlinks()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned retVal = this->inodeDiskData.getNumHardlinks();
safeLock.unlock();
return retVal;
}
/**
* Get the thread ID of the thread allow to work with this inode, so the ID, which has
* set the exclusiveTID value.
*/
pthread_t getExclusiveThreadID()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
pthread_t retVal = this->exclusiveTID;
safeLock.unlock();
return retVal;
}
void setExclusiveTID(pthread_t posixTID)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
this->exclusiveTID = posixTID;
safeLock.unlock();
}
/**
* @return num read-sessions + num write-sessions
*/
unsigned getNumSessionsAll()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned num = this->numSessionsRead + this->numSessionsWrite;
safeLock.unlock();
return num;
}
unsigned getNumSessionsRead()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned num = this->numSessionsRead;
safeLock.unlock();
return num;
}
unsigned getNumSessionsWrite()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
unsigned num = this->numSessionsWrite;
safeLock.unlock();
return num;
}
/**
* Increase number of sessions for read or write (=> file open).
*
* @param accessFlags OPENFILE_ACCESS_... flags
*/
void incNumSessions(unsigned accessFlags)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
incNumSessionsUnlocked(accessFlags);
safeLock.unlock();
}
void incNumSessionsUnlocked(unsigned accessFlags)
{
if(accessFlags & OPENFILE_ACCESS_READ)
this->numSessionsRead++;
else
this->numSessionsWrite++; // (includes read+write)
}
FileInode* clone()
{
Serializer ser;
serializeMetaData(ser);
boost::scoped_array<char> buf(new (std::nothrow) char[ser.size()]);
if (!buf)
return NULL;
ser = Serializer(buf.get(), ser.size());
serializeMetaData(ser);
FileInode* clone = new FileInode();
Deserializer des(buf.get(), ser.size());
clone->deserializeMetaData(des);
if (unlikely(!des.good()) )
{
delete clone;
clone = NULL;
}
else
{
/* Ihe inode serializer might will not set origParentEntryID and origParentUID if the file
* was not moved or the inode was not de-inlined, but the cloned inode needs these
* values */
clone->inodeDiskData.origParentEntryID = this->inodeDiskData.origParentEntryID;
clone->inodeDiskData.origParentUID = this->inodeDiskData.origParentUID;
}
return clone;
}
void incParentRef(const std::string& referenceParentID)
{
this->numParentRefs.increase();
SafeRWLock rwLock(&this->setGetReferenceParentLock, SafeRWLock_WRITE);
this->referenceParentID = referenceParentID;
rwLock.unlock();
}
void decParentRef()
{
this->numParentRefs.decrease();
}
ssize_t getNumParentRefs()
{
return this->numParentRefs.read();
}
std::string getReferenceParentID()
{
SafeRWLock rwLock(&this->setGetReferenceParentLock, SafeRWLock_READ);
std::string id = this->referenceParentID;
rwLock.unlock();
return id;
}
/**
* Update inline information.
*
* @param value If set to false pathInfo flags and dentryCompat data will be updated as
* well.
*/
void setIsInlined(bool value)
{
SafeRWLock safeLock(&this->rwlock, SafeRWLock_WRITE);
setIsInlinedUnlocked(value);
safeLock.unlock();
}
/**
* See setIsInlined().
*/
void setIsInlinedUnlocked(bool value)
{
this->isInlined = value;
if (value == false)
{
if (this->inodeDiskData.getOrigFeature() == FileInodeOrigFeature_TRUE)
{
/* If the inode gets de-inlined origParentEntryID cannot be set from parentDir
* anymore. */
addFeatureFlagUnlocked(FILEINODE_FEATURE_HAS_ORIG_PARENTID);
}
// dentryCompatData also need the update
this->dentryCompatData.featureFlags &= ~(DENTRY_FEATURE_INODE_INLINE);
}
else
this->dentryCompatData.featureFlags |= DENTRY_FEATURE_INODE_INLINE;
}
bool getIsInlined()
{
SafeRWLock rwLock(&this->rwlock, SafeRWLock_READ);
bool retVal = this->isInlined;
rwLock.unlock();
return retVal;
}
void getPathInfo(PathInfo* outPathInfo)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
this->inodeDiskData.getPathInfo(outPathInfo);
safeLock.unlock();
}
void setIsBuddyMirrored(bool mirrored = true)
{
SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE);
this->inodeDiskData.setBuddyMirrorFeatureFlag(mirrored);
safeLock.unlock();
}
bool getIsBuddyMirrored()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
bool retVal = getIsBuddyMirroredUnlocked();
safeLock.unlock();
return retVal;
}
bool getIsRstAvailable()
{
SafeRWLock safeLock(&rwlock, SafeRWLock_READ);
bool retVal = this->getIsRstAvailableUnlocked();
safeLock.unlock();
return retVal;
}
/**
* Sets the file state using a raw state value and persists it to disk.
* Only updates file state if the access flags transition is allowed based
* on active read/write sessions.
*
* @param entryInfo EntryInfo of the file to update
* @param value Raw byte value representing the new file state
* @return FhgfsOpsErr_SUCCESS if state was successfully updated to disk
* FhgfsOpsErr_INUSE if file has active client sessions
* FhgfsOpsErr_INTERNAL if state update failed to persist to disk
* @note This method is called by the FileState-based overload below.
*/
FhgfsOpsErr setFileState(EntryInfo* entryInfo, uint8_t value)
{
RWLockGuard lock(rwlock, SafeRWLock_WRITE);
FileState oldState(this->getFileStateUnlocked());
FileState newState(value);
// Prevent file state changes if file has active sessions
if (!checkAccessFlagTransition(oldState, newState))
return FhgfsOpsErr_INUSE;
this->inodeDiskData.setFileState(value);
if (this->storeUpdatedInodeUnlocked(entryInfo))
return FhgfsOpsErr_SUCCESS;
else
return FhgfsOpsErr_INTERNAL;
}
FhgfsOpsErr setFileState(EntryInfo* entryInfo, const FileState& state)
{
return setFileState(entryInfo, state.getRawValue());
}
/**
* Validates whether a file access flag transition is permitted
* based on the current active read/write sessions.
* Transition rules:
* - Acquiring stricter locks (adding flags): not allowed if conflicting sessions exist
* - Relaxing locks (removing flags): not allowed if dependent sessions exist
* (This protects HSM/special clients using bypass access privileges)
* - Full unlock (no flags): only allowed if there are no active sessions
*
* Special client handling using bypass access:
* When a file is already locked (e.g., READ_LOCK), only special clients with
* bypass permissions can have active read sessions. These sessions must finish
* before removing the lock to ensure consistency of HSM operations.
*
* @param oldState The current file state
* @param newState The requested new file state
* @return true if the transition is allowed, false otherwise
*/
bool checkAccessFlagTransition(const FileState& oldState, const FileState& newState) const
{
const uint8_t oldFlags = oldState.getAccessFlags();
const uint8_t newFlags = newState.getAccessFlags();
// no change in access flags
// (e.g., READ_LOCK -> READ_LOCK)
if (oldFlags == newFlags)
return true;
// --- Acquiring / Relaxing locks ---
const uint8_t addedFlags = newFlags & ~oldFlags;
const uint8_t removedFlags = oldFlags & ~newFlags;
// --- Acquiring stricter locks ---
// Prevent upgrading to a stricter lock if there are conflicting sessions
if ((addedFlags & AccessFlags::READ_LOCK) && numSessionsRead > 0)
return false;
if ((addedFlags & AccessFlags::WRITE_LOCK) && numSessionsWrite > 0)
return false;
// --- Relaxing locks ---
// Prevent loosening restrictions while dependent sessions are active
// These checks primarily protect special client operations using bypass access
if ((removedFlags & AccessFlags::READ_LOCK) && numSessionsRead > 0)
return false;
if ((removedFlags & AccessFlags::WRITE_LOCK) && numSessionsWrite > 0)
return false;
return true;
}
FileState getFileState()
{
RWLockGuard lock(rwlock, SafeRWLock_READ);
return FileState(this->getFileStateUnlocked());
}
bool incrementFileVersion(EntryInfo* entryInfo)
{
UniqueRWLock lock(rwlock, SafeRWLock_WRITE);
inodeDiskData.setFileVersion(inodeDiskData.getFileVersion() + 1);
return storeUpdatedInodeUnlocked(entryInfo, nullptr);
}
uint32_t getFileVersion()
{
UniqueRWLock lock(rwlock, SafeRWLock_WRITE);
return inodeDiskData.getFileVersion();
}
bool incrementMetaVersion(EntryInfo* entryInfo)
{
UniqueRWLock lock(rwlock, SafeRWLock_WRITE);
inodeDiskData.setMetaVersion(inodeDiskData.getMetaVersion() + 1);
return storeUpdatedInodeUnlocked(entryInfo, nullptr);
}
uint32_t getMetaVersion()
{
UniqueRWLock lock(rwlock, SafeRWLock_WRITE);
return inodeDiskData.getMetaVersion();
}
protected:
void setOrigParentID(std::string origParentEntryId)
{
this->inodeDiskData.setDynamicOrigParentEntryID(origParentEntryId);
}
void setPersistentOrigParentID(std::string origParentEntryId)
{
this->inodeDiskData.setPersistentOrigParentEntryID(origParentEntryId);
}
private:
/**
* Increase link counter by one
*/
void incDecNumHardlinksUnpersistentUnlocked(int value)
{
this->inodeDiskData.incDecNumHardlinks(value);
}
FileInodeStoreData* getInodeDiskData()
{
return &this->inodeDiskData;
}
void addFeatureFlagUnlocked(unsigned flag)
{
this->inodeDiskData.addInodeFeatureFlag(flag);
}
/**
* Unlocked version of getIsBuddyMirrored().
* Note: Caller must at least hold read lock.
*/
bool getIsBuddyMirroredUnlocked()
{
return this->inodeDiskData.getIsBuddyMirrored();
}
bool getIsRstAvailableUnlocked()
{
return this->inodeDiskData.getIsRstAvailable();
}
uint8_t getFileStateUnlocked() const
{
return this->inodeDiskData.getFileState();
}
};