nfs4: Fix race condition between inode creation and removal

If the underlying file system reuses inode numbers it is possible that
an entry of inode with reused id in InodeIdMap will be removed.
This patch should also improve behavior when one of many hard links
is removed.
This commit is contained in:
Pawel Dziepak
2012-11-01 18:34:26 +01:00
parent b70890b138
commit 81f2b97177
5 changed files with 80 additions and 10 deletions
@@ -41,6 +41,7 @@ FileSystem::FileSystem(const MountConfiguration& configuration)
mutex_init(&fOpenOwnerLock, NULL); mutex_init(&fOpenOwnerLock, NULL);
mutex_init(&fOpenLock, NULL); mutex_init(&fOpenLock, NULL);
mutex_init(&fDelegationLock, NULL); mutex_init(&fDelegationLock, NULL);
rw_lock_init(&fRemoveNodeLock, NULL);
} }
@@ -48,6 +49,7 @@ FileSystem::~FileSystem()
{ {
NFSServer()->RemoveFileSystem(this); NFSServer()->RemoveFileSystem(this);
rw_lock_destroy(&fRemoveNodeLock);
mutex_destroy(&fDelegationLock); mutex_destroy(&fDelegationLock);
mutex_destroy(&fOpenLock); mutex_destroy(&fOpenLock);
mutex_destroy(&fOpenOwnerLock); mutex_destroy(&fOpenOwnerLock);
@@ -78,6 +78,8 @@ public:
FileSystem* fNext; FileSystem* fNext;
FileSystem* fPrev; FileSystem* fPrev;
rw_lock fRemoveNodeLock;
private: private:
FileSystem(const MountConfiguration& config); FileSystem(const MountConfiguration& config);
@@ -281,6 +281,8 @@ Inode::Remove(const char* name, FileType type, ino_t* id)
status_t result = NFS4Inode::RemoveObject(name, type, &changeInfo, &fileID); status_t result = NFS4Inode::RemoveObject(name, type, &changeInfo, &fileID);
if (result != B_OK) if (result != B_OK)
return result; return result;
if (type != NF4NAMEDATTR)
fFileSystem->InoIdMap()->MarkRemoved(fileID);
DirectoryCache* cache = type != NF4NAMEDATTR ? fCache : fAttrCache; DirectoryCache* cache = type != NF4NAMEDATTR ? fCache : fAttrCache;
cache->Lock(); cache->Lock();
@@ -16,6 +16,10 @@
#include "FileInfo.h" #include "FileInfo.h"
struct InodeIdMapEntry {
FileInfo fFileInfo;
bool fRemoved;
};
class InodeIdMap { class InodeIdMap {
public: public:
@@ -24,11 +28,15 @@ public:
inline status_t AddEntry(const FileInfo& fi, inline status_t AddEntry(const FileInfo& fi,
ino_t id, bool weak = false); ino_t id, bool weak = false);
inline status_t MarkRemoved(ino_t id);
inline status_t RemoveEntry(ino_t id); inline status_t RemoveEntry(ino_t id);
inline status_t GetFileInfo(FileInfo* fi, ino_t id); inline status_t GetFileInfo(FileInfo* fi, ino_t id);
protected:
inline bool _IsEntryRemoved(ino_t id);
private: private:
AVLTreeMap<ino_t, FileInfo> fMap; AVLTreeMap<ino_t, InodeIdMapEntry> fMap;
mutex fLock; mutex fLock;
}; };
@@ -51,10 +59,29 @@ InodeIdMap::~InodeIdMap()
inline status_t inline status_t
InodeIdMap::AddEntry(const FileInfo& fi, ino_t id, bool weak) InodeIdMap::AddEntry(const FileInfo& fi, ino_t id, bool weak)
{ {
InodeIdMapEntry entry;
MutexLocker _(fLock); MutexLocker _(fLock);
//if (weak) if (weak || _IsEntryRemoved(id))
fMap.Remove(id); fMap.Remove(id);
return fMap.Insert(id, fi);
entry.fFileInfo = fi;
entry.fRemoved = false;
return fMap.Insert(id, entry);
}
inline status_t
InodeIdMap::MarkRemoved(ino_t id)
{
MutexLocker _(fLock);
AVLTreeMap<ino_t, InodeIdMapEntry>::Iterator it = fMap.Find(id);
if (!it.HasCurrent())
return B_ENTRY_NOT_FOUND;
it.CurrentValuePointer()->fRemoved = true;
return B_OK;
} }
@@ -62,7 +89,9 @@ inline status_t
InodeIdMap::RemoveEntry(ino_t id) InodeIdMap::RemoveEntry(ino_t id)
{ {
MutexLocker _(fLock); MutexLocker _(fLock);
return fMap.Remove(id); if (_IsEntryRemoved(id))
return fMap.Remove(id);
return B_OK;
} }
@@ -72,14 +101,26 @@ InodeIdMap::GetFileInfo(FileInfo* fi, ino_t id)
ASSERT(fi != NULL); ASSERT(fi != NULL);
MutexLocker _(fLock); MutexLocker _(fLock);
AVLTreeMap<ino_t, FileInfo>::Iterator it = fMap.Find(id); AVLTreeMap<ino_t, InodeIdMapEntry>::Iterator it = fMap.Find(id);
if (!it.HasCurrent()) if (!it.HasCurrent())
return B_ENTRY_NOT_FOUND; return B_ENTRY_NOT_FOUND;
*fi = it.Current(); *fi = it.Current().fFileInfo;
return B_OK; return B_OK;
} }
// Caller must hold fLock
inline bool
InodeIdMap::_IsEntryRemoved(ino_t id)
{
AVLTreeMap<ino_t, InodeIdMapEntry>::Iterator it = fMap.Find(id);
if (!it.HasCurrent())
return true;
return it.Current().fRemoved;
}
#endif // INODEIDMAP_H #endif // INODEIDMAP_H
@@ -25,7 +25,9 @@
#include "RPCServer.h" #include "RPCServer.h"
#include "WorkQueue.h" #include "WorkQueue.h"
#ifdef DEBUG
#define TRACE_NFS4 #define TRACE_NFS4
#endif
#ifdef TRACE_NFS4 #ifdef TRACE_NFS4
static mutex gTraceLock = MUTEX_INITIALIZER(NULL); static mutex gTraceLock = MUTEX_INITIALIZER(NULL);
@@ -33,7 +35,7 @@ static mutex gTraceLock = MUTEX_INITIALIZER(NULL);
#define TRACE(x...) \ #define TRACE(x...) \
{ \ { \
mutex_lock(&gTraceLock); \ mutex_lock(&gTraceLock); \
dprintf("%s(): ", __FUNCTION__); \ dprintf("nfs4: %s(): ", __FUNCTION__); \
dprintf(x); \ dprintf(x); \
dprintf("\n"); \ dprintf("\n"); \
mutex_unlock(&gTraceLock); \ mutex_unlock(&gTraceLock); \
@@ -251,10 +253,13 @@ nfs4_read_fs_info(fs_volume* volume, struct fs_info* info)
static status_t static status_t
nfs4_lookup(fs_volume* volume, fs_vnode* dir, const char* name, ino_t* _id) nfs4_lookup(fs_volume* volume, fs_vnode* dir, const char* name, ino_t* _id)
{ {
FileSystem* fs = reinterpret_cast<FileSystem*>(volume->private_volume);
Inode* inode = reinterpret_cast<Inode*>(dir->private_node); Inode* inode = reinterpret_cast<Inode*>(dir->private_node);
TRACE("volume = %p, dir = %llu, name = %s", volume, inode->ID(), name); TRACE("volume = %p, dir = %llu, name = %s", volume, inode->ID(), name);
ReadLocker(fs->fRemoveNodeLock);
status_t result = inode->LookUp(name, _id); status_t result = inode->LookUp(name, _id);
if (result != B_OK) if (result != B_OK)
return result; return result;
@@ -262,7 +267,9 @@ nfs4_lookup(fs_volume* volume, fs_vnode* dir, const char* name, ino_t* _id)
void* ptr; void* ptr;
TRACE("*_id = %llu", *_id); TRACE("*_id = %llu", *_id);
return get_vnode(volume, *_id, &ptr); result = get_vnode(volume, *_id, &ptr);
unremove_vnode(volume, *_id);
return result;
} }
@@ -485,9 +492,12 @@ static status_t
nfs4_unlink(fs_volume* volume, fs_vnode* dir, const char* name) nfs4_unlink(fs_volume* volume, fs_vnode* dir, const char* name)
{ {
Inode* inode = reinterpret_cast<Inode*>(dir->private_node); Inode* inode = reinterpret_cast<Inode*>(dir->private_node);
FileSystem* fs = reinterpret_cast<FileSystem*>(volume->private_volume);
TRACE("volume = %p, dir = %llu, name = %s", volume, inode->ID(), name); TRACE("volume = %p, dir = %llu, name = %s", volume, inode->ID(), name);
WriteLocker(fs->fRemoveNodeLock);
ino_t id; ino_t id;
status_t result = inode->Remove(name, NF4REG, &id); status_t result = inode->Remove(name, NF4REG, &id);
if (result != B_OK) if (result != B_OK)
@@ -503,11 +513,14 @@ nfs4_rename(fs_volume* volume, fs_vnode* fromDir, const char* fromName,
{ {
Inode* fromInode = reinterpret_cast<Inode*>(fromDir->private_node); Inode* fromInode = reinterpret_cast<Inode*>(fromDir->private_node);
Inode* toInode = reinterpret_cast<Inode*>(toDir->private_node); Inode* toInode = reinterpret_cast<Inode*>(toDir->private_node);
FileSystem* fs = reinterpret_cast<FileSystem*>(volume->private_volume);
TRACE("volume = %p, fromDir = %llu, toDir = %llu, fromName = %s, " \ TRACE("volume = %p, fromDir = %llu, toDir = %llu, fromName = %s, " \
"toName = %s", volume, fromInode->ID(), toInode->ID(), fromName, "toName = %s", volume, fromInode->ID(), toInode->ID(), fromName,
toName); toName);
ReadLocker(fs->fRemoveNodeLock);
ino_t id; ino_t id;
status_t result = Inode::Rename(fromInode, toInode, fromName, toName, false, status_t result = Inode::Rename(fromInode, toInode, fromName, toName, false,
&id); &id);
@@ -517,6 +530,8 @@ nfs4_rename(fs_volume* volume, fs_vnode* fromDir, const char* fromName,
Inode* child; Inode* child;
result = get_vnode(volume, id, reinterpret_cast<void**>(&child)); result = get_vnode(volume, id, reinterpret_cast<void**>(&child));
if (result == B_OK) { if (result == B_OK) {
unremove_vnode(volume, id);
child->fInfo.fParent = toInode->fInfo.fHandle; child->fInfo.fParent = toInode->fInfo.fHandle;
child->fInfo.CreateName(toInode->fInfo.fPath, toName); child->fInfo.CreateName(toInode->fInfo.fPath, toName);
} }
@@ -564,10 +579,13 @@ nfs4_create(fs_volume* volume, fs_vnode* dir, const char* name, int openMode,
*_cookie = cookie; *_cookie = cookie;
Inode* inode = reinterpret_cast<Inode*>(dir->private_node); Inode* inode = reinterpret_cast<Inode*>(dir->private_node);
FileSystem* fs = reinterpret_cast<FileSystem*>(volume->private_volume);
TRACE("volume = %p, dir = %llu, name = %s, openMode = %d, perms = %d", TRACE("volume = %p, dir = %llu, name = %s, openMode = %d, perms = %d",
volume, inode->ID(), name, openMode, perms); volume, inode->ID(), name, openMode, perms);
ReadLocker(fs->fRemoveNodeLock);
OpenDelegationData data; OpenDelegationData data;
status_t result = inode->Create(name, openMode, perms, cookie, &data, status_t result = inode->Create(name, openMode, perms, cookie, &data,
_newVnodeID); _newVnodeID);
@@ -591,7 +609,8 @@ nfs4_create(fs_volume* volume, fs_vnode* dir, const char* name, int openMode,
delete cookie; delete cookie;
return result; return result;
} }
} } else
unremove_vnode(volume, *_newVnodeID);
child->SetOpenState(cookie->fOpenState); child->SetOpenState(cookie->fOpenState);
@@ -732,7 +751,11 @@ static status_t
nfs4_remove_dir(fs_volume* volume, fs_vnode* parent, const char* name) nfs4_remove_dir(fs_volume* volume, fs_vnode* parent, const char* name)
{ {
Inode* inode = reinterpret_cast<Inode*>(parent->private_node); Inode* inode = reinterpret_cast<Inode*>(parent->private_node);
FileSystem* fs = reinterpret_cast<FileSystem*>(volume->private_volume);
TRACE("volume = %p, parent = %llu, name = %s", volume, inode->ID(), name); TRACE("volume = %p, parent = %llu, name = %s", volume, inode->ID(), name);
WriteLocker(fs->fRemoveNodeLock);
ino_t id; ino_t id;
status_t result = inode->Remove(name, NF4DIR, &id); status_t result = inode->Remove(name, NF4DIR, &id);
if (result != B_OK) if (result != B_OK)