diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEEntry.h b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEEntry.h index f415207232..13379d3f8a 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEEntry.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEEntry.h @@ -7,6 +7,8 @@ #include +#include + #include #include @@ -75,7 +77,7 @@ struct FUSEEntry : public HashTableLink, typedef DoublyLinkedList FUSEEntryList; -struct FUSENode : public HashTableLink { +struct FUSENode : RWLockable, HashTableLink { ino_t id; FUSEEntryList entries; int type; @@ -88,6 +90,12 @@ struct FUSENode : public HashTableLink { refCount(1) { } + + FUSENode* Parent() const + { + FUSEEntry* entry = entries.Head(); + return entry != NULL ? entry->parent : NULL; + } }; diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp index 31610f56f4..e4466a8265 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp @@ -132,7 +132,7 @@ private: }; -struct FUSEVolume::DirCookie : fuse_file_info { +struct FUSEVolume::DirCookie : fuse_file_info, RWLockable { union { off_t currentEntryOffset; uint32 currentEntryIndex; @@ -178,7 +178,7 @@ struct FUSEVolume::FileCookie : fuse_file_info { }; -struct FUSEVolume::AttrDirCookie { +struct FUSEVolume::AttrDirCookie : RWLockable { AttrDirCookie() : fAttributes(NULL), @@ -297,6 +297,108 @@ struct FUSEVolume::ReadDirBuffer { }; +struct FUSEVolume::RWLockableWriteLocking { + RWLockableWriteLocking(FUSEVolume* volume) + : + fLockManager(volume != NULL ? &volume->fLockManager : NULL) + { + } + + RWLockableWriteLocking(RWLockManager* lockManager) + : + fLockManager(lockManager) + { + } + + RWLockableWriteLocking(const RWLockableWriteLocking& other) + : + fLockManager(other.fLockManager) + { + } + + inline bool Lock(RWLockable* lockable) + { + return fLockManager != NULL && fLockManager->WriteLock(lockable); + } + + inline void Unlock(RWLockable* lockable) + { + if (fLockManager != NULL) + fLockManager->WriteUnlock(lockable); + } + +private: + RWLockManager* fLockManager; +}; + + +struct FUSEVolume::RWLockableWriteLocker + : public AutoLocker { + + RWLockableWriteLocker(FUSEVolume* volume, RWLockable* lockable) + : + AutoLocker( + RWLockableWriteLocking(volume)) + { + SetTo(lockable, false); + } +}; + + +struct FUSEVolume::NodeLocker { + NodeLocker(FUSEVolume* volume, FUSENode* node, bool parent, bool writeLock) + : + fVolume(volume), + fNode(NULL), + fParent(parent), + fWriteLock(writeLock) + { + fStatus = volume->_LockNodeChain(node, parent, writeLock); + if (fStatus == B_OK) + fNode = node; + } + + ~NodeLocker() + { + if (fNode != NULL) + fVolume->_UnlockNodeChain(fNode, fParent, fWriteLock); + } + + status_t Status() const + { + return fStatus; + } + +private: + FUSEVolume* fVolume; + FUSENode* fNode; + status_t fStatus; + bool fParent; + bool fWriteLock; +}; + + +struct FUSEVolume::NodeReadLocker : NodeLocker { + NodeReadLocker(FUSEVolume* volume, FUSENode* node, bool parent) + : + NodeLocker(volume, node, parent, false) + { + } +}; + + +struct FUSEVolume::NodeWriteLocker : NodeLocker { + NodeWriteLocker(FUSEVolume* volume, FUSENode* node, bool parent) + : + NodeLocker(volume, node, parent, true) + { + } +}; + + +// #pragma mark - + + inline FUSEFileSystem* FUSEVolume::_FileSystem() const { @@ -323,8 +425,13 @@ FUSEVolume::~FUSEVolume() status_t FUSEVolume::Init() { + // init lock manager + status_t error = fLockManager.Init(); + if (error != B_OK) + return error; + // init entry and node tables - status_t error = fEntries.Init(); + error = fEntries.Init(); if (error != B_OK) return error; @@ -459,6 +566,11 @@ FUSEVolume::Lookup(void* _dir, const char* entryName, ino_t* vnid) { FUSENode* dir = (FUSENode*)_dir; + // lock the directory + NodeReadLocker nodeLocker(this, dir, false); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + // look the node up FUSENode* node; status_t error = _GetNode(dir, entryName, &node); @@ -619,6 +731,11 @@ FUSEVolume::ReadSymlink(void* _node, char* buffer, size_t bufferSize, { FUSENode* node = (FUSENode*)_node; + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + AutoLocker locker(fLock); // get a path for the node @@ -685,6 +802,11 @@ FUSEVolume::Access(void* _node, int mode) { FUSENode* node = (FUSENode*)_node; + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + AutoLocker locker(fLock); // get a path for the node @@ -711,6 +833,11 @@ FUSEVolume::ReadStat(void* _node, struct stat* st) FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::ReadStat(%p (%lld), %p)\n", node, node->id, st)); + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + AutoLocker locker(fLock); // get a path for the node @@ -757,6 +884,11 @@ FUSEVolume::Open(void* _node, int openMode, void** _cookie) FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::Open(%p (%lld), %#x)\n", node, node->id, openMode)); + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + bool truncate = (openMode & O_TRUNC) != 0; openMode &= ~O_TRUNC; @@ -799,6 +931,11 @@ FUSEVolume::Close(void* _node, void* _cookie) FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + AutoLocker locker(fLock); // get a path for the node @@ -825,6 +962,11 @@ FUSEVolume::FreeCookie(void* _node, void* _cookie) FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + ObjectDeleter cookieDeleter(cookie); AutoLocker locker(fLock); @@ -856,6 +998,11 @@ FUSEVolume::Read(void* _node, void* _cookie, off_t pos, void* buffer, *_bytesRead = 0; + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + AutoLocker locker(fLock); // get a path for the node @@ -913,6 +1060,11 @@ FUSEVolume::OpenDir(void* _node, void** _cookie) FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::OpenDir(%p (%lld), %p)\n", node, node->id, _cookie)); + // lock the parent directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + // allocate a dir cookie DirCookie* cookie = new(std::nothrow) DirCookie; if (cookie == NULL) @@ -961,6 +1113,11 @@ FUSEVolume::FreeDirCookie(void* _node, void* _cookie) FUSENode* node = (FUSENode*)_node; DirCookie* cookie = (DirCookie*)_cookie; + // lock the parent directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + ObjectDeleter cookieDeleter(cookie); if (cookie->getdirInterface) @@ -997,6 +1154,8 @@ bufferSize, count)); FUSENode* node = (FUSENode*)_node; DirCookie* cookie = (DirCookie*)_cookie; + RWLockableWriteLocker cookieLocker(this, cookie); + uint32 countRead = 0; status_t readDirError = B_OK; @@ -1005,6 +1164,16 @@ bufferSize, count)); if (cookie->entryCache == NULL) { // We don't have an entry cache (yet), so we need to ask the client // file system to read the directory. + + locker.Unlock(); + + // lock the directory + NodeReadLocker nodeLocker(this, node, false); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + + locker.Lock(); + ReadDirBuffer readDirBuffer(this, node, cookie, buffer, bufferSize, count); @@ -1064,7 +1233,7 @@ FUSEVolume::RewindDir(void* _node, void* _cookie) PRINT(("FUSEVolume::RewindDir(%p, %p)\n", _node, _cookie)); DirCookie* cookie = (DirCookie*)_cookie; - AutoLocker locker(fLock); + RWLockableWriteLocker cookieLocker(this, cookie); if (cookie->getdirInterface) { delete cookie->entryCache; @@ -1121,8 +1290,15 @@ FUSEVolume::ReadAttrDir(void* _node, void* _cookie, void* buffer, FUSENode* node = (FUSENode*)_node; AttrDirCookie* cookie = (AttrDirCookie*)_cookie; + RWLockableWriteLocker cookieLocker(this, cookie); + *_countRead = 0; + // lock the directory + NodeReadLocker nodeLocker(this, node, true); + if (nodeLocker.Status() != B_OK) + RETURN_ERROR(nodeLocker.Status()); + AutoLocker locker(fLock); // get a path for the node @@ -1184,6 +1360,8 @@ FUSEVolume::RewindAttrDir(void* _node, void* _cookie) { AttrDirCookie* cookie = (AttrDirCookie*)_cookie; + RWLockableWriteLocker cookieLocker(this, cookie); + cookie->Clear(); return B_OK; @@ -1369,6 +1547,115 @@ FUSEVolume::_PutNode(FUSENode* node) } +void +FUSEVolume::_PutNodes(FUSENode* const* nodes, int32 count) +{ + for (int32 i = 0; i < count; i++) + _PutNode(nodes[i]); +} + + +/*! Locks the given node and all of its ancestors up to the root. The given + node is write-locked, if \a writeLock is \c true, read-locked otherwise. All + ancestors are always read-locked in either case. + + If \a parent is \c true, the given node itself is ignored, but locking + starts with the parent node of the given node (\a writeLock applies to the + parent node then). + + If the method fails, none of the nodes is locked. + + The volume lock must not be held. +*/ +status_t +FUSEVolume::_LockNodeChain(FUSENode* node, bool parent, bool writeLock) +{ + AutoLocker locker(fLock); + + if (parent && node != NULL) + node = node->Parent(); + + if (node == NULL) + RETURN_ERROR(B_ENTRY_NOT_FOUND); + + FUSENode* originalNode = node; + + status_t error = B_OK; + + do { + // increment the ref count first + node->refCount++; + + // lock the node + if (!fLockManager.TryGenericLock(node == originalNode && writeLock, + node)) { + // node is locked -- we need to unlock the volume and wait for + // the lock + locker.Unlock(); + error = fLockManager.GenericLock(node == originalNode && writeLock, + node); + locker.Lock(); + + if (error != B_OK) + break; + } + + // get the parent node + FUSENode* parent = node->Parent(); + if (parent == node) + break; + node = parent; + } while (node != NULL); + + // Fail, if we couldn't lock all nodes up to the root. + if (error == B_OK && node == NULL) + error = B_ENTRY_NOT_FOUND; + + if (error != B_OK) { + // locking failed -- unlock and release the references of all + // nodes + FUSENode* stopNode = node; + node = originalNode; + while (node != stopNode) { + fLockManager.GenericUnlock( + node == originalNode && writeLock, node); + FUSENode* parent = node->Parent(); + _PutNode(node); + node = parent; + } + + if (stopNode != NULL) + _PutNode(stopNode); + RETURN_ERROR(error); + } + + return B_OK; +} + + +void +FUSEVolume::_UnlockNodeChain(FUSENode* node, bool parent, bool writeLock) +{ + AutoLocker locker(fLock); + + if (parent && node != NULL) + node = node->Parent(); + + FUSENode* originalNode = node; + + while (node != NULL) { + FUSENode* parent = node->Parent(); + + fLockManager.GenericUnlock(node == originalNode && writeLock, node); + _PutNode(node); + + if (parent == node) + break; + node = parent; + } +} + + status_t FUSEVolume::_BuildPath(FUSENode* dir, const char* entryName, char* path, size_t& pathLen) diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h index e6eec2d511..b7ea5389b2 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h @@ -6,6 +6,7 @@ #define USERLAND_FS_FUSE_VOLUME_H #include +#include #include "Locker.h" @@ -134,6 +135,14 @@ private: struct FileCookie; struct AttrDirCookie; struct ReadDirBuffer; + struct RWLockableWriteLocking; + struct RWLockableWriteLocker; + struct NodeLocker; + struct NodeReadLocker; + struct NodeWriteLocker; + + friend struct RWLockableWriteLocking; + friend struct NodeLocker; private: inline FUSEFileSystem* _FileSystem() const; @@ -146,6 +155,12 @@ private: const char* entryName, FUSENode** _node, AutoLocker& locker); void _PutNode(FUSENode* node); + void _PutNodes(FUSENode* const* nodes, int32 count); + + status_t _LockNodeChain(FUSENode* node, bool parent, + bool writeLock); + void _UnlockNodeChain(FUSENode* node, bool parent, + bool writeLock); status_t _BuildPath(FUSENode* dir, const char* entryName, char* path, size_t& pathLen); @@ -161,6 +176,7 @@ private: off_t offset); private: + RWLockManager fLockManager; Locker fLock; fuse_fs* fFS; FUSEEntryTable fEntries; diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile index 69f6fc71c8..beb759395e 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile @@ -27,5 +27,5 @@ SharedLibrary libuserlandfs_fuse.so FUSEVolume.cpp : - userlandfs_server be + userlandfs_server libshared.a be ;