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)
|
OpenStateCookie::OpenStateCookie(FileSystem* fileSystem)
|
||||||
:
|
:
|
||||||
Cookie(fileSystem)
|
Cookie(fileSystem),
|
||||||
|
fUid(geteuid()),
|
||||||
|
fGid(getegid())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ struct OpenStateCookie : public Cookie {
|
|||||||
OpenState* fOpenState;
|
OpenState* fOpenState;
|
||||||
uint32 fMode;
|
uint32 fMode;
|
||||||
|
|
||||||
|
uid_t fUid;
|
||||||
|
gid_t fGid;
|
||||||
|
|
||||||
OpenStateCookie(FileSystem* fileSystem);
|
OpenStateCookie(FileSystem* fileSystem);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,22 +20,55 @@ Delegation::Delegation(const OpenDelegationData& data, Inode* inode,
|
|||||||
fData(data),
|
fData(data),
|
||||||
fInode(inode),
|
fInode(inode),
|
||||||
fAttribute(attribute),
|
fAttribute(attribute),
|
||||||
|
fStateSeq(data.fStateSeq),
|
||||||
fUid(geteuid()),
|
fUid(geteuid()),
|
||||||
fGid(getegid())
|
fGid(getegid()),
|
||||||
|
fRecalled(false)
|
||||||
{
|
{
|
||||||
ASSERT(inode != NULL);
|
ASSERT(inode != NULL);
|
||||||
|
memcpy(fStateID, data.fStateID, sizeof(fStateID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
Delegation::GiveUp(bool truncate)
|
Delegation::GiveUp(bool truncate)
|
||||||
{
|
{
|
||||||
|
PrepareGiveUp(truncate);
|
||||||
|
|
||||||
|
return DoGiveUp(truncate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
Delegation::PrepareGiveUp(bool truncate)
|
||||||
|
{
|
||||||
|
status_t status = B_OK;
|
||||||
if (!fAttribute && !truncate)
|
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);
|
uint64 clientID, bool attr = false);
|
||||||
|
|
||||||
status_t GiveUp(bool truncate = 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 void SetData(const OpenDelegationData& data);
|
||||||
inline Inode* GetInode() const;
|
inline Inode* GetInode() const;
|
||||||
|
void GetStateIDandSeq(uint32* stateID, uint32& stateSeq) const;
|
||||||
inline OpenDelegation Type();
|
inline OpenDelegation Type();
|
||||||
|
inline void MarkRecalled();
|
||||||
|
inline bool RecallInitiated() const;
|
||||||
|
|
||||||
void Dump(void (*xprintf)(const char*, ...) = dprintf) const;
|
void Dump(void (*xprintf)(const char*, ...) = dprintf) const;
|
||||||
|
|
||||||
@@ -39,8 +44,11 @@ private:
|
|||||||
OpenDelegationData fData;
|
OpenDelegationData fData;
|
||||||
Inode* fInode;
|
Inode* fInode;
|
||||||
bool fAttribute;
|
bool fAttribute;
|
||||||
|
uint32 fStateID[3];
|
||||||
|
uint32 fStateSeq;
|
||||||
uid_t fUid;
|
uid_t fUid;
|
||||||
gid_t fGid;
|
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
|
#endif // DELEGATION_H
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Inode::Inode()
|
|||||||
fWriteDirty(false),
|
fWriteDirty(false),
|
||||||
fAIOWait(create_sem(1, NULL)),
|
fAIOWait(create_sem(1, NULL)),
|
||||||
fAIOCount(0),
|
fAIOCount(0),
|
||||||
|
fOpenStateReleasesPending(0),
|
||||||
fStale(false)
|
fStale(false)
|
||||||
{
|
{
|
||||||
rw_lock_init(&fDelegationLock, "nfs4 Inode::fDelegationLock");
|
rw_lock_init(&fDelegationLock, "nfs4 Inode::fDelegationLock");
|
||||||
@@ -704,12 +705,15 @@ Inode::WriteStat(const struct stat* st, uint32 mask, OpenAttrCookie* cookie)
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReadLocker delegationLocker(fDelegationLock);
|
||||||
if (cookie == NULL) {
|
if (cookie == NULL) {
|
||||||
MutexLocker stateLocker(fStateLock);
|
MutexLocker stateLocker(fStateLock);
|
||||||
ASSERT(fOpenState != NULL || !(mask & B_STAT_SIZE));
|
ASSERT(fOpenState != NULL || !(mask & B_STAT_SIZE));
|
||||||
result = NFS4Inode::WriteStat(fOpenState, attr, i);
|
result = NFS4Inode::WriteStat(fOpenState, fDelegation, attr, i);
|
||||||
} else
|
} else {
|
||||||
result = NFS4Inode::WriteStat(cookie->fOpenState, attr, i);
|
ASSERT(cookie->fOpenState->fDelegation == fDelegation);
|
||||||
|
result = NFS4Inode::WriteStat(cookie->fOpenState, NULL, attr, i);
|
||||||
|
}
|
||||||
|
|
||||||
fMetaCache.InvalidateStat();
|
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
|
void
|
||||||
Inode::RecallReadDelegation()
|
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
|
void
|
||||||
Inode::ReleaseOpenState()
|
Inode::ReleaseOpenState()
|
||||||
{
|
{
|
||||||
ASSERT(fOpenState != NULL);
|
ASSERT(fOpenState != NULL);
|
||||||
|
|
||||||
if (fOpenState->ReleaseReference() == 1) {
|
// If IO is pending, wait until it is finished to release the (possibly last) reference.
|
||||||
ASSERT(fAIOCount == 0);
|
// It would be simpler to call WaitAIOComplete here, but that won't work if this
|
||||||
fOpenState = NULL;
|
// 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
|
status_t
|
||||||
Inode::SyncAndCommit(bool force)
|
Inode::SyncAndCommit(bool force, OpenStateCookie* cookie)
|
||||||
{
|
{
|
||||||
if (!force && fDelegation != NULL)
|
Sync(force, true);
|
||||||
return B_OK;
|
|
||||||
|
|
||||||
file_cache_sync(fFileCache);
|
// The server is liable to deny a commit request that does not come from a user who
|
||||||
WaitAIOComplete();
|
// opened the file.
|
||||||
return Commit();
|
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
|
void
|
||||||
Inode::EndAIOOp()
|
Inode::EndAIOOp()
|
||||||
{
|
{
|
||||||
MutexLocker _(fAIOLock);
|
MutexLocker AIOLocker(fAIOLock);
|
||||||
ASSERT(fAIOCount > 0);
|
ASSERT(fAIOCount > 0);
|
||||||
fAIOCount--;
|
fAIOCount--;
|
||||||
if (fAIOCount == 0)
|
if (fAIOCount == 0)
|
||||||
release_sem(fAIOWait);
|
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 OpenState* GetOpenState();
|
||||||
|
|
||||||
|
inline Delegation* GetDelegation() const;
|
||||||
void SetDelegation(Delegation* delegation);
|
void SetDelegation(Delegation* delegation);
|
||||||
void RecallDelegation(bool truncate = false);
|
void RecallDelegation(bool truncate = false);
|
||||||
|
void PrepareDelegationRecall(bool truncate = false);
|
||||||
|
void RecallDelegationAsync(bool truncate = false);
|
||||||
void RecallReadDelegation();
|
void RecallReadDelegation();
|
||||||
|
void UnlockAndRelockStateLocks();
|
||||||
|
void UnlockAndRelockWriteLock();
|
||||||
|
|
||||||
status_t LookUp(const char* name, ino_t* id);
|
status_t LookUp(const char* name, ino_t* id);
|
||||||
|
|
||||||
status_t Access(int mode);
|
status_t Access(int mode);
|
||||||
|
|
||||||
status_t Commit();
|
status_t Sync(bool force = false, bool wait = true);
|
||||||
status_t SyncAndCommit(bool force = false);
|
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,
|
status_t CreateObject(const char* name, const char* path,
|
||||||
int mode, FileType type, ino_t* id);
|
int mode, FileType type, ino_t* id);
|
||||||
@@ -117,6 +123,7 @@ public:
|
|||||||
|
|
||||||
void BeginAIOOp();
|
void BeginAIOOp();
|
||||||
void EndAIOOp();
|
void EndAIOOp();
|
||||||
|
bool AIOIncomplete();
|
||||||
inline void WaitAIOComplete();
|
inline void WaitAIOComplete();
|
||||||
|
|
||||||
inline void SetStale(bool stale = true);
|
inline void SetStale(bool stale = true);
|
||||||
@@ -177,6 +184,7 @@ private:
|
|||||||
sem_id fAIOWait;
|
sem_id fAIOWait;
|
||||||
uint32 fAIOCount;
|
uint32 fAIOCount;
|
||||||
mutex fAIOLock;
|
mutex fAIOLock;
|
||||||
|
uint32 fOpenStateReleasesPending;
|
||||||
|
|
||||||
bool fStale;
|
bool fStale;
|
||||||
};
|
};
|
||||||
@@ -266,6 +274,13 @@ Inode::GetOpenState()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline Delegation*
|
||||||
|
Inode::GetDelegation() const
|
||||||
|
{
|
||||||
|
return fDelegation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
Inode::SetStale(bool stale)
|
Inode::SetStale(bool stale)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ Inode::Open(int mode, OpenFileCookie* cookie)
|
|||||||
state->fInfo = fInfo;
|
state->fInfo = fInfo;
|
||||||
state->fFileSystem = fFileSystem;
|
state->fFileSystem = fFileSystem;
|
||||||
state->fMode = mode & O_RWMASK;
|
state->fMode = mode & O_RWMASK;
|
||||||
|
state->fDelegation = fDelegation;
|
||||||
status_t result = OpenFile(state, mode, &data);
|
status_t result = OpenFile(state, mode, &data);
|
||||||
if (result != B_OK) {
|
if (result != B_OK) {
|
||||||
delete state;
|
delete state;
|
||||||
@@ -125,8 +126,8 @@ Inode::Open(int mode, OpenFileCookie* cookie)
|
|||||||
fOpenState = state;
|
fOpenState = state;
|
||||||
cookie->fOpenState = state;
|
cookie->fOpenState = state;
|
||||||
locker.Unlock();
|
locker.Unlock();
|
||||||
|
if (fDelegation == NULL)
|
||||||
RevalidateFileCache();
|
RevalidateFileCache();
|
||||||
} else {
|
} else {
|
||||||
fOpenState->AcquireReference();
|
fOpenState->AcquireReference();
|
||||||
cookie->fOpenState = fOpenState;
|
cookie->fOpenState = fOpenState;
|
||||||
@@ -138,7 +139,7 @@ Inode::Open(int mode, OpenFileCookie* cookie)
|
|||||||
if (oldMode == O_RDONLY)
|
if (oldMode == O_RDONLY)
|
||||||
RecallReadDelegation();
|
RecallReadDelegation();
|
||||||
|
|
||||||
status_t result = OpenFile(fOpenState, O_RDWR, &data);
|
status_t result = OpenFile(fOpenState, newMode, &data);
|
||||||
if (result != B_OK) {
|
if (result != B_OK) {
|
||||||
locker.Lock();
|
locker.Lock();
|
||||||
ReleaseOpenState();
|
ReleaseOpenState();
|
||||||
@@ -197,7 +198,7 @@ Inode::Close(OpenFileCookie* cookie)
|
|||||||
|
|
||||||
int mode = cookie->fMode & O_RWMASK;
|
int mode = cookie->fMode & O_RWMASK;
|
||||||
if (mode == O_RDWR || mode == O_WRONLY)
|
if (mode == O_RDWR || mode == O_WRONLY)
|
||||||
SyncAndCommit();
|
SyncAndCommit(false, cookie);
|
||||||
|
|
||||||
MutexLocker _(fStateLock);
|
MutexLocker _(fStateLock);
|
||||||
ReleaseOpenState();
|
ReleaseOpenState();
|
||||||
@@ -323,6 +324,8 @@ Inode::ReadDirect(OpenStateCookie* cookie, off_t pos, void* buffer,
|
|||||||
|
|
||||||
status_t result;
|
status_t result;
|
||||||
OpenState* state = cookie != NULL ? cookie->fOpenState : fOpenState;
|
OpenState* state = cookie != NULL ? cookie->fOpenState : fOpenState;
|
||||||
|
ReadLocker delegationLocker(fDelegationLock);
|
||||||
|
ASSERT(state->fDelegation == fDelegation);
|
||||||
while (size < *_length && !*eof) {
|
while (size < *_length && !*eof) {
|
||||||
uint32 len = *_length - size;
|
uint32 len = *_length - size;
|
||||||
result = ReadFile(cookie, state, pos + size, &len,
|
result = ReadFile(cookie, state, pos + size, &len,
|
||||||
@@ -363,7 +366,7 @@ status_t
|
|||||||
Inode::WriteDirect(OpenStateCookie* cookie, off_t pos, const void* _buffer,
|
Inode::WriteDirect(OpenStateCookie* cookie, off_t pos, const void* _buffer,
|
||||||
size_t* _length)
|
size_t* _length)
|
||||||
{
|
{
|
||||||
ASSERT(cookie != NULL || fOpenState != NULL);
|
ASSERT_WITH_DUMP(cookie != NULL || fOpenState != NULL, this);
|
||||||
ASSERT(_buffer != NULL);
|
ASSERT(_buffer != NULL);
|
||||||
ASSERT(_length != NULL);
|
ASSERT(_length != NULL);
|
||||||
|
|
||||||
@@ -385,6 +388,8 @@ Inode::WriteDirect(OpenStateCookie* cookie, off_t pos, const void* _buffer,
|
|||||||
fWriteDirty = true;
|
fWriteDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReadLocker delegationLocker(fDelegationLock);
|
||||||
|
ASSERT(state->fDelegation == fDelegation);
|
||||||
while (size < *_length) {
|
while (size < *_length) {
|
||||||
uint32 len = *_length - size;
|
uint32 len = *_length - size;
|
||||||
status_t result = WriteFile(cookie, state, pos + size, &len,
|
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.
|
// We will let the server zero out the rest of the hole.
|
||||||
fMaxFileSize = pos;
|
fMaxFileSize = pos;
|
||||||
size_t pageOffset = pos % B_PAGE_SIZE;
|
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;
|
fMaxFileSize = fileSize;
|
||||||
fMetaCache.GrowFile(fMaxFileSize);
|
fMetaCache.GrowFile(fMaxFileSize);
|
||||||
@@ -444,7 +450,7 @@ Inode::Write(OpenFileCookie* cookie, off_t pos, const void* _buffer,
|
|||||||
|
|
||||||
if ((cookie->fMode & O_NOCACHE) != 0) {
|
if ((cookie->fMode & O_NOCACHE) != 0) {
|
||||||
WriteDirect(cookie, pos, _buffer, _length);
|
WriteDirect(cookie, pos, _buffer, _length);
|
||||||
Commit();
|
Commit(cookie->fUid, cookie->fGid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return file_cache_write(fFileCache, cookie, pos, _buffer, _length);
|
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
|
status_t
|
||||||
Inode::Commit()
|
Inode::Commit(uid_t uid, gid_t gid)
|
||||||
{
|
{
|
||||||
if (!fWriteDirty)
|
if (!fWriteDirty)
|
||||||
return B_OK;
|
return B_OK;
|
||||||
|
|
||||||
WriteLocker _(fWriteLock);
|
WriteLocker _(fWriteLock);
|
||||||
status_t result = CommitWrites();
|
status_t result = CommitWrites(fDelegation != NULL, uid, gid);
|
||||||
if (result != B_OK)
|
if (result != B_OK)
|
||||||
return result;
|
return result;
|
||||||
fWriteDirty = false;
|
fWriteDirty = false;
|
||||||
|
|||||||
@@ -61,20 +61,38 @@ NFS4Inode::GetChangeInfo(uint64* change, bool attrDir)
|
|||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
NFS4Inode::CommitWrites()
|
NFS4Inode::CommitWrites(bool unlockBetweenAttempts, uid_t uid, gid_t gid)
|
||||||
{
|
{
|
||||||
uint32 attempt = 0;
|
uint32 attempt = 0;
|
||||||
|
uint32 retryLimit = 0;
|
||||||
|
bool hard = true;
|
||||||
|
if (fFileSystem != NULL) {
|
||||||
|
retryLimit = fFileSystem->GetConfiguration().fRetryLimit;
|
||||||
|
hard = fFileSystem->GetConfiguration().fHard;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
RPC::Server* serv = fFileSystem->Server();
|
RPC::Server* serv = fFileSystem->Server();
|
||||||
Request request(serv, fFileSystem, geteuid(), getegid());
|
Request request(serv, fFileSystem, uid, gid, unlockBetweenAttempts);
|
||||||
RequestBuilder& req = request.Builder();
|
RequestBuilder& req = request.Builder();
|
||||||
|
|
||||||
req.PutFH(fInfo.fHandle);
|
req.PutFH(fInfo.fHandle);
|
||||||
req.Commit(0, 0);
|
req.Commit(0, 0);
|
||||||
|
|
||||||
status_t result = request.Send();
|
status_t result = request.Send();
|
||||||
if (result != B_OK)
|
if (result != B_OK) {
|
||||||
return result;
|
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();
|
ReplyInterpreter& reply = request.Reply();
|
||||||
|
|
||||||
@@ -318,22 +336,40 @@ NFS4Inode::GetStat(AttrValue** values, uint32* count, OpenAttrCookie* cookie)
|
|||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
NFS4Inode::WriteStat(OpenState* state, AttrValue* attrs, uint32 attrCount)
|
NFS4Inode::WriteStat(OpenState* state, Delegation* delegation, AttrValue* attrs, uint32 attrCount)
|
||||||
{
|
{
|
||||||
ASSERT(attrs != NULL);
|
ASSERT(attrs != NULL);
|
||||||
|
|
||||||
uint32 attempt = 0;
|
uint32 attempt = 0;
|
||||||
|
Inode* inode = delegation != NULL ? delegation->GetInode() : NULL;
|
||||||
|
|
||||||
|
bool useDelegationStateID = true;
|
||||||
do {
|
do {
|
||||||
RPC::Server* serv = fFileSystem->Server();
|
RPC::Server* serv = fFileSystem->Server();
|
||||||
Request request(serv, fFileSystem, geteuid(), getegid());
|
Request request(serv, fFileSystem, geteuid(), getegid());
|
||||||
RequestBuilder& req = request.Builder();
|
RequestBuilder& req = request.Builder();
|
||||||
|
|
||||||
|
uint32 stateID[3];
|
||||||
|
memset(stateID, 0, sizeof(stateID));
|
||||||
|
uint32 stateSeq = 0;
|
||||||
|
|
||||||
if (state != NULL) {
|
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.PutFH(state->fInfo.fHandle);
|
||||||
req.SetAttr(state->fStateID, state->fStateSeq, attrs, attrCount);
|
req.SetAttr(stateID, stateSeq, attrs, attrCount);
|
||||||
} else {
|
} 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.PutFH(fInfo.fHandle);
|
||||||
req.SetAttr(NULL, 0, attrs, attrCount);
|
req.SetAttr(stateID, stateSeq, attrs, attrCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t result = request.Send();
|
status_t result = request.Send();
|
||||||
@@ -342,8 +378,19 @@ NFS4Inode::WriteStat(OpenState* state, AttrValue* attrs, uint32 attrCount)
|
|||||||
|
|
||||||
ReplyInterpreter& reply = request.Reply();
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
reply.PutFH();
|
reply.PutFH();
|
||||||
result = reply.SetAttr();
|
result = reply.SetAttr();
|
||||||
@@ -726,13 +773,24 @@ NFS4Inode::ReadFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
|||||||
ASSERT(eof != NULL);
|
ASSERT(eof != NULL);
|
||||||
|
|
||||||
uint32 attempt = 0;
|
uint32 attempt = 0;
|
||||||
|
bool useDelegationStateID = true;
|
||||||
do {
|
do {
|
||||||
RPC::Server* serv = fFileSystem->Server();
|
RPC::Server* serv = fFileSystem->Server();
|
||||||
Request request(serv, fFileSystem, geteuid(), getegid());
|
Request request(serv, fFileSystem, geteuid(), getegid());
|
||||||
RequestBuilder& req = request.Builder();
|
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.PutFH(state->fInfo.fHandle);
|
||||||
req.Read(state->fStateID, state->fStateSeq, position, *length);
|
req.Read(stateID, stateSeq, position, *length);
|
||||||
|
|
||||||
status_t result = request.Send(cookie);
|
status_t result = request.Send(cookie);
|
||||||
if (result != B_OK)
|
if (result != B_OK)
|
||||||
@@ -740,8 +798,13 @@ NFS4Inode::ReadFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
|||||||
|
|
||||||
ReplyInterpreter& reply = request.Reply();
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
reply.PutFH();
|
reply.PutFH();
|
||||||
result = reply.Read(buffer, length, eof);
|
result = reply.Read(buffer, length, eof);
|
||||||
@@ -762,14 +825,25 @@ NFS4Inode::WriteFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
|||||||
ASSERT(buffer != NULL);
|
ASSERT(buffer != NULL);
|
||||||
|
|
||||||
uint32 attempt = 0;
|
uint32 attempt = 0;
|
||||||
|
bool useDelegationStateID = true;
|
||||||
do {
|
do {
|
||||||
RPC::Server* serv = fFileSystem->Server();
|
RPC::Server* serv = fFileSystem->Server();
|
||||||
Request request(serv, fFileSystem, state->fUid, state->fGid);
|
Request request(serv, fFileSystem, state->fUid, state->fGid);
|
||||||
RequestBuilder& req = request.Builder();
|
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.PutFH(state->fInfo.fHandle);
|
||||||
|
|
||||||
req.Write(state->fStateID, state->fStateSeq, buffer, position, *length,
|
req.Write(stateID, stateSeq, buffer, position, *length,
|
||||||
commit);
|
commit);
|
||||||
|
|
||||||
status_t result = request.Send(cookie);
|
status_t result = request.Send(cookie);
|
||||||
@@ -778,8 +852,13 @@ NFS4Inode::WriteFile(OpenStateCookie* cookie, OpenState* state, uint64 position,
|
|||||||
|
|
||||||
ReplyInterpreter& reply = request.Reply();
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
reply.PutFH();
|
reply.PutFH();
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
status_t Access(uint32* allowed);
|
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,
|
status_t LookUp(const char* name, uint64* change, uint64* fileID,
|
||||||
FileHandle* handle, bool parent = false);
|
FileHandle* handle, bool parent = false);
|
||||||
@@ -43,8 +43,8 @@ protected:
|
|||||||
|
|
||||||
status_t GetStat(AttrValue** values, uint32* count,
|
status_t GetStat(AttrValue** values, uint32* count,
|
||||||
OpenAttrCookie* attr = NULL);
|
OpenAttrCookie* attr = NULL);
|
||||||
status_t WriteStat(OpenState* state, AttrValue* attrs,
|
status_t WriteStat(OpenState* state, Delegation* delegation,
|
||||||
uint32 attrCount);
|
AttrValue* attrs, uint32 attrCount);
|
||||||
|
|
||||||
status_t CreateFile(const char* name, int mode, int perms,
|
status_t CreateFile(const char* name, int mode, int perms,
|
||||||
OpenState* state, ChangeInfo* changeInfo,
|
OpenState* state, ChangeInfo* changeInfo,
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ NFS4Object::HandleErrors(uint32& attempt, uint32 nfs4Error, RPC::Server* server,
|
|||||||
}
|
}
|
||||||
return false;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,6 +348,11 @@ NFS4Server::CallbackRecall(RequestInterpreter* request, ReplyBuilder* reply)
|
|||||||
DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
|
DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
|
||||||
args->fDelegation = delegation;
|
args->fDelegation = delegation;
|
||||||
args->fTruncate = truncate;
|
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);
|
gWorkQueue->EnqueueJob(DelegationRecall, args);
|
||||||
|
|
||||||
reply->Recall(B_OK);
|
reply->Recall(B_OK);
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ Request::_SendUDP(Cookie* cookie)
|
|||||||
hard = fFileSystem->GetConfiguration().fHard;
|
hard = fFileSystem->GetConfiguration().fHard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fSingleAttempt) {
|
||||||
|
requestTimeout /= 10;
|
||||||
|
retryLimit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
result = fServer->WaitCall(rpc, requestTimeout);
|
result = fServer->WaitCall(rpc, requestTimeout);
|
||||||
if (result != B_OK) {
|
if (result != B_OK) {
|
||||||
int attempts = 1;
|
int attempts = 1;
|
||||||
@@ -107,6 +112,11 @@ Request::_SendTCP(Cookie* cookie)
|
|||||||
hard = fFileSystem->GetConfiguration().fHard;
|
hard = fFileSystem->GetConfiguration().fHard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fSingleAttempt) {
|
||||||
|
requestTimeout /= 10;
|
||||||
|
retryLimit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
result = fServer->SendCallAsync(fBuilder.Request(), &rpl, &rpc);
|
result = fServer->SendCallAsync(fBuilder.Request(), &rpl, &rpc);
|
||||||
if (result == B_NO_MEMORY)
|
if (result == B_NO_MEMORY)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Request {
|
|||||||
public:
|
public:
|
||||||
inline Request(RPC::Server* server,
|
inline Request(RPC::Server* server,
|
||||||
FileSystem* fileSystem,
|
FileSystem* fileSystem,
|
||||||
uid_t uid, gid_t gid);
|
uid_t uid, gid_t gid, bool singleAttempt = false);
|
||||||
|
|
||||||
inline RequestBuilder& Builder();
|
inline RequestBuilder& Builder();
|
||||||
inline ReplyInterpreter& Reply();
|
inline ReplyInterpreter& Reply();
|
||||||
@@ -38,15 +38,19 @@ private:
|
|||||||
|
|
||||||
RequestBuilder fBuilder;
|
RequestBuilder fBuilder;
|
||||||
ReplyInterpreter fReply;
|
ReplyInterpreter fReply;
|
||||||
|
|
||||||
|
bool fSingleAttempt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
inline
|
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),
|
fServer(server),
|
||||||
fFileSystem(fileSystem),
|
fFileSystem(fileSystem),
|
||||||
fBuilder(uid, gid)
|
fBuilder(uid, gid),
|
||||||
|
fSingleAttempt(singleAttempt)
|
||||||
{
|
{
|
||||||
ASSERT(server != NULL);
|
ASSERT(server != NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,24 @@ void
|
|||||||
WorkQueue::JobRecall(DelegationRecallArgs* args)
|
WorkQueue::JobRecall(DelegationRecallArgs* args)
|
||||||
{
|
{
|
||||||
ASSERT(args != NULL);
|
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
|
// Unless the file was deleted by someone else, verify that all known names have been
|
||||||
// unlinked.
|
// unlinked.
|
||||||
if (node->IsStale() == false) {
|
if (node != NULL && node->IsStale() == false) {
|
||||||
FileInfo fileInfo;
|
FileInfo fileInfo;
|
||||||
ASSERT(fs->InoIdMap()->GetFileInfo(&fileInfo, vti->ID()) == B_ENTRY_NOT_FOUND);
|
ASSERT(fs->InoIdMap()->GetFileInfo(&fileInfo, vti->ID()) == B_ENTRY_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user