nfs4: Improve delegation handling
* Use the delegation stateid instead of the open stateid when sending a read, write, or write stat request. * Ensure that the uid and gid of a request reflect the user who opened the file in question. * Add measures to avoid deadlocks when a delegation is recalled. * Fix a race condition in which OpenState can be deleted before an IO job is done with it. * Possible fix for #19694. The NFS 4.0 RFC specifies that the if a delegation is held, the delegation stateid should be used for IO requests, and for SETATTR (write stat) requests that set file size. Change-Id: I9604ef58e3232f64d1e02ab180c603220e967e1d Reviewed-on: https://review.haiku-os.org/c/haiku/+/9587 Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
@@ -147,7 +147,9 @@ Cookie::CancelAll()
|
||||
|
||||
OpenStateCookie::OpenStateCookie(FileSystem* fileSystem)
|
||||
:
|
||||
Cookie(fileSystem)
|
||||
Cookie(fileSystem),
|
||||
fUid(geteuid()),
|
||||
fGid(getegid())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@ struct OpenStateCookie : public Cookie {
|
||||
OpenState* fOpenState;
|
||||
uint32 fMode;
|
||||
|
||||
uid_t fUid;
|
||||
gid_t fGid;
|
||||
|
||||
OpenStateCookie(FileSystem* fileSystem);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,22 +20,55 @@ Delegation::Delegation(const OpenDelegationData& data, Inode* inode,
|
||||
fData(data),
|
||||
fInode(inode),
|
||||
fAttribute(attribute),
|
||||
fStateSeq(data.fStateSeq),
|
||||
fUid(geteuid()),
|
||||
fGid(getegid())
|
||||
fGid(getegid()),
|
||||
fRecalled(false)
|
||||
{
|
||||
ASSERT(inode != NULL);
|
||||
memcpy(fStateID, data.fStateID, sizeof(fStateID));
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Delegation::GiveUp(bool truncate)
|
||||
{
|
||||
PrepareGiveUp(truncate);
|
||||
|
||||
return DoGiveUp(truncate);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Delegation::PrepareGiveUp(bool truncate)
|
||||
{
|
||||
status_t status = B_OK;
|
||||
if (!fAttribute && !truncate)
|
||||
fInode->SyncAndCommit(true);
|
||||
status = fInode->Sync(true, false);
|
||||
|
||||
ReturnDelegation();
|
||||
return status;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
|
||||
status_t
|
||||
Delegation::DoGiveUp(bool truncate, bool wait)
|
||||
{
|
||||
if (!fAttribute && !truncate && wait)
|
||||
fInode->WaitAIOComplete();
|
||||
|
||||
status_t status = ReturnDelegation();
|
||||
|
||||
fInode->Commit(fUid, fGid);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Delegation::GetStateIDandSeq(uint32* stateID, uint32& stateSeq) const
|
||||
{
|
||||
memcpy(stateID, fStateID, sizeof(uint32) * 3);
|
||||
stateSeq = fStateSeq;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,10 +24,15 @@ public:
|
||||
uint64 clientID, bool attr = false);
|
||||
|
||||
status_t GiveUp(bool truncate = false);
|
||||
status_t PrepareGiveUp(bool truncate);
|
||||
status_t DoGiveUp(bool truncate, bool wait = true);
|
||||
|
||||
inline void SetData(const OpenDelegationData& data);
|
||||
inline Inode* GetInode() const;
|
||||
void GetStateIDandSeq(uint32* stateID, uint32& stateSeq) const;
|
||||
inline OpenDelegation Type();
|
||||
inline void MarkRecalled();
|
||||
inline bool RecallInitiated() const;
|
||||
|
||||
void Dump(void (*xprintf)(const char*, ...) = dprintf) const;
|
||||
|
||||
@@ -39,8 +44,11 @@ private:
|
||||
OpenDelegationData fData;
|
||||
Inode* fInode;
|
||||
bool fAttribute;
|
||||
uint32 fStateID[3];
|
||||
uint32 fStateSeq;
|
||||
uid_t fUid;
|
||||
gid_t fGid;
|
||||
bool fRecalled;
|
||||
};
|
||||
|
||||
|
||||
@@ -65,5 +73,19 @@ Delegation::Type()
|
||||
}
|
||||
|
||||
|
||||
inline void
|
||||
Delegation::MarkRecalled()
|
||||
{
|
||||
fRecalled = true;
|
||||
}
|
||||
|
||||
|
||||
inline bool
|
||||
Delegation::RecallInitiated() const
|
||||
{
|
||||
return fRecalled;
|
||||
}
|
||||
|
||||
|
||||
#endif // DELEGATION_H
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ Inode::Inode()
|
||||
fWriteDirty(false),
|
||||
fAIOWait(create_sem(1, NULL)),
|
||||
fAIOCount(0),
|
||||
fOpenStateReleasesPending(0),
|
||||
fStale(false)
|
||||
{
|
||||
rw_lock_init(&fDelegationLock, "nfs4 Inode::fDelegationLock");
|
||||
@@ -704,12 +705,15 @@ Inode::WriteStat(const struct stat* st, uint32 mask, OpenAttrCookie* cookie)
|
||||
i++;
|
||||
}
|
||||
|
||||
ReadLocker delegationLocker(fDelegationLock);
|
||||
if (cookie == NULL) {
|
||||
MutexLocker stateLocker(fStateLock);
|
||||
ASSERT(fOpenState != NULL || !(mask & B_STAT_SIZE));
|
||||
result = NFS4Inode::WriteStat(fOpenState, attr, i);
|
||||
} else
|
||||
result = NFS4Inode::WriteStat(cookie->fOpenState, attr, i);
|
||||
result = NFS4Inode::WriteStat(fOpenState, fDelegation, attr, i);
|
||||
} else {
|
||||
ASSERT(cookie->fOpenState->fDelegation == fDelegation);
|
||||
result = NFS4Inode::WriteStat(cookie->fOpenState, NULL, attr, i);
|
||||
}
|
||||
|
||||
fMetaCache.InvalidateStat();
|
||||
|
||||
@@ -974,6 +978,52 @@ Inode::RecallDelegation(bool truncate)
|
||||
}
|
||||
|
||||
|
||||
/*! Flush write data to the server if needed before returning the delegation.
|
||||
@post If data needs to be flushed, an IO job is enqueued but may not be complete.
|
||||
*/
|
||||
void
|
||||
Inode::PrepareDelegationRecall(bool truncate)
|
||||
{
|
||||
rw_lock_write_lock(&fDelegationLock);
|
||||
fDelegation->MarkRecalled();
|
||||
rw_lock_write_unlock(&fDelegationLock);
|
||||
|
||||
ReadLocker _(fDelegationLock);
|
||||
if (fDelegation == NULL)
|
||||
return;
|
||||
|
||||
fDelegation->PrepareGiveUp(truncate);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*! Return the delegation after data has been flushed to the server.
|
||||
@pre RecallDelegationAsyncPrep has been called.
|
||||
*/
|
||||
void
|
||||
Inode::RecallDelegationAsync(bool truncate)
|
||||
{
|
||||
WriteLocker _(fDelegationLock);
|
||||
if (fDelegation == NULL)
|
||||
return;
|
||||
|
||||
fDelegation->DoGiveUp(truncate, false);
|
||||
|
||||
fMetaCache.UnlockValid();
|
||||
fFileSystem->RemoveDelegation(fDelegation);
|
||||
|
||||
MutexLocker stateLocker(fStateLock);
|
||||
fOpenState->fDelegation = NULL;
|
||||
ReleaseOpenState();
|
||||
|
||||
delete fDelegation;
|
||||
fDelegation = NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Inode::RecallReadDelegation()
|
||||
{
|
||||
@@ -1003,27 +1053,90 @@ Inode::ReturnDelegation(bool truncate)
|
||||
}
|
||||
|
||||
|
||||
/*! Temporarily unlock the locks that need to be acquired by the WorkQueue when a delegation
|
||||
is recalled.
|
||||
@pre fStateLock is locked and fDelegation lock is read-locked.
|
||||
*/
|
||||
void
|
||||
Inode::UnlockAndRelockStateLocks()
|
||||
{
|
||||
rw_lock_read_unlock(&fDelegationLock);
|
||||
mutex_unlock(&fStateLock);
|
||||
rw_lock_read_lock(&fDelegationLock);
|
||||
mutex_lock(&fStateLock);
|
||||
}
|
||||
|
||||
|
||||
/*! Temporarily unlock fWriteLock. Useful for allowing an IO job to complete.
|
||||
@pre fWriteLock is write-locked and fDelegationLock is read-locked.
|
||||
*/
|
||||
void
|
||||
Inode::UnlockAndRelockWriteLock()
|
||||
{
|
||||
rw_lock_write_unlock(&fWriteLock);
|
||||
rw_lock_write_lock(&fWriteLock);
|
||||
}
|
||||
|
||||
|
||||
/*! Release a reference to fOpenState or set up later release if IO is not complete.
|
||||
@pre fStateLock is locked.
|
||||
*/
|
||||
void
|
||||
Inode::ReleaseOpenState()
|
||||
{
|
||||
ASSERT(fOpenState != NULL);
|
||||
|
||||
if (fOpenState->ReleaseReference() == 1) {
|
||||
ASSERT(fAIOCount == 0);
|
||||
fOpenState = NULL;
|
||||
// If IO is pending, wait until it is finished to release the (possibly last) reference.
|
||||
// It would be simpler to call WaitAIOComplete here, but that won't work if this
|
||||
// is the WorkQueue thread.
|
||||
MutexLocker _(fAIOLock);
|
||||
if (fAIOCount > 0) {
|
||||
++fOpenStateReleasesPending;
|
||||
} else {
|
||||
if (fOpenState->ReleaseReference() == 1)
|
||||
fOpenState = NULL;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*! Sync file cache data to server.
|
||||
@param wait If true, the function returns only after the sync is finished.
|
||||
*/
|
||||
status_t
|
||||
Inode::Sync(bool force, bool wait)
|
||||
{
|
||||
ReadLocker locker(fDelegationLock);
|
||||
if (!force && fDelegation != NULL && fDelegation->Type() == OPEN_DELEGATE_WRITE
|
||||
&& !fDelegation->RecallInitiated()) {
|
||||
locker.Unlock();
|
||||
if (wait == true) {
|
||||
// Wait for any IO jobs that may already be enqueued.
|
||||
WaitAIOComplete();
|
||||
}
|
||||
return B_OK;
|
||||
}
|
||||
locker.Unlock();
|
||||
|
||||
status_t status = file_cache_sync(fFileCache);
|
||||
if (wait == true)
|
||||
WaitAIOComplete();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::SyncAndCommit(bool force)
|
||||
Inode::SyncAndCommit(bool force, OpenStateCookie* cookie)
|
||||
{
|
||||
if (!force && fDelegation != NULL)
|
||||
return B_OK;
|
||||
Sync(force, true);
|
||||
|
||||
file_cache_sync(fFileCache);
|
||||
WaitAIOComplete();
|
||||
return Commit();
|
||||
// The server is liable to deny a commit request that does not come from a user who
|
||||
// opened the file.
|
||||
uid_t uid = cookie != NULL ? cookie->fUid : geteuid();
|
||||
gid_t gid = cookie != NULL ? cookie->fGid : getegid();
|
||||
return Commit(uid, gid);
|
||||
}
|
||||
|
||||
|
||||
@@ -1040,11 +1153,33 @@ Inode::BeginAIOOp()
|
||||
void
|
||||
Inode::EndAIOOp()
|
||||
{
|
||||
MutexLocker _(fAIOLock);
|
||||
MutexLocker AIOLocker(fAIOLock);
|
||||
ASSERT(fAIOCount > 0);
|
||||
fAIOCount--;
|
||||
if (fAIOCount == 0)
|
||||
release_sem(fAIOWait);
|
||||
|
||||
if (fOpenStateReleasesPending > 0) {
|
||||
MutexLocker stateLocker(fStateLock);
|
||||
--fOpenStateReleasesPending;
|
||||
if (fOpenState->ReleaseReference() == 1) {
|
||||
ASSERT(fAIOCount == 0);
|
||||
ASSERT(fOpenStateReleasesPending == 0);
|
||||
fOpenState = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Inode::AIOIncomplete()
|
||||
{
|
||||
MutexLocker _(fAIOLock);
|
||||
|
||||
if (fAIOCount > 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -40,16 +40,22 @@ public:
|
||||
|
||||
inline OpenState* GetOpenState();
|
||||
|
||||
inline Delegation* GetDelegation() const;
|
||||
void SetDelegation(Delegation* delegation);
|
||||
void RecallDelegation(bool truncate = false);
|
||||
void PrepareDelegationRecall(bool truncate = false);
|
||||
void RecallDelegationAsync(bool truncate = false);
|
||||
void RecallReadDelegation();
|
||||
void UnlockAndRelockStateLocks();
|
||||
void UnlockAndRelockWriteLock();
|
||||
|
||||
status_t LookUp(const char* name, ino_t* id);
|
||||
|
||||
status_t Access(int mode);
|
||||
|
||||
status_t Commit();
|
||||
status_t SyncAndCommit(bool force = false);
|
||||
status_t Sync(bool force = false, bool wait = true);
|
||||
status_t Commit(uid_t uid, gid_t gid);
|
||||
status_t SyncAndCommit(bool force = false, OpenStateCookie* cookie = NULL);
|
||||
|
||||
status_t CreateObject(const char* name, const char* path,
|
||||
int mode, FileType type, ino_t* id);
|
||||
@@ -117,6 +123,7 @@ public:
|
||||
|
||||
void BeginAIOOp();
|
||||
void EndAIOOp();
|
||||
bool AIOIncomplete();
|
||||
inline void WaitAIOComplete();
|
||||
|
||||
inline void SetStale(bool stale = true);
|
||||
@@ -177,6 +184,7 @@ private:
|
||||
sem_id fAIOWait;
|
||||
uint32 fAIOCount;
|
||||
mutex fAIOLock;
|
||||
uint32 fOpenStateReleasesPending;
|
||||
|
||||
bool fStale;
|
||||
};
|
||||
@@ -266,6 +274,13 @@ Inode::GetOpenState()
|
||||
}
|
||||
|
||||
|
||||
inline Delegation*
|
||||
Inode::GetDelegation() const
|
||||
{
|
||||
return fDelegation;
|
||||
}
|
||||
|
||||
|
||||
inline void
|
||||
Inode::SetStale(bool stale)
|
||||
{
|
||||
|
||||
@@ -115,6 +115,7 @@ Inode::Open(int mode, OpenFileCookie* cookie)
|
||||
state->fInfo = fInfo;
|
||||
state->fFileSystem = fFileSystem;
|
||||
state->fMode = mode & O_RWMASK;
|
||||
state->fDelegation = fDelegation;
|
||||
status_t result = OpenFile(state, mode, &data);
|
||||
if (result != B_OK) {
|
||||
delete state;
|
||||
@@ -125,8 +126,8 @@ Inode::Open(int mode, OpenFileCookie* cookie)
|
||||
fOpenState = state;
|
||||
cookie->fOpenState = state;
|
||||
locker.Unlock();
|
||||
|
||||
RevalidateFileCache();
|
||||
if (fDelegation == NULL)
|
||||
RevalidateFileCache();
|
||||
} else {
|
||||
fOpenState->AcquireReference();
|
||||
cookie->fOpenState = fOpenState;
|
||||
@@ -138,7 +139,7 @@ Inode::Open(int mode, OpenFileCookie* cookie)
|
||||
if (oldMode == O_RDONLY)
|
||||
RecallReadDelegation();
|
||||
|
||||
status_t result = OpenFile(fOpenState, O_RDWR, &data);
|
||||
status_t result = OpenFile(fOpenState, newMode, &data);
|
||||
if (result != B_OK) {
|
||||
locker.Lock();
|
||||
ReleaseOpenState();
|
||||
@@ -197,7 +198,7 @@ Inode::Close(OpenFileCookie* cookie)
|
||||
|
||||
int mode = cookie->fMode & O_RWMASK;
|
||||
if (mode == O_RDWR || mode == O_WRONLY)
|
||||
SyncAndCommit();
|
||||
SyncAndCommit(false, cookie);
|
||||
|
||||
MutexLocker _(fStateLock);
|
||||
ReleaseOpenState();
|
||||
@@ -323,6 +324,8 @@ Inode::ReadDirect(OpenStateCookie* cookie, off_t pos, void* buffer,
|
||||
|
||||
status_t result;
|
||||
OpenState* state = cookie != NULL ? cookie->fOpenState : fOpenState;
|
||||
ReadLocker delegationLocker(fDelegationLock);
|
||||
ASSERT(state->fDelegation == fDelegation);
|
||||
while (size < *_length && !*eof) {
|
||||
uint32 len = *_length - size;
|
||||
result = ReadFile(cookie, state, pos + size, &len,
|
||||
@@ -363,7 +366,7 @@ status_t
|
||||
Inode::WriteDirect(OpenStateCookie* cookie, off_t pos, const void* _buffer,
|
||||
size_t* _length)
|
||||
{
|
||||
ASSERT(cookie != NULL || fOpenState != NULL);
|
||||
ASSERT_WITH_DUMP(cookie != NULL || fOpenState != NULL, this);
|
||||
ASSERT(_buffer != NULL);
|
||||
ASSERT(_length != NULL);
|
||||
|
||||
@@ -385,6 +388,8 @@ Inode::WriteDirect(OpenStateCookie* cookie, off_t pos, const void* _buffer,
|
||||
fWriteDirty = true;
|
||||
}
|
||||
|
||||
ReadLocker delegationLocker(fDelegationLock);
|
||||
ASSERT(state->fDelegation == fDelegation);
|
||||
while (size < *_length) {
|
||||
uint32 len = *_length - size;
|
||||
status_t result = WriteFile(cookie, state, pos + size, &len,
|
||||
@@ -436,7 +441,8 @@ Inode::Write(OpenFileCookie* cookie, off_t pos, const void* _buffer,
|
||||
// We will let the server zero out the rest of the hole.
|
||||
fMaxFileSize = pos;
|
||||
size_t pageOffset = pos % B_PAGE_SIZE;
|
||||
file_cache_write(fFileCache, cookie, pos - pageOffset, NULL, &pageOffset);
|
||||
if (pageOffset != 0)
|
||||
file_cache_write(fFileCache, cookie, pos - pageOffset, NULL, &pageOffset);
|
||||
}
|
||||
fMaxFileSize = fileSize;
|
||||
fMetaCache.GrowFile(fMaxFileSize);
|
||||
@@ -444,7 +450,7 @@ Inode::Write(OpenFileCookie* cookie, off_t pos, const void* _buffer,
|
||||
|
||||
if ((cookie->fMode & O_NOCACHE) != 0) {
|
||||
WriteDirect(cookie, pos, _buffer, _length);
|
||||
Commit();
|
||||
Commit(cookie->fUid, cookie->fGid);
|
||||
}
|
||||
|
||||
return file_cache_write(fFileCache, cookie, pos, _buffer, _length);
|
||||
@@ -452,13 +458,13 @@ Inode::Write(OpenFileCookie* cookie, off_t pos, const void* _buffer,
|
||||
|
||||
|
||||
status_t
|
||||
Inode::Commit()
|
||||
Inode::Commit(uid_t uid, gid_t gid)
|
||||
{
|
||||
if (!fWriteDirty)
|
||||
return B_OK;
|
||||
|
||||
WriteLocker _(fWriteLock);
|
||||
status_t result = CommitWrites();
|
||||
status_t result = CommitWrites(fDelegation != NULL, uid, gid);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
fWriteDirty = false;
|
||||
|
||||
@@ -61,20 +61,38 @@ NFS4Inode::GetChangeInfo(uint64* change, bool attrDir)
|
||||
|
||||
|
||||
status_t
|
||||
NFS4Inode::CommitWrites()
|
||||
NFS4Inode::CommitWrites(bool unlockBetweenAttempts, uid_t uid, gid_t gid)
|
||||
{
|
||||
uint32 attempt = 0;
|
||||
uint32 retryLimit = 0;
|
||||
bool hard = true;
|
||||
if (fFileSystem != NULL) {
|
||||
retryLimit = fFileSystem->GetConfiguration().fRetryLimit;
|
||||
hard = fFileSystem->GetConfiguration().fHard;
|
||||
}
|
||||
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv, fFileSystem, geteuid(), getegid());
|
||||
Request request(serv, fFileSystem, uid, gid, unlockBetweenAttempts);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
req.Commit(0, 0);
|
||||
|
||||
status_t result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
if (result != B_OK) {
|
||||
if (unlockBetweenAttempts && (hard || attempt < retryLimit)) {
|
||||
// The server won't reply to a commit request while a delegation recall is in
|
||||
// progress. To avoid a deadlock, we respond to a failure to reply by releasing
|
||||
// fWriteLock, allowing the CallbackServer thread to proceed with CallbackRecall()
|
||||
// if it is blocked, and trying the commit request again.
|
||||
static_cast<Inode*>(this)->UnlockAndRelockWriteLock();
|
||||
++attempt;
|
||||
continue;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
@@ -318,22 +336,40 @@ NFS4Inode::GetStat(AttrValue** values, uint32* count, OpenAttrCookie* cookie)
|
||||
|
||||
|
||||
status_t
|
||||
NFS4Inode::WriteStat(OpenState* state, AttrValue* attrs, uint32 attrCount)
|
||||
NFS4Inode::WriteStat(OpenState* state, Delegation* delegation, AttrValue* attrs, uint32 attrCount)
|
||||
{
|
||||
ASSERT(attrs != NULL);
|
||||
|
||||
uint32 attempt = 0;
|
||||
Inode* inode = delegation != NULL ? delegation->GetInode() : NULL;
|
||||
|
||||
bool useDelegationStateID = true;
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv, fFileSystem, geteuid(), getegid());
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
uint32 stateID[3];
|
||||
memset(stateID, 0, sizeof(stateID));
|
||||
uint32 stateSeq = 0;
|
||||
|
||||
if (state != NULL) {
|
||||
if (state->fDelegation != NULL && useDelegationStateID) {
|
||||
delegation = state->fDelegation;
|
||||
delegation->GetStateIDandSeq(stateID, stateSeq);
|
||||
} else {
|
||||
memcpy(stateID, state->fStateID, sizeof(stateID));
|
||||
stateSeq = state->fStateSeq;
|
||||
}
|
||||
req.PutFH(state->fInfo.fHandle);
|
||||
req.SetAttr(state->fStateID, state->fStateSeq, attrs, attrCount);
|
||||
req.SetAttr(stateID, stateSeq, attrs, attrCount);
|
||||
} else {
|
||||
if (delegation != NULL && useDelegationStateID) {
|
||||
// We might have a delegation regardless of whether the file is open.
|
||||
delegation->GetStateIDandSeq(stateID, stateSeq);
|
||||
}
|
||||
req.PutFH(fInfo.fHandle);
|
||||
req.SetAttr(NULL, 0, attrs, attrCount);
|
||||
req.SetAttr(stateID, stateSeq, attrs, attrCount);
|
||||
}
|
||||
|
||||
status_t result = request.Send();
|
||||
@@ -342,8 +378,19 @@ NFS4Inode::WriteStat(OpenState* state, AttrValue* attrs, uint32 attrCount)
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (HandleErrors(attempt, reply.NFS4Error(), serv))
|
||||
if (HandleErrors(attempt, reply.NFS4Error(), serv, NULL, state)) {
|
||||
if (reply.NFS4Error() == NFS4ERR_DELAY && delegation != NULL) {
|
||||
delegation->GetInode()->UnlockAndRelockStateLocks();
|
||||
if (inode != NULL) {
|
||||
// The OpenState and Delegation pointers might be invalid now.
|
||||
state = inode->GetOpenState();
|
||||
delegation = inode->GetDelegation();
|
||||
}
|
||||
} else if (reply.NFS4Error() == NFS4ERR_OPENMODE && state->fDelegation != NULL) {
|
||||
useDelegationStateID = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
reply.PutFH();
|
||||
result = reply.SetAttr();
|
||||
@@ -726,13 +773,24 @@ NFS4Inode::ReadFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
||||
ASSERT(eof != NULL);
|
||||
|
||||
uint32 attempt = 0;
|
||||
bool useDelegationStateID = true;
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv, fFileSystem, geteuid(), getegid());
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
uint32 stateID[3];
|
||||
memset(stateID, 0, sizeof(stateID));
|
||||
uint32 stateSeq = 0;
|
||||
if (state->fDelegation != NULL && useDelegationStateID) {
|
||||
state->fDelegation->GetStateIDandSeq(stateID, stateSeq);
|
||||
} else {
|
||||
memcpy(stateID, state->fStateID, sizeof(stateID));
|
||||
stateSeq = state->fStateSeq;
|
||||
}
|
||||
|
||||
req.PutFH(state->fInfo.fHandle);
|
||||
req.Read(state->fStateID, state->fStateSeq, position, *length);
|
||||
req.Read(stateID, stateSeq, position, *length);
|
||||
|
||||
status_t result = request.Send(cookie);
|
||||
if (result != B_OK)
|
||||
@@ -740,8 +798,13 @@ NFS4Inode::ReadFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (HandleErrors(attempt, reply.NFS4Error(), serv, cookie, state))
|
||||
if (HandleErrors(attempt, reply.NFS4Error(), serv, cookie, state)) {
|
||||
if (reply.NFS4Error() == NFS4ERR_DELAY && state->fDelegation != NULL)
|
||||
state->fDelegation->GetInode()->UnlockAndRelockStateLocks();
|
||||
else if (reply.NFS4Error() == NFS4ERR_OPENMODE && state->fDelegation != NULL)
|
||||
useDelegationStateID = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
reply.PutFH();
|
||||
result = reply.Read(buffer, length, eof);
|
||||
@@ -762,14 +825,25 @@ NFS4Inode::WriteFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
||||
ASSERT(buffer != NULL);
|
||||
|
||||
uint32 attempt = 0;
|
||||
bool useDelegationStateID = true;
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv, fFileSystem, state->fUid, state->fGid);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
uint32 stateID[3];
|
||||
memset(stateID, 0, sizeof(stateID));
|
||||
uint32 stateSeq = 0;
|
||||
if (state->fDelegation != NULL && useDelegationStateID) {
|
||||
state->fDelegation->GetStateIDandSeq(stateID, stateSeq);
|
||||
} else {
|
||||
memcpy(stateID, state->fStateID, sizeof(stateID));
|
||||
stateSeq = state->fStateSeq;
|
||||
}
|
||||
|
||||
req.PutFH(state->fInfo.fHandle);
|
||||
|
||||
req.Write(state->fStateID, state->fStateSeq, buffer, position, *length,
|
||||
req.Write(stateID, stateSeq, buffer, position, *length,
|
||||
commit);
|
||||
|
||||
status_t result = request.Send(cookie);
|
||||
@@ -778,8 +852,13 @@ NFS4Inode::WriteFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (HandleErrors(attempt, reply.NFS4Error(), serv, cookie, state))
|
||||
if (HandleErrors(attempt, reply.NFS4Error(), serv, cookie, state)) {
|
||||
if (reply.NFS4Error() == NFS4ERR_DELAY && state->fDelegation != NULL)
|
||||
state->fDelegation->GetInode()->UnlockAndRelockStateLocks();
|
||||
else if (reply.NFS4Error() == NFS4ERR_OPENMODE && state->fDelegation != NULL)
|
||||
useDelegationStateID = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
protected:
|
||||
status_t Access(uint32* allowed);
|
||||
|
||||
status_t CommitWrites();
|
||||
status_t CommitWrites(bool unlockBetweenAttempts, uid_t uid, gid_t gid);
|
||||
|
||||
status_t LookUp(const char* name, uint64* change, uint64* fileID,
|
||||
FileHandle* handle, bool parent = false);
|
||||
@@ -43,8 +43,8 @@ protected:
|
||||
|
||||
status_t GetStat(AttrValue** values, uint32* count,
|
||||
OpenAttrCookie* attr = NULL);
|
||||
status_t WriteStat(OpenState* state, AttrValue* attrs,
|
||||
uint32 attrCount);
|
||||
status_t WriteStat(OpenState* state, Delegation* delegation,
|
||||
AttrValue* attrs, uint32 attrCount);
|
||||
|
||||
status_t CreateFile(const char* name, int mode, int perms,
|
||||
OpenState* state, ChangeInfo* changeInfo,
|
||||
|
||||
@@ -184,6 +184,12 @@ NFS4Object::HandleErrors(uint32& attempt, uint32 nfs4Error, RPC::Server* server,
|
||||
}
|
||||
return false;
|
||||
|
||||
// delegation might have just been recalled, or is about to be recalled
|
||||
case NFS4ERR_OPENMODE:
|
||||
if (state->fDelegation != NULL)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -348,6 +348,11 @@ NFS4Server::CallbackRecall(RequestInterpreter* request, ReplyBuilder* reply)
|
||||
DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
|
||||
args->fDelegation = delegation;
|
||||
args->fTruncate = truncate;
|
||||
|
||||
// If an IORequest job is needed, we should enqueue it before enqueueing
|
||||
// the DelegationRecall job.
|
||||
delegation->GetInode()->PrepareDelegationRecall(truncate);
|
||||
|
||||
gWorkQueue->EnqueueJob(DelegationRecall, args);
|
||||
|
||||
reply->Recall(B_OK);
|
||||
|
||||
@@ -48,6 +48,11 @@ Request::_SendUDP(Cookie* cookie)
|
||||
hard = fFileSystem->GetConfiguration().fHard;
|
||||
}
|
||||
|
||||
if (fSingleAttempt) {
|
||||
requestTimeout /= 10;
|
||||
retryLimit = 0;
|
||||
}
|
||||
|
||||
result = fServer->WaitCall(rpc, requestTimeout);
|
||||
if (result != B_OK) {
|
||||
int attempts = 1;
|
||||
@@ -107,6 +112,11 @@ Request::_SendTCP(Cookie* cookie)
|
||||
hard = fFileSystem->GetConfiguration().fHard;
|
||||
}
|
||||
|
||||
if (fSingleAttempt) {
|
||||
requestTimeout /= 10;
|
||||
retryLimit = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
result = fServer->SendCallAsync(fBuilder.Request(), &rpl, &rpc);
|
||||
if (result == B_NO_MEMORY)
|
||||
|
||||
@@ -21,7 +21,7 @@ class Request {
|
||||
public:
|
||||
inline Request(RPC::Server* server,
|
||||
FileSystem* fileSystem,
|
||||
uid_t uid, gid_t gid);
|
||||
uid_t uid, gid_t gid, bool singleAttempt = false);
|
||||
|
||||
inline RequestBuilder& Builder();
|
||||
inline ReplyInterpreter& Reply();
|
||||
@@ -38,15 +38,19 @@ private:
|
||||
|
||||
RequestBuilder fBuilder;
|
||||
ReplyInterpreter fReply;
|
||||
|
||||
bool fSingleAttempt;
|
||||
};
|
||||
|
||||
|
||||
inline
|
||||
Request::Request(RPC::Server* server, FileSystem* fileSystem, uid_t uid, gid_t gid)
|
||||
Request::Request(RPC::Server* server, FileSystem* fileSystem, uid_t uid, gid_t gid,
|
||||
bool singleAttempt)
|
||||
:
|
||||
fServer(server),
|
||||
fFileSystem(fileSystem),
|
||||
fBuilder(uid, gid)
|
||||
fBuilder(uid, gid),
|
||||
fSingleAttempt(singleAttempt)
|
||||
{
|
||||
ASSERT(server != NULL);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,24 @@ void
|
||||
WorkQueue::JobRecall(DelegationRecallArgs* args)
|
||||
{
|
||||
ASSERT(args != NULL);
|
||||
args->fDelegation->GetInode()->RecallDelegation(args->fTruncate);
|
||||
|
||||
Inode* inode = args->fDelegation->GetInode();
|
||||
if (inode->AIOIncomplete()) {
|
||||
// Re-queue and try again later.
|
||||
WorkQueueEntry* entry = new(std::nothrow) WorkQueueEntry;
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
entry->fType = DelegationRecall;
|
||||
entry->fArguments = args;
|
||||
|
||||
// The queue is already locked.
|
||||
fQueue.InsertAfter(fQueue.Tail(), entry);
|
||||
} else {
|
||||
args->fDelegation->GetInode()->RecallDelegationAsync(args->fTruncate);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ nfs4_remove_vnode(fs_volume* volume, fs_vnode* vnode, bool reenter)
|
||||
|
||||
// Unless the file was deleted by someone else, verify that all known names have been
|
||||
// unlinked.
|
||||
if (node->IsStale() == false) {
|
||||
if (node != NULL && node->IsStale() == false) {
|
||||
FileInfo fileInfo;
|
||||
ASSERT(fs->InoIdMap()->GetFileInfo(&fileInfo, vti->ID()) == B_ENTRY_NOT_FOUND);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user