/* * Copyright 2001-2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include "FUSEVolume.h" #include #include #include #include #include #include #include #include "FUSELowLevel.h" #include "../IORequestInfo.h" #include "../kernel_emu.h" #include "../RequestThread.h" #define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1)) // TODO: For remote/shared file systems (sshfs, nfs, etc.) we need to notice // that entries have been added/removed, so that we can (1) update our // FUSEEntry/FUSENode objects and (2) send out node monitoring messages. // The maximal node tree hierarchy levels we support. static const uint32 kMaxNodeTreeDepth = 1024; struct FUSEVolume::DirEntryCache { DirEntryCache() : fEntries(NULL), fNames(NULL), fEntryCount(0), fEntryCapacity(0), fNamesSize(0), fNamesCapacity(0) { } ~DirEntryCache() { free(fEntries); free(fNames); } status_t AddEntry(ino_t nodeID, const char* name) { // resize entries array, if full if (fEntryCount == fEntryCapacity) { // entries array full -- resize uint32 newCapacity = std::max(fEntryCapacity * 2, (uint32)8); Entry* newEntries = (Entry*)realloc(fEntries, newCapacity * sizeof(Entry)); if (newEntries == NULL) return B_NO_MEMORY; fEntries = newEntries; fEntryCapacity = newCapacity; } // resize names buffer, if full size_t nameSize = strlen(name) + 1; if (fNamesSize + nameSize > fNamesCapacity) { size_t newCapacity = std::max(fNamesCapacity * 2, (size_t)256); while (newCapacity < fNamesSize + nameSize) newCapacity *= 2; char* names = (char*)realloc(fNames, newCapacity); if (names == NULL) return B_NO_MEMORY; fNames = names; fNamesCapacity = newCapacity; } // add the entry fEntries[fEntryCount].nodeID = nodeID; fEntries[fEntryCount].nameOffset = fNamesSize; fEntries[fEntryCount].nameSize = nameSize; fEntryCount++; memcpy(fNames + fNamesSize, name, nameSize); fNamesSize += nameSize; return B_OK; } uint32 CountEntries() const { return fEntryCount; } bool ReadDirent(uint32 index, dev_t volumeID, bool align, dirent* buffer, size_t bufferSize) const { if (index >= fEntryCount) return false; const Entry& entry = fEntries[index]; // get and check the size size_t size = offsetof(struct dirent, d_name) + entry.nameSize; if (size > bufferSize) return false; // align the size, if requested if (align) size = std::min(bufferSize, ROUNDUP(size, 8)); // fill in the dirent buffer->d_dev = volumeID; buffer->d_ino = entry.nodeID; memcpy(buffer->d_name, fNames + entry.nameOffset, entry.nameSize); buffer->d_reclen = size; return true; } private: struct Entry { ino_t nodeID; uint32 nameOffset; uint32 nameSize; }; private: Entry* fEntries; char* fNames; uint32 fEntryCount; uint32 fEntryCapacity; size_t fNamesSize; size_t fNamesCapacity; }; struct FUSEVolume::DirCookie : fuse_file_info, RWLockable { union { off_t currentEntryOffset; uint32 currentEntryIndex; }; DirEntryCache* entryCache; bool getdirInterface; DirCookie() : currentEntryOffset(0), entryCache(NULL), getdirInterface(false) { flags = 0; fh_old = 0; writepage = 0; direct_io = 0; keep_cache = 0; flush = 0; fh = 0; lock_owner = 0; } ~DirCookie() { delete entryCache; } }; struct FUSEVolume::FileCookie : fuse_file_info, RWLockable { FileCookie(int openMode) { flags = openMode; fh_old = 0; writepage = 0; direct_io = 0; keep_cache = 0; flush = 0; fh = 0; lock_owner = 0; } }; struct FUSEVolume::AttrDirCookie : RWLockable { AttrDirCookie() : fAttributes(NULL), fAttributesSize(0), fCurrentOffset(0), fValid(false) { } ~AttrDirCookie() { Clear(); } void Clear() { free(fAttributes); fAttributes = NULL; fAttributesSize = 0; fCurrentOffset = 0; fValid = false; } status_t Allocate(size_t size) { Clear(); if (size == 0) return B_OK; fAttributes = (char*)malloc(size); if (fAttributes == NULL) return B_NO_MEMORY; fAttributesSize = size; return B_OK; } bool IsValid() const { return fValid; } void SetValid(bool valid) { fValid = valid; } char* AttributesBuffer() const { return fAttributes; } bool ReadNextEntry(dev_t volumeID, ino_t nodeID, bool align, dirent* buffer, size_t bufferSize) { if (fCurrentOffset >= fAttributesSize) return false; const char* name = fAttributes + fCurrentOffset; size_t nameLen = strlen(name); // get and check the size size_t size = offsetof(struct dirent, d_name) + nameLen + 1; if (size > bufferSize) return false; // align the size, if requested if (align) size = std::min(bufferSize, ROUNDUP(size, 8)); // fill in the dirent buffer->d_dev = volumeID; buffer->d_ino = nodeID; memcpy(buffer->d_name, name, nameLen + 1); buffer->d_reclen = size; fCurrentOffset += nameLen + 1; return true; } private: char* fAttributes; size_t fAttributesSize; size_t fCurrentOffset; bool fValid; }; struct FUSEVolume::AttrCookie : RWLockable { public: AttrCookie(const char* name) : fValue(NULL), fSize(0), fType(0) { _SetType(name); } AttrCookie(const char* name, const char* value) : fValue(strdup(value)), fSize(strlen(value) + 1), fType(0) { _SetType(name); } ~AttrCookie() { free(fValue); } bool IsValid() const { return fValue != NULL; } uint32 Type() const { return fType; } status_t Allocate(size_t size) { fValue = (char*)malloc(size); if (fValue == NULL) { fSize = 0; return B_NO_MEMORY; } fSize = size; return B_OK; } void Read(void* buffer, size_t bufferSize, off_t pos, size_t* bytesRead) const { if (pos < 0 || (uint64)pos > SIZE_MAX || (size_t)pos > fSize - 1) { *bytesRead = 0; return; } size_t copySize = fSize - pos; if (copySize > bufferSize) copySize = bufferSize; strlcpy((char*)buffer, &fValue[pos], bufferSize); *bytesRead = copySize; } char* Buffer() { return fValue; } size_t Size() const { return fSize; } private: void _SetType(const char* name) { if (strcmp(name, kAttrMimeTypeName) == 0) fType = B_MIME_STRING_TYPE; else fType = B_RAW_TYPE; } private: char* fValue; size_t fSize; uint32 fType; }; struct FUSEVolume::ReadDirBuffer { FUSEVolume* volume; FUSENode* directory; DirCookie* cookie; void* buffer; size_t bufferSize; size_t usedSize; uint32 entriesRead; uint32 maxEntries; status_t error; ReadDirBuffer(FUSEVolume* volume, FUSENode* directory, DirCookie* cookie, void* buffer, size_t bufferSize, uint32 maxEntries) : volume(volume), directory(directory), cookie(cookie), buffer(buffer), bufferSize(bufferSize), usedSize(0), entriesRead(0), maxEntries(maxEntries), error(B_OK) { } }; struct FUSEVolume::LockIterator { FUSEVolume* volume; FUSENode* firstNode; FUSENode* lastLockedNode; FUSENode* nextNode; FUSENode* stopBeforeNode; bool writeLock; LockIterator(FUSEVolume* volume, FUSENode* node, bool writeLock, FUSENode* stopBeforeNode) : volume(volume), firstNode(node), lastLockedNode(NULL), nextNode(node), stopBeforeNode(stopBeforeNode), writeLock(writeLock) { } ~LockIterator() { Unlock(); } void SetTo(FUSEVolume* volume, FUSENode* node, bool writeLock, FUSENode* stopBeforeNode) { Unlock(); this->volume = volume; this->firstNode = node; this->lastLockedNode = NULL; this->nextNode = node; this->stopBeforeNode = stopBeforeNode; this->writeLock = writeLock; } status_t LockNext(bool* _done, bool* _volumeUnlocked) { // increment the ref count first nextNode->refCount++; if (volume->fLockManager.TryGenericLock( nextNode == firstNode && writeLock, nextNode)) { // got the lock *_volumeUnlocked = false; } else { // node is locked -- we need to unlock the volume and wait for // the lock volume->fLock.Unlock(); status_t error = volume->fLockManager.GenericLock( nextNode == firstNode && writeLock, nextNode); volume->fLock.Lock(); *_volumeUnlocked = false; if (error != B_OK) { volume->_PutNode(nextNode); return error; } } lastLockedNode = nextNode; // get the parent node FUSENode* parent = nextNode->Parent(); if (parent == stopBeforeNode || parent == nextNode) { if (parent == nextNode) parent = NULL; *_done = true; } else *_done = false; nextNode = parent; return B_OK; } void Unlock() { if (lastLockedNode == NULL) return; volume->_UnlockNodeChainInternal(firstNode, writeLock, lastLockedNode, NULL); lastLockedNode = NULL; nextNode = firstNode; } void SetStopBeforeNode(FUSENode* stopBeforeNode) { this->stopBeforeNode = stopBeforeNode; } void Detach() { lastLockedNode = NULL; nextNode = firstNode; } }; struct FUSEVolume::RWLockableReadLocking { RWLockableReadLocking(FUSEVolume* volume) : fLockManager(volume != NULL ? &volume->fLockManager : NULL) { } RWLockableReadLocking(RWLockManager* lockManager) : fLockManager(lockManager) { } RWLockableReadLocking(const RWLockableReadLocking& other) : fLockManager(other.fLockManager) { } inline bool Lock(RWLockable* lockable) { return fLockManager != NULL && fLockManager->ReadLock(lockable); } inline void Unlock(RWLockable* lockable) { if (fLockManager != NULL) fLockManager->ReadUnlock(lockable); } private: RWLockManager* fLockManager; }; 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::RWLockableReadLocker : public AutoLocker { RWLockableReadLocker(FUSEVolume* volume, RWLockable* lockable) : AutoLocker( RWLockableReadLocking(volume)) { SetTo(lockable, false); } }; 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) { } }; struct FUSEVolume::MultiNodeLocker { MultiNodeLocker(FUSEVolume* volume, FUSENode* node1, bool lockParent1, bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2) : fVolume(volume), fNode1(NULL), fNode2(NULL), fLockParent1(lockParent1), fWriteLock1(writeLock1), fLockParent2(lockParent2), fWriteLock2(writeLock2) { fStatus = volume->_LockNodeChains(node1, lockParent1, writeLock1, node2, lockParent2, writeLock2); if (fStatus == B_OK) { fNode1 = node1; fNode2 = node2; } } ~MultiNodeLocker() { if (fNode1 != NULL) { fVolume->_UnlockNodeChains(fNode1, fLockParent1, fWriteLock1, fNode2, fLockParent2, fWriteLock2); } } status_t Status() const { return fStatus; } private: FUSEVolume* fVolume; FUSENode* fNode1; FUSENode* fNode2; bool fLockParent1; bool fWriteLock1; bool fLockParent2; bool fWriteLock2; status_t fStatus; }; // #pragma mark - FUSEVolume::FUSEVolume(FUSEFileSystem* fileSystem, dev_t id) : Volume(fileSystem, id), fFS(NULL), fRootNode(NULL), fNextNodeID(FUSE_ROOT_ID + 1), fUseNodeIDs(false) { } 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 error = fEntries.Init(); if (error != B_OK) return error; error = fNodes.Init(); if (error != B_OK) return error; // check lock error = fLock.InitCheck(); if (error != B_OK) return error; return B_OK; } // #pragma mark - FS status_t FUSEVolume::Mount(const char* device, uint32 flags, const char* parameters, ino_t* rootID) { printf("FUSEVolume::Mount()\n"); status_t error = _FileSystem()->InitClientFS(parameters); if (error != B_OK) RETURN_ERROR(error); fOps = _FileSystem()->GetLowlevelOps(); if (fOps == NULL) fFS = _FileSystem()->GetFS(); _FileSystem()->GetVolumeCapabilities(fCapabilities); const fuse_config& config = _FileSystem()->GetFUSEConfig(); fUseNodeIDs = config.use_ino; // update the fuse_context::private_data field before calling into the FS if (fFS != NULL) { fuse_context* context = (fuse_context*)RequestThread::GetCurrentThread() ->GetContext()->GetFSData(); context->private_data = fFS->userData; } // get the root node struct stat st; int fuseError; if (fOps != NULL) fuseError = fuse_ll_getattr(fOps, FUSE_ROOT_ID, &st); else fuseError = fuse_fs_getattr(fFS, "/", &st); if (fuseError != 0) RETURN_ERROR(fuseError); if (!fUseNodeIDs) st.st_ino = FUSE_ROOT_ID; // create a node and an entry object for the root node AutoLocker _(fLock); FUSENode* node = new(std::nothrow) FUSENode(st.st_ino, st.st_mode & S_IFMT); FUSEEntry* entry = node != NULL ? FUSEEntry::Create(node, "/", node) : NULL; if (node == NULL || entry == NULL) { delete node; delete entry; _FileSystem()->ExitClientFS(B_NO_MEMORY); RETURN_ERROR(B_NO_MEMORY); } node->refCount++; // for the entry node->entries.Add(entry); fRootNode = node; // insert the node and the entry fNodes.Insert(node); fEntries.Insert(entry); // init the volume name snprintf(fName, sizeof(fName), "%s Volume", _FileSystem()->GetName()); // publish the root node error = UserlandFS::KernelEmu::publish_vnode(fID, node->id, node, node->type, 0, _FileSystem()->GetNodeCapabilities()); if (error != B_OK) { _FileSystem()->ExitClientFS(B_NO_MEMORY); RETURN_ERROR(error); } *rootID = node->id; return B_OK; } status_t FUSEVolume::Unmount() { printf("FUSEVolume::Unmount()\n"); _FileSystem()->ExitClientFS(B_OK); return B_OK; } status_t FUSEVolume::Sync() { PRINT(("FUSEVolume::Sync()\n")); // There's no FUSE hook for sync'ing the whole FS. We need to individually // fsync all nodes that have been marked dirty. To keep things simple, we // hold the volume lock the whole time. That's a concurrency killer, but // usually sync isn't invoked that often. AutoLocker _(fLock); // iterate through all nodes FUSENodeTable::Iterator it = fNodes.GetIterator(); while (FUSENode* node = it.Next()) { if (!node->dirty) continue; // node is dirty -- we have to sync it int fuseError; if (fOps != NULL) { fuse_file_info cookie; fuseError = fuse_ll_open(fOps, node->id, &cookie); if (fuseError == 0) { fuse_ll_fsync(fOps, node->id, 0, &cookie); // full sync, not only data fuse_ll_flush(fOps, node->id, &cookie); fuse_ll_release(fOps, node->id, &cookie); } } else { // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) continue; // open, sync, and close the node FileCookie cookie(O_RDONLY); fuseError = fuse_fs_open(fFS, path, &cookie); if (fuseError == 0) { fuseError = fuse_fs_fsync(fFS, path, 0, &cookie); // full sync, not only data fuse_fs_flush(fFS, path, &cookie); fuse_fs_release(fFS, path, &cookie); } } if (fuseError == 0) { // sync'ing successful -- mark the node not dirty node->dirty = false; } } return B_OK; } status_t FUSEVolume::ReadFSInfo(fs_info* info) { if (_FileSystem()->HasHaikuFuseExtensions() && fFS->ops.ioctl != NULL) { int fuseError = fuse_fs_ioctl(fFS, "/", FUSE_HAIKU_GET_DRIVE_INFO, info, NULL, sizeof(fs_info), NULL); if (fuseError != 0) return fuseError; return B_OK; } // No Haiku FUSE extensions, so our knowledge is limited: use some values // from statfs and make reasonable guesses for the rest of them. struct statvfs st; int fuseError; if (fOps != NULL) { fuseError = fuse_ll_statfs(fOps, FUSE_ROOT_ID, &st); } else { if (fFS->ops.statfs == NULL) return B_UNSUPPORTED; fuseError = fuse_fs_statfs(fFS, "/", &st); } if (fuseError != 0) return fuseError; memset(info, 0, sizeof(*info)); info->flags = B_FS_IS_PERSISTENT; // assume the FS is persistent info->block_size = st.f_bsize; info->io_size = 64 * 1024; // some value info->total_blocks = st.f_blocks; info->free_blocks = st.f_bfree; info->total_nodes = st.f_files; info->free_nodes = 100; // st.f_favail is ignored by statfs() strlcpy(info->volume_name, fName, sizeof(info->volume_name)); // no way to get the real name (if any) return B_OK; } // #pragma mark - vnodes status_t 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); if (error != B_OK) return error; *vnid = node->id; return B_OK; } status_t FUSEVolume::GetVNodeName(void* _node, char* buffer, size_t bufferSize) { FUSENode* node = (FUSENode*)_node; AutoLocker _(fLock); // get one of the node's entries and return its name FUSEEntry* entry = node->entries.Head(); if (entry == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); if (entry->name == NULL || entry->name[0] == '\0') RETURN_ERROR(B_BAD_DATA); if (strlcpy(buffer, entry->name, bufferSize) >= bufferSize) RETURN_ERROR(B_NAME_TOO_LONG); return B_OK; } status_t FUSEVolume::ReadVNode(ino_t vnid, bool reenter, void** _node, int* type, uint32* flags, FSVNodeCapabilities* _capabilities) { AutoLocker _(fLock); FUSENode* node = fNodes.Lookup(vnid); if (node == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); node->refCount++; *_node = node; *type = node->type; *flags = 0; *_capabilities = _FileSystem()->GetNodeCapabilities(); return B_OK; } status_t FUSEVolume::WriteVNode(void* _node, bool reenter) { FUSENode* node = (FUSENode*)_node; AutoLocker _(fLock); _PutNode(node); return B_OK; } status_t FUSEVolume::RemoveVNode(void* node, bool reenter) { // TODO: Implement for real! return WriteVNode(node, reenter); } // #pragma mark - asynchronous I/O status_t FUSEVolume::DoIO(void* _node, void* _cookie, const IORequestInfo& requestInfo) { FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); if (!S_ISREG(node->type)) RETURN_ERROR(B_BAD_VALUE); BStackOrHeapArray buffer(requestInfo.length); char path[B_PATH_NAME_LENGTH]; size_t pathLen; int fuseError = 0; status_t error = B_OK; FileCookie alternativeCookie(requestInfo.isWrite ? O_WRONLY : O_RDONLY); if (cookie == NULL) { cookie = &alternativeCookie; if (fOps != NULL) { fuseError = fuse_ll_open(fOps, node->id, cookie); } else { AutoLocker locker(fLock); error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); fuseError = fuse_fs_open(fFS, path, cookie); } } if (fuseError != 0) RETURN_ERROR(fuseError); RWLockableReadLocker cookieLocker(this, cookie); if (requestInfo.isWrite) { error = UserlandFS::KernelEmu::read_from_io_request(GetID(), requestInfo.id, buffer, requestInfo.length); } size_t bytes = 0; if (error == B_OK) { bytes = requestInfo.length; error = _InternalIO(node, cookie, path, requestInfo.offset, buffer, bytes, requestInfo.isWrite); } if (error == B_OK && !requestInfo.isWrite) { error = UserlandFS::KernelEmu::write_to_io_request(GetID(), requestInfo.id, buffer, bytes); } UserlandFS::KernelEmu::notify_io_request(GetID(), requestInfo.id, error); if (cookie == &alternativeCookie) { if (fOps != NULL) fuse_ll_release(fOps, node->id, cookie); else fuse_fs_release(fFS, path, cookie); } if (error != B_OK) RETURN_ERROR(error); return B_OK; } // #pragma mark - nodes status_t FUSEVolume::SetFlags(void* _node, void* _cookie, int flags) { FileCookie* cookie = (FileCookie*)_cookie; RWLockableWriteLocker cookieLocker(this, cookie); const int settableFlags = O_APPEND | O_NONBLOCK | O_SYNC | O_RSYNC | O_DSYNC | O_DIRECT; cookie->flags = (cookie->flags & ~settableFlags) | (flags & settableFlags); return B_OK; } status_t FUSEVolume::FSync(void* _node) { FUSENode* node = (FUSENode*)_node; // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; bool dirty; AutoLocker locker(fLock); if (fOps != NULL) { // mark the node not dirty dirty = node->dirty; node->dirty = false; locker.Unlock(); fuse_file_info cookie; fuseError = fuse_ll_open(fOps, node->id, &cookie); if (fuseError == 0) { fuseError = fuse_ll_fsync(fOps, node->id, 0, &cookie); // full sync, not only data fuse_ll_flush(fOps, node->id, &cookie); fuse_ll_release(fOps, node->id, &cookie); } } else { // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); // mark the node not dirty dirty = node->dirty; node->dirty = false; locker.Unlock(); // open, sync, and close the node FileCookie cookie(O_RDONLY); fuseError = fuse_fs_open(fFS, path, &cookie); if (fuseError == 0) { fuseError = fuse_fs_fsync(fFS, path, 0, &cookie); // full sync, not only data fuse_fs_flush(fFS, path, &cookie); fuse_fs_release(fFS, path, &cookie); } } if (fuseError != 0) { // sync'ing failed -- mark the node dirty again locker.Lock(); node->dirty |= dirty; RETURN_ERROR(fuseError); } return B_OK; } status_t FUSEVolume::ReadSymlink(void* _node, char* buffer, size_t bufferSize, size_t* _bytesRead) { FUSENode* node = (FUSENode*)_node; // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_readlink(fOps, node->id, buffer, bufferSize); if (fuseError != 0) { *_bytesRead = 0; return fuseError; } // fuse_ll_readlink returns the actual size (even if the data didn't fit the buffer) *_bytesRead = fuseError; } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // read the symlink int fuseError = fuse_fs_readlink(fFS, path, buffer, bufferSize); if (fuseError != 0) { *_bytesRead = 0; return fuseError; } // fuse_fs_readlink() is supposed to return a NULL-terminated string, which // the Haiku interface doesn't require. We have to return the string length, // though. *_bytesRead = strnlen(buffer, bufferSize); } return B_OK; } status_t FUSEVolume::CreateSymlink(void* _dir, const char* name, const char* target, int mode) { FUSENode* dir = (FUSENode*)_dir; PRINT(("FUSEVolume::CreateSymlink(%p (%" B_PRId64 "), \"%s\" -> \"%s\", " "%#x)\n", dir, dir->id, name, target, mode)); // lock the directory NodeWriteLocker nodeLocker(this, dir, false); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_symlink(fOps, target, dir->id, name); } else { AutoLocker locker(fLock); // get a path for the entry char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(dir, name, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // create the symlink fuseError = fuse_fs_symlink(fFS, target, path); } if (fuseError != 0) RETURN_ERROR(fuseError); // TODO: Set the mode?! // mark the dir dirty AutoLocker locker(fLock); dir->dirty = true; locker.Unlock(); // send node monitoring message ino_t nodeID; if (_GetNodeID(dir, name, &nodeID)) { UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0, dir->id, nodeID, NULL, name); } return B_OK; } status_t FUSEVolume::Link(void* _dir, const char* name, void* _node) { FUSENode* dir = (FUSENode*)_dir; FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::Link(%p (%" B_PRId64 "), \"%s\" -> %p (%" B_PRId64 "))\n", dir, dir->id, name, node, node->id)); // lock the directories -- the target directory for writing, the node's // parent for reading MultiNodeLocker nodeLocker(this, dir, false, true, node, true, false); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_link(fOps, node->id, dir->id, name); } else { AutoLocker locker(fLock); // get a path for the entries char oldPath[B_PATH_NAME_LENGTH]; size_t oldPathLen; status_t error = _BuildPath(node, oldPath, oldPathLen); if (error != B_OK) RETURN_ERROR(error); char newPath[B_PATH_NAME_LENGTH]; size_t newPathLen; error = _BuildPath(dir, name, newPath, newPathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // link fuseError = fuse_fs_link(fFS, oldPath, newPath); } if (fuseError != 0) RETURN_ERROR(fuseError); // mark the dir and the node dirty AutoLocker locker(fLock); dir->dirty = true; node->dirty = true; locker.Unlock(); // send node monitoring message UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0, dir->id, node->id, NULL, name); return B_OK; } status_t FUSEVolume::Unlink(void* _dir, const char* name) { FUSENode* dir = (FUSENode*)_dir; PRINT(("FUSEVolume::Unlink(%p (%" B_PRId64 "), \"%s\")\n", dir, dir->id, name)); // lock the directory NodeWriteLocker nodeLocker(this, dir, false); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); // get the node ID (for the node monitoring message) ino_t nodeID; bool doNodeMonitoring = _GetNodeID(dir, name, &nodeID); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_unlink(fOps, dir->id, name); } else { AutoLocker locker(fLock); // get a path for the entry char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(dir, name, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // unlink fuseError = fuse_fs_unlink(fFS, path); } if (fuseError != 0) RETURN_ERROR(fuseError); // remove the entry AutoLocker locker(fLock); _RemoveEntry(dir, name); // mark the dir dirty dir->dirty = true; locker.Unlock(); // send node monitoring message if (doNodeMonitoring) { UserlandFS::KernelEmu::notify_listener(B_ENTRY_REMOVED, 0, fID, 0, dir->id, nodeID, NULL, name); } return B_OK; } status_t FUSEVolume::Rename(void* _oldDir, const char* oldName, void* _newDir, const char* newName) { FUSENode* oldDir = (FUSENode*)_oldDir; FUSENode* newDir = (FUSENode*)_newDir; PRINT(("FUSEVolume::Rename(%p (%" B_PRId64 "), \"%s\", %p (%" B_PRId64 "), \"%s\")\n", oldDir, oldDir->id, oldName, newDir, newDir->id, newName)); // lock the directories MultiNodeLocker nodeLocker(this, oldDir, false, true, newDir, false, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_rename(fOps, oldDir->id, oldName, newDir->id, newName); } else { AutoLocker locker(fLock); // get a path for the entries char oldPath[B_PATH_NAME_LENGTH]; size_t oldPathLen; status_t error = _BuildPath(oldDir, oldName, oldPath, oldPathLen); if (error != B_OK) RETURN_ERROR(error); char newPath[B_PATH_NAME_LENGTH]; size_t newPathLen; error = _BuildPath(newDir, newName, newPath, newPathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // rename fuseError = fuse_fs_rename(fFS, oldPath, newPath); } if (fuseError != 0) RETURN_ERROR(fuseError); // rename the entry AutoLocker locker(fLock); _RenameEntry(oldDir, oldName, newDir, newName); // mark the dirs dirty oldDir->dirty = true; newDir->dirty = true; // send node monitoring message ino_t nodeID; if (_GetNodeID(newDir, newName, &nodeID)) { UserlandFS::KernelEmu::notify_listener(B_ENTRY_MOVED, 0, fID, oldDir->id, newDir->id, nodeID, oldName, newName); } return B_OK; } status_t 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()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_access(fOps, node->id, mode); } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // call the access hook on the path fuseError = fuse_fs_access(fFS, path, mode); } if (fuseError != 0) return fuseError; return B_OK; } status_t FUSEVolume::ReadStat(void* _node, struct stat* st) { FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::ReadStat(%p (%" B_PRId64 "), %p)\n", node, node->id, st)); // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); st->st_dev = GetID(); st->st_ino = node->id; st->st_blksize = 2048; st->st_type = 0; int fuseError; if (fOps != NULL) { fuseError = fuse_ll_getattr(fOps, node->id, st); } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // stat the path fuseError = fuse_fs_getattr(fFS, path, st); } if (fuseError != 0) return fuseError; return B_OK; } status_t FUSEVolume::WriteStat(void* _node, const struct stat* st, uint32 mask) { FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::WriteStat(%p (%" B_PRId64 "), %p, %#" B_PRIx32 ")\n", node, node->id, st, mask)); // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); if (fOps != NULL) { int fuseError = fuse_ll_setattr(fOps, node->id, st, mask); if (fuseError != 0) RETURN_ERROR(fuseError); } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // permissions if ((mask & B_STAT_MODE) != 0) { int fuseError = fuse_fs_chmod(fFS, path, st->st_mode); if (fuseError != 0) RETURN_ERROR(fuseError); } // owner if ((mask & (B_STAT_UID | B_STAT_GID)) != 0) { uid_t uid = (mask & B_STAT_UID) != 0 ? st->st_uid : (uid_t)-1; gid_t gid = (mask & B_STAT_GID) != 0 ? st->st_gid : (gid_t)-1; int fuseError = fuse_fs_chown(fFS, path, uid, gid); if (fuseError != 0) RETURN_ERROR(fuseError); } // size if ((mask & B_STAT_SIZE) != 0) { // truncate int fuseError = fuse_fs_truncate(fFS, path, st->st_size); if (fuseError != 0) RETURN_ERROR(fuseError); } // access/modification time if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) != 0) { timespec tv[2] = { {st->st_atime, 0}, {st->st_mtime, 0} }; // If either time is not specified, we need to stat the file to get the // current value. if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) != (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) { struct stat currentStat; int fuseError = fuse_fs_getattr(fFS, path, ¤tStat); if (fuseError != 0) RETURN_ERROR(fuseError); if ((mask & B_STAT_ACCESS_TIME) == 0) tv[0].tv_sec = currentStat.st_atime; else tv[1].tv_sec = currentStat.st_mtime; } int fuseError = fuse_fs_utimens(fFS, path, tv); if (fuseError != 0) RETURN_ERROR(fuseError); } } // mark the node dirty AutoLocker locker(fLock); node->dirty = true; // send node monitoring message uint32 changedFields = mask & (B_STAT_MODE | B_STAT_UID | B_STAT_GID | B_STAT_SIZE | B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME); if (changedFields != 0) { UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED, changedFields, fID, 0, 0, node->id, NULL, NULL); } return B_OK; } // #pragma mark - files status_t FUSEVolume::Create(void* _dir, const char* name, int openMode, int mode, void** _cookie, ino_t* _vnid) { FUSENode* dir = (FUSENode*)_dir; PRINT(("FUSEVolume::Create(%p (%" B_PRId64 "), \"%s\", %#x, %#x)\n", dir, dir->id, name, openMode, mode)); // lock the directory NodeWriteLocker nodeLocker(this, dir, false); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); // allocate a file cookie FileCookie* cookie = new(std::nothrow) FileCookie(openMode); if (cookie == NULL) RETURN_ERROR(B_NO_MEMORY); ObjectDeleter cookieDeleter(cookie); FUSENode* node; int fuseError; if (fOps) { fuse_file_info cookie; fuse_ino_t ino; fuseError = fuse_ll_create(fOps, dir->id, name, mode, &cookie, ino); if (fuseError != 0) RETURN_ERROR(fuseError); // get the node // TODO do we really need it? status_t error = _GetNode(dir, name, &node); if (error != B_OK) { // This is bad. We've create the file successfully, but couldn't get // the node. Delete the entry. // We can't close the file because we don't know its inode. fuse_ll_flush(fOps, ino, &cookie); fuse_ll_release(fOps, ino, &cookie); fuse_ll_unlink(fOps, dir->id, name); RETURN_ERROR(error); } } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(dir, name, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // create the file fuseError = fuse_fs_create(fFS, path, mode, cookie); if (fuseError != 0) RETURN_ERROR(fuseError); // get the node error = _GetNode(dir, name, &node); if (error != B_OK) { // This is bad. We've create the file successfully, but couldn't get // the node. Close the file and delete the entry. fuse_fs_flush(fFS, path, cookie); fuse_fs_release(fFS, path, cookie); fuse_fs_unlink(fFS, path); RETURN_ERROR(error); } } // mark the dir and the node dirty AutoLocker locker(fLock); dir->dirty = true; node->dirty = true; locker.Unlock(); // send node monitoring message UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0, dir->id, node->id, NULL, name); cookieDeleter.Detach(); *_cookie = cookie; *_vnid = node->id; return B_OK; } status_t FUSEVolume::Open(void* _node, int openMode, void** _cookie) { FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::Open(%p (%" B_PRId64 "), %#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; // allocate a file cookie FileCookie* cookie = new(std::nothrow) FileCookie(openMode); if (cookie == NULL) RETURN_ERROR(B_NO_MEMORY); ObjectDeleter cookieDeleter(cookie); char path[B_PATH_NAME_LENGTH]; size_t pathLen; int fuseError; struct fuse_file_info llCookie = { 0 }; // FIXME store this in the FileCookie for lowlevel streams, we'll need it in read, write... if (fOps != NULL) { llCookie.flags = openMode; if (S_ISDIR(node->type)) fuseError = fuse_ll_opendir(fOps, node->id, &llCookie); else fuseError = fuse_ll_open(fOps, node->id, &llCookie); } else { AutoLocker locker(fLock); // get a path for the node status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // open the file fuseError = fuse_fs_open(fFS, path, cookie); } if (fuseError != 0) RETURN_ERROR(fuseError); // truncate the file, if requested if (truncate) { if (fOps != NULL) { struct stat st; st.st_size = 0; fuseError = fuse_ll_setattr(fOps, node->id, &st, FUSE_SET_ATTR_SIZE); if (fuseError != 0) { fuse_ll_flush(fOps, node->id, &llCookie); fuse_ll_release(fOps, node->id, &llCookie); RETURN_ERROR(fuseError); } } else { fuseError = fuse_fs_ftruncate(fFS, path, 0, cookie); if (fuseError == ENOSYS) { // Fallback to truncate if ftruncate is not implemented fuseError = fuse_fs_truncate(fFS, path, 0); } if (fuseError != 0) { fuse_fs_flush(fFS, path, cookie); fuse_fs_release(fFS, path, cookie); RETURN_ERROR(fuseError); } } // mark the node dirty AutoLocker locker(fLock); node->dirty = true; // send node monitoring message UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED, B_STAT_SIZE | B_STAT_MODIFICATION_TIME, fID, 0, 0, node->id, NULL, NULL); } if (S_ISREG(node->type)) { // The caching logic does not seem to work quite right with many // filesystems (e.g. sshfs is one such): read past the end of a file // returns errors instead of no data, for instance. #if 0 if (cookie->direct_io || llCookie.direct_io) { if (node->cacheCount > 0) { // In some very rare cases, for the same node, the first `open` // indicates that caching is allowed (by not setting `direct_io`), // but a subsequent `open` on the same node indicates that it is // NOT allowed (by setting `direct_io` to 1). debugger("FUSEVolume::Open(): inconsistent direct_io flags!"); UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id); node->cacheCount = 0; } } else { if (node->cacheCount == 0) { struct stat st; if (fOps != NULL) { fuseError = fuse_ll_getattr(fOps, node->id, &st); } else { fuseError = fuse_fs_getattr(fFS, path, &st); } if (fuseError != 0) { RETURN_ERROR(fuseError); } status_t error = UserlandFS::KernelEmu::file_cache_create(GetID(), node->id, st.st_size); if (error != B_OK) { RETURN_ERROR(error); } } // Increment cacheCount by extra 1 if the cache is kept to prevent // the cache from being deleted at close(). node->cacheCount += 1 + cookie->keep_cache + llCookie.keep_cache; } #endif } cookieDeleter.Detach(); *_cookie = cookie; return B_OK; } status_t FUSEVolume::Close(void* _node, void* _cookie) { FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; RWLockableReadLocker cookieLocker(this, cookie); // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_flush(fOps, node->id, cookie); } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // flush the file fuseError = fuse_fs_flush(fFS, path, cookie); } if (fuseError != 0) return fuseError; if (S_ISREG(node->type) && node->cacheCount > 0) { --node->cacheCount; if (node->cacheCount == 0) { UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id); } } return B_OK; } status_t FUSEVolume::FreeCookie(void* _node, void* _cookie) { FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; // no need to lock the cookie here, as no-one else uses it anymore // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); ObjectDeleter cookieDeleter(cookie); int fuseError; if (fOps) { fuseError = fuse_ll_release(fOps, node->id, cookie); } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // release the file fuseError = fuse_fs_release(fFS, path, cookie); } if (fuseError != 0) return fuseError; return B_OK; } status_t FUSEVolume::Read(void* _node, void* _cookie, off_t pos, void* buffer, size_t bufferSize, size_t* _bytesRead) { FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; RWLockableReadLocker cookieLocker(this, cookie); // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); *_bytesRead = bufferSize; status_t error = B_OK; if (S_ISREG(node->type) && node->cacheCount > 0) { error = UserlandFS::KernelEmu::file_cache_read(GetID(), node->id, cookie, pos, buffer, _bytesRead); } else error = _InternalIO(node, cookie, NULL, pos, (char *)buffer, *_bytesRead, false); if (error != B_OK) RETURN_ERROR(error); return B_OK; } status_t FUSEVolume::Write(void* _node, void* _cookie, off_t pos, const void* buffer, size_t bufferSize, size_t* _bytesWritten) { FUSENode* node = (FUSENode*)_node; FileCookie* cookie = (FileCookie*)_cookie; RWLockableReadLocker cookieLocker(this, cookie); *_bytesWritten = 0; // lock the directory NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); *_bytesWritten = bufferSize; status_t error = B_OK; if (S_ISREG(node->type) && node->cacheCount > 0) { error = UserlandFS::KernelEmu::file_cache_write(GetID(), node->id, cookie, pos, buffer, _bytesWritten); } else error = _InternalIO(node, cookie, NULL, pos, (char *)buffer, *_bytesWritten, true); if (error != B_OK) RETURN_ERROR(error); // mark the node dirty AutoLocker locker(fLock); node->dirty = true; // send node monitoring message UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED, B_STAT_SIZE | B_STAT_MODIFICATION_TIME, fID, 0, 0, node->id, NULL, NULL); // TODO: The size possibly doesn't change. // TODO: Avoid message flooding -- use a timeout and set the // B_STAT_INTERIM_UPDATE flag. return B_OK; } // #pragma mark - directories status_t FUSEVolume::CreateDir(void* _dir, const char* name, int mode) { FUSENode* dir = (FUSENode*)_dir; PRINT(("FUSEVolume::CreateDir(%p (%" B_PRId64 "), \"%s\", %#x)\n", dir, dir->id, name, mode)); // lock the directory NodeWriteLocker nodeLocker(this, dir, false); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_mkdir(fOps, dir->id, name, mode); } else { AutoLocker locker(fLock); // get a path for the entry char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(dir, name, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // create the dir fuseError = fuse_fs_mkdir(fFS, path, mode); } if (fuseError != 0) RETURN_ERROR(fuseError); // mark the dir dirty AutoLocker locker(fLock); dir->dirty = true; // send node monitoring message ino_t nodeID; if (_GetNodeID(dir, name, &nodeID)) { UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0, dir->id, nodeID, NULL, name); } return B_OK; } status_t FUSEVolume::RemoveDir(void* _dir, const char* name) { FUSENode* dir = (FUSENode*)_dir; PRINT(("FUSEVolume::RemoveDir(%p (%" B_PRId64 "), \"%s\")\n", dir, dir->id, name)); // lock the directory NodeWriteLocker nodeLocker(this, dir, false); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); // get the node ID (for the node monitoring message) ino_t nodeID; bool doNodeMonitoring = _GetNodeID(dir, name, &nodeID); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_rmdir(fOps, dir->id, name); } else { AutoLocker locker(fLock); // get a path for the entry char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(dir, name, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // remove the dir fuseError = fuse_fs_rmdir(fFS, path); } if (fuseError != 0) RETURN_ERROR(fuseError); // remove the entry AutoLocker locker(fLock); _RemoveEntry(dir, name); // mark the parent dir dirty dir->dirty = true; // send node monitoring message if (doNodeMonitoring) { UserlandFS::KernelEmu::notify_listener(B_ENTRY_REMOVED, 0, fID, 0, dir->id, nodeID, NULL, name); } return B_OK; } status_t FUSEVolume::OpenDir(void* _node, void** _cookie) { FUSENode* node = (FUSENode*)_node; PRINT(("FUSEVolume::OpenDir(%p (%" B_PRId64 "), %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) RETURN_ERROR(B_NO_MEMORY); ObjectDeleter cookieDeleter(cookie); if (fOps) { int fuseError = fuse_ll_opendir(fOps, node->id, cookie); if (fuseError != 0) return fuseError; } else { AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); if (fFS->ops.readdir == NULL && fFS->ops.getdir != NULL) { // no open call -- the FS only supports the deprecated getdir() // interface cookie->getdirInterface = true; } else { // open the dir int fuseError = fuse_fs_opendir(fFS, path, cookie); if (fuseError != 0) return fuseError; } } cookieDeleter.Detach(); *_cookie = cookie; return B_OK; } status_t FUSEVolume::CloseDir(void* node, void* _cookie) { return B_OK; } status_t 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); int fuseError; if (fOps != NULL) { fuseError = fuse_ll_releasedir(fOps, node->id, cookie); } else { if (cookie->getdirInterface) return B_OK; AutoLocker locker(fLock); // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); // release the dir fuseError = fuse_fs_releasedir(fFS, path, cookie); } if (fuseError != 0) return fuseError; return B_OK; } status_t FUSEVolume::ReadDir(void* _node, void* _cookie, void* buffer, size_t bufferSize, uint32 count, uint32* _countRead) { PRINT(("FUSEVolume::ReadDir(%p, %p, %p, %" B_PRIuSIZE ", %" B_PRId32 ")\n", _node, _cookie, buffer, bufferSize, count)); *_countRead = 0; FUSENode* node = (FUSENode*)_node; DirCookie* cookie = (DirCookie*)_cookie; RWLockableWriteLocker cookieLocker(this, cookie); uint32 countRead = 0; status_t readDirError = B_OK; AutoLocker locker(fLock); 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); off_t offset = cookie->currentEntryOffset; // read the dir int fuseError; if (fOps != NULL) { locker.Unlock(); // TODO pass the cookie from opendir here instead of NULL fuseError = fuse_ll_readdir(fOps, node->id, &readDirBuffer, (char*)buffer, bufferSize, &_AddReadDirEntryLowLevel, offset, NULL); // The request filler may or may not be used. If the filesystem decides that it has // already cached the directory, it can reply with an already filled buffer from a // previous run. So, we can't rely on any updates done to the cookie by that function. // So we need to check the number of entries in the buffer, and advance the // currentEntryOffset. if (fuseError > 0) { struct dirent* dirent = (struct dirent*)buffer; while (countRead < count && (char*)dirent + dirent->d_reclen <= (char*)buffer + fuseError) { countRead++; dirent = (struct dirent*)(((char*)dirent) + dirent->d_reclen); if (dirent->d_reclen == 0) break; } cookie->currentEntryOffset += (char*)dirent - (char*)buffer; fuseError = 0; } readDirError = 0; } else { // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); if (cookie->getdirInterface) { PRINT((" using getdir() interface\n")); fuseError = fFS->ops.getdir(path, (fuse_dirh_t)&readDirBuffer, &_AddReadDirEntryGetDir); } else { PRINT((" using readdir() interface\n")); fuseError = fuse_fs_readdir(fFS, path, &readDirBuffer, &_AddReadDirEntry, offset, cookie); } countRead = readDirBuffer.entriesRead; readDirError = readDirBuffer.error; } if (fuseError != 0) return fuseError; locker.Lock(); } if (cookie->entryCache != NULL) { // we're using an entry cache -- read into the buffer what we can dirent* entryBuffer = (dirent*)buffer; while (countRead < count && cookie->entryCache->ReadDirent(cookie->currentEntryIndex, fID, countRead + 1 < count, entryBuffer, bufferSize)) { countRead++; cookie->currentEntryIndex++; bufferSize -= entryBuffer->d_reclen; entryBuffer = (dirent*)((uint8*)entryBuffer + entryBuffer->d_reclen); } } *_countRead = countRead; return countRead > 0 ? B_OK : readDirError; } status_t FUSEVolume::RewindDir(void* _node, void* _cookie) { PRINT(("FUSEVolume::RewindDir(%p, %p)\n", _node, _cookie)); DirCookie* cookie = (DirCookie*)_cookie; RWLockableWriteLocker cookieLocker(this, cookie); if (cookie->getdirInterface) { delete cookie->entryCache; cookie->entryCache = NULL; cookie->currentEntryIndex = 0; } else { cookie->currentEntryOffset = 0; } return B_OK; } // #pragma mark - attribute directories // OpenAttrDir status_t FUSEVolume::OpenAttrDir(void* _node, void** _cookie) { // allocate an attribute directory cookie AttrDirCookie* cookie = new(std::nothrow) AttrDirCookie; if (cookie == NULL) RETURN_ERROR(B_NO_MEMORY); *_cookie = cookie; return B_OK; } // CloseAttrDir status_t FUSEVolume::CloseAttrDir(void* node, void* cookie) { return B_OK; } // FreeAttrDirCookie status_t FUSEVolume::FreeAttrDirCookie(void* _node, void* _cookie) { delete (AttrDirCookie*)_cookie; return B_OK; } // ReadAttrDir status_t FUSEVolume::ReadAttrDir(void* _node, void* _cookie, void* buffer, size_t bufferSize, uint32 count, uint32* _countRead) { 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()); char path[B_PATH_NAME_LENGTH]; size_t pathLen; if (fOps == NULL) { AutoLocker locker(fLock); // get a path for the node status_t error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); } if (!cookie->IsValid()) { // cookie not yet valid -- get the length of the list int listSize; if (fOps != NULL) listSize = fuse_ll_listxattr(fOps, node->id, NULL, 0); else listSize = fuse_fs_listxattr(fFS, path, NULL, 0); if (listSize < 0) RETURN_ERROR(listSize); while (true) { // allocate space for the listing status_t error = cookie->Allocate(listSize); if (error != B_OK) RETURN_ERROR(error); // read the listing int bytesRead; if (fOps != NULL) { bytesRead = fuse_ll_listxattr(fOps, node->id, cookie->AttributesBuffer(), listSize); } else bytesRead = fuse_fs_listxattr(fFS, path, cookie->AttributesBuffer(), listSize); if (bytesRead < 0) RETURN_ERROR(bytesRead); if (bytesRead == listSize) break; // attributes listing changed -- reread it listSize = bytesRead; } cookie->SetValid(true); } // we have a valid cookie now -- get the next entries from the cookie uint32 countRead = 0; dirent* entryBuffer = (dirent*)buffer; while (countRead < count && cookie->ReadNextEntry(fID, node->id, countRead + 1 < count, entryBuffer, bufferSize)) { countRead++; bufferSize -= entryBuffer->d_reclen; entryBuffer = (dirent*)((uint8*)entryBuffer + entryBuffer->d_reclen); } *_countRead = countRead; return B_OK; } // RewindAttrDir status_t FUSEVolume::RewindAttrDir(void* _node, void* _cookie) { AttrDirCookie* cookie = (AttrDirCookie*)_cookie; RWLockableWriteLocker cookieLocker(this, cookie); cookie->Clear(); return B_OK; } // #pragma mark - attributes status_t FUSEVolume::OpenAttr(void* _node, const char* name, int openMode, void** _cookie) { FUSENode* node = (FUSENode*)_node; // lock the node NodeReadLocker nodeLocker(this, node, true); if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); if (openMode != O_RDONLY) { // Write support currently not implemented RETURN_ERROR(B_UNSUPPORTED); } char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error; int attrSize; if (fOps != NULL) { attrSize = fuse_ll_getxattr(fOps, node->id, name, NULL, 0); } else { AutoLocker locker(fLock); // get a path for the node error = _BuildPath(node, path, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); attrSize = fuse_fs_getxattr(fFS, path, name, NULL, 0); } if (attrSize < 0) { if (strcmp(name, kAttrMimeTypeName) == 0) { // Return a fake MIME type attribute based on the file extension const char* mimeType = NULL; error = set_mime(&mimeType, S_ISDIR(node->type) ? NULL : &path[0]); if (error != B_OK) return error; *_cookie = new(std::nothrow)AttrCookie(name, mimeType); return B_OK; } // Reading attribute failed return attrSize; } AttrCookie* cookie = new(std::nothrow)AttrCookie(name); if (cookie == NULL) RETURN_ERROR(B_NO_MEMORY); error = cookie->Allocate(attrSize); if (error != B_OK) { delete cookie; RETURN_ERROR(error); } int bytesRead; if (fOps != NULL) bytesRead = fuse_ll_getxattr(fOps, node->id, name, cookie->Buffer(), attrSize); else bytesRead = fuse_fs_getxattr(fFS, path, name, cookie->Buffer(), attrSize); if (bytesRead < 0) { delete cookie; return bytesRead; } *_cookie = cookie; return B_OK; } status_t FUSEVolume::CloseAttr(void* _node, void* _cookie) { return B_OK; } status_t FUSEVolume::FreeAttrCookie(void* _node, void* _cookie) { delete (AttrCookie*)_cookie; return B_OK; } status_t FUSEVolume::ReadAttr(void* _node, void* _cookie, off_t pos, void* buffer, size_t bufferSize, size_t* bytesRead) { AttrCookie* cookie = (AttrCookie*)_cookie; RWLockableWriteLocker cookieLocker(this, cookie); if (!cookie->IsValid()) RETURN_ERROR(B_BAD_VALUE); cookie->Read(buffer, bufferSize, pos, bytesRead); return B_OK; } status_t FUSEVolume::ReadAttrStat(void* _node, void* _cookie, struct stat* st) { AttrCookie* cookie = (AttrCookie*)_cookie; RWLockableWriteLocker cookieLocker(this, cookie); if (!cookie->IsValid()) RETURN_ERROR(B_BAD_VALUE); st->st_size = cookie->Size(); st->st_type = cookie->Type(); return B_OK; } // #pragma mark - ino_t FUSEVolume::_GenerateNodeID() { ino_t id; do { id = fNextNodeID++; } while (fNodes.Lookup(id) != NULL); return id; } /*! Gets the ID of the node the entry specified by \a dir and \a entryName refers to. The ID is returned via \a _nodeID. The caller doesn't get a reference to the node. */ bool FUSEVolume::_GetNodeID(FUSENode* dir, const char* entryName, ino_t* _nodeID) { while (true) { AutoLocker locker(fLock); FUSENode* node; status_t error = _InternalGetNode(dir, entryName, &node, locker); if (error != B_OK) return false; if (node == NULL) continue; *_nodeID = node->id; _PutNode(node); return true; } } /*! Gets the node the entry specified by \a dir and \a entryName refers to. The found node is returned via \a _node. The caller gets a reference to the node as well as a vnode reference. */ status_t FUSEVolume::_GetNode(FUSENode* dir, const char* entryName, FUSENode** _node) { while (true) { AutoLocker locker(fLock); FUSENode* node; status_t error = _InternalGetNode(dir, entryName, &node, locker); if (error != B_OK) return error; if (node == NULL) continue; ino_t nodeID = node->id; locker.Unlock(); // get a reference for the caller void* privateNode; error = UserlandFS::KernelEmu::get_vnode(fID, nodeID, &privateNode); if (error != B_OK) RETURN_ERROR(error); locker.Lock(); if (privateNode != node) { // weird, the node changed! ERROR(("FUSEVolume::_GetNode(): cookie for node %" B_PRId64 " changed: expected: %p, got: %p\n", nodeID, node, privateNode)); UserlandFS::KernelEmu::put_vnode(fID, nodeID); _PutNode(node); continue; } // Put the node reference we got from _InternalGetNode. We've now got // a reference from get_vnode(). _PutNode(node); *_node = node; return B_OK; } } status_t FUSEVolume::_InternalGetNode(FUSENode* dir, const char* entryName, FUSENode** _node, AutoLocker& locker) { // handle special cases if (strcmp(entryName, ".") == 0) { // same directory if (!S_ISDIR(dir->type)) RETURN_ERROR(B_NOT_A_DIRECTORY); dir->refCount++; *_node = dir; return B_OK; } if (strcmp(entryName, "..") == 0) { // parent directory if (!S_ISDIR(dir->type)) RETURN_ERROR(B_NOT_A_DIRECTORY); FUSEEntry* entry = dir->entries.Head(); if (entry == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); entry->parent->refCount++; *_node = entry->parent; return B_OK; } // lookup the entry in the table FUSEEntry* entry = fEntries.Lookup(FUSEEntryRef(dir->id, entryName)); if (entry != NULL) { entry->node->refCount++; *_node = entry->node; return B_OK; } int fuseError; struct stat st; if (fOps != NULL) { fuseError = fuse_ll_lookup(fOps, dir->id, entryName, &st); } else { // construct a path for the entry char path[B_PATH_NAME_LENGTH]; size_t pathLen = 0; status_t error = _BuildPath(dir, entryName, path, pathLen); if (error != B_OK) return error; locker.Unlock(); // stat the path fuseError = fuse_fs_getattr(fFS, path, &st); } if (fuseError != 0) return fuseError; // lookup the entry in the table again entry = fEntries.Lookup(FUSEEntryRef(dir->id, entryName)); if (entry != NULL) { // check whether the node still matches if (entry->node->id == st.st_ino) { entry->node->refCount++; *_node = entry->node; } else { // nope, something changed -- return a NULL node and let the caller // call us again *_node = NULL; } return B_OK; } // lookup the node in the table FUSENode* node = NULL; if (fUseNodeIDs) node = fNodes.Lookup(st.st_ino); else st.st_ino = _GenerateNodeID(); if (node == NULL) { // no node yet -- create one node = new(std::nothrow) FUSENode(st.st_ino, st.st_mode & S_IFMT); if (node == NULL) RETURN_ERROR(B_NO_MEMORY); fNodes.Insert(node); } else { // get a node reference for the entry node->refCount++; } // create the entry entry = FUSEEntry::Create(dir, entryName, node); if (entry == NULL) { _PutNode(node); RETURN_ERROR(B_NO_MEMORY); } dir->refCount++; // dir reference for the entry fEntries.Insert(entry); node->entries.Add(entry); locker.Unlock(); // get a reference for the caller node->refCount++; *_node = node; return B_OK; } void FUSEVolume::_PutNode(FUSENode* node) { if (--node->refCount == 0) { fNodes.Remove(node); if (node->cacheCount != 0) UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id); delete node; } } void FUSEVolume::_PutNodes(FUSENode* const* nodes, int32 count) { for (int32 i = 0; i < count; i++) _PutNode(nodes[i]); } /*! Volume must be locked. The entry's directory must be write locked. */ void FUSEVolume::_RemoveEntry(FUSEEntry* entry) { fEntries.Remove(entry); entry->node->entries.Remove(entry); _PutNode(entry->node); _PutNode(entry->parent); delete entry; } /*! Volume must be locked. The directory must be write locked. */ status_t FUSEVolume::_RemoveEntry(FUSENode* dir, const char* name) { FUSEEntry* entry = fEntries.Lookup(FUSEEntryRef(dir->id, name)); if (entry == NULL) return B_ENTRY_NOT_FOUND; _RemoveEntry(entry); return B_OK; } /*! Volume must be locked. The directories must be write locked. */ status_t FUSEVolume::_RenameEntry(FUSENode* oldDir, const char* oldName, FUSENode* newDir, const char* newName) { FUSEEntry* entry = fEntries.Lookup(FUSEEntryRef(oldDir->id, oldName)); if (entry == NULL) return B_ENTRY_NOT_FOUND; // get a node reference for the new entry FUSENode* node = entry->node; node->refCount++; // remove the old entry _RemoveEntry(entry); // make sure there's no entry in our way _RemoveEntry(newDir, newName); // create a new entry entry = FUSEEntry::Create(newDir, newName, node); if (entry == NULL) { _PutNode(node); RETURN_ERROR(B_NO_MEMORY); } newDir->refCount++; // dir reference for the entry fEntries.Insert(entry); node->entries.Add(entry); return B_OK; } /*! 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 lockParent 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 lockParent, bool writeLock) { AutoLocker locker(fLock); FUSENode* originalNode = node; if (lockParent && node != NULL) node = node->Parent(); if (node == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); LockIterator iterator(this, node, writeLock, NULL); bool done; do { bool volumeUnlocked; status_t error = iterator.LockNext(&done, &volumeUnlocked); if (error != B_OK) RETURN_ERROR(error); if (volumeUnlocked) { // check whether we're still locking the right node if (lockParent && originalNode->Parent() != node) { // We don't -- unlock everything and try again. node = originalNode->Parent(); iterator.SetTo(this, node, writeLock, NULL); } } } while (!done); // Fail, if we couldn't lock all nodes up to the root. if (iterator.lastLockedNode != fRootNode) RETURN_ERROR(B_ENTRY_NOT_FOUND); iterator.Detach(); return B_OK; } void FUSEVolume::_UnlockNodeChain(FUSENode* node, bool parent, bool writeLock) { AutoLocker locker(fLock); if (parent && node != NULL) node = node->Parent(); _UnlockNodeChainInternal(node, writeLock, NULL, NULL); } /*! Unlocks all nodes from \a node up to (and including) \a stopNode (if \c NULL, it is ignored). If \a stopBeforeNode is given, the method stops before unlocking that node. The volume lock must be held. */ void FUSEVolume::_UnlockNodeChainInternal(FUSENode* node, bool writeLock, FUSENode* stopNode, FUSENode* stopBeforeNode) { FUSENode* originalNode = node; while (node != NULL && node != stopBeforeNode) { FUSENode* parent = node->Parent(); fLockManager.GenericUnlock(node == originalNode && writeLock, node); _PutNode(node); if (node == stopNode || parent == node) break; node = parent; } } status_t FUSEVolume::_LockNodeChains(FUSENode* node1, bool lockParent1, bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2) { // Since in this case locking is more complicated, we use a helper method. // It does the main work, but simply returns telling us to retry when the // node hierarchy changes. bool retry; do { status_t error = _LockNodeChainsInternal(node1, lockParent1, writeLock1, node2, lockParent2, writeLock2, &retry); if (error != B_OK) return error; } while (retry); return B_OK; } status_t FUSEVolume::_LockNodeChainsInternal(FUSENode* node1, bool lockParent1, bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2, bool* _retry) { // Locking order: // * A child of a node has to be locked before its parent. // * Sibling nodes have to be locked in ascending node ID order. // // This implies the following locking algorithm: // * We find the closest common ancestor of the two given nodes (might even // be one of the given nodes). // * We lock all ancestors on one branch (the one with the lower common // ancestor child node ID), but not including the common ancestor. // * We lock all ancestors on the other branch, not including the common // ancestor. // * We lock the common ancestor and all of its ancestors up to the root // node. // // When the hierarchy changes while we're waiting for a lock, we recheck the // conditions and in doubt have to be restarted. AutoLocker locker(fLock); FUSENode* originalNode1 = node1; FUSENode* originalNode2 = node2; if (lockParent1 && node1 != NULL) node1 = node1->Parent(); if (lockParent2 && node2 != NULL) node2 = node2->Parent(); if (node1 == NULL || node2 == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); // find the first common ancestor FUSENode* commonAncestor; bool inverseLockingOrder; if (!_FindCommonAncestor(node1, node2, &commonAncestor, &inverseLockingOrder)) { RETURN_ERROR(B_ENTRY_NOT_FOUND); } // lock the both node chains up to (but not including) the common ancestor LockIterator iterator1(this, node1, writeLock1, commonAncestor); LockIterator iterator2(this, node2, writeLock2, commonAncestor); for (int i = 0; i < 2; i++) { LockIterator& iterator = (i == 0) != inverseLockingOrder ? iterator1 : iterator2; // If the node is the common ancestor, don't enter the "do" loop, since // we don't have to lock anything here. if (iterator.firstNode == commonAncestor) continue; bool done; do { bool volumeUnlocked; status_t error = iterator.LockNext(&done, &volumeUnlocked); if (error != B_OK) RETURN_ERROR(error); if (volumeUnlocked) { // check whether we're still locking the right nodes if ((lockParent1 && originalNode1->Parent() != node1) || (lockParent2 && originalNode2->Parent() != node2)) { // We don't -- unlock everything and retry. *_retry = true; return B_OK; } // also recheck the common ancestor FUSENode* newCommonParent; bool newInverseLockingOrder; if (!_FindCommonAncestor(node1, node2, &newCommonParent, &newInverseLockingOrder)) { RETURN_ERROR(B_ENTRY_NOT_FOUND); } if (newCommonParent != commonAncestor || inverseLockingOrder != newInverseLockingOrder) { // Something changed -- unlock everything and retry. *_retry = true; return B_OK; } } } while (!done); } // Continue locking from the common ancestor to the root. If one of the // given nodes is the common ancestor and shall be write locked, we need to // use the respective iterator. LockIterator& iterator = node2 == commonAncestor && writeLock2 ? iterator2 : iterator1; iterator.SetStopBeforeNode(NULL); bool done; do { bool volumeUnlocked; status_t error = iterator.LockNext(&done, &volumeUnlocked); if (error != B_OK) RETURN_ERROR(error); if (volumeUnlocked) { // check whether we're still locking the right nodes if ((lockParent1 && originalNode1->Parent() != node1) || (lockParent2 && originalNode2->Parent() != node2)) { // We don't -- unlock everything and retry. *_retry = true; return B_OK; } // Also recheck the common ancestor, if we have just locked it. // Otherwise we can just continue to lock, since nothing below the // previously locked node can have changed. if (iterator.lastLockedNode == commonAncestor) { FUSENode* newCommonParent; bool newInverseLockingOrder; if (!_FindCommonAncestor(node1, node2, &newCommonParent, &newInverseLockingOrder)) { RETURN_ERROR(B_ENTRY_NOT_FOUND); } if (newCommonParent != commonAncestor || inverseLockingOrder != newInverseLockingOrder) { // Something changed -- unlock everything and retry. *_retry = true; return B_OK; } } } } while (!done); // Fail, if we couldn't lock all nodes up to the root. if (iterator.lastLockedNode != fRootNode) RETURN_ERROR(B_ENTRY_NOT_FOUND); // everything went fine iterator1.Detach(); iterator2.Detach(); *_retry = false; return B_OK; } void FUSEVolume::_UnlockNodeChains(FUSENode* node1, bool lockParent1, bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2) { AutoLocker locker(fLock); if (lockParent1 && node1 != NULL) node1 = node1->Parent(); if (lockParent2 && node2 != NULL) node2 = node2->Parent(); if (node1 == NULL || node2 == NULL) return; // find the common ancestor FUSENode* commonAncestor; bool inverseLockingOrder; if (!_FindCommonAncestor(node1, node2, &commonAncestor, &inverseLockingOrder)) { return; } // Unlock one branch up to the common ancestor and then the complete other // branch up to the root. If one of the given nodes is the common ancestor, // we need to make sure, we write-unlock it, if requested. if (node2 == commonAncestor && writeLock2) { _UnlockNodeChainInternal(node1, writeLock1, NULL, commonAncestor); _UnlockNodeChainInternal(node2, writeLock2, NULL, NULL); } else { _UnlockNodeChainInternal(node2, writeLock2, NULL, commonAncestor); _UnlockNodeChainInternal(node1, writeLock1, NULL, NULL); } } bool FUSEVolume::_FindCommonAncestor(FUSENode* node1, FUSENode* node2, FUSENode** _commonAncestor, bool* _inverseLockingOrder) { // handle trivial special case -- both nodes are the same if (node1 == node2) { *_commonAncestor = node1; *_inverseLockingOrder = false; return true; } // get the ancestors of both nodes FUSENode* ancestors1[kMaxNodeTreeDepth]; FUSENode* ancestors2[kMaxNodeTreeDepth]; uint32 count1; uint32 count2; if (!_GetNodeAncestors(node1, ancestors1, &count1) || !_GetNodeAncestors(node2, ancestors2, &count2)) { return false; } // find the first ancestor not common to both nodes uint32 index = 0; for (; index < count1 && index < count2; index++) { FUSENode* ancestor1 = ancestors1[count1 - index - 1]; FUSENode* ancestor2 = ancestors2[count2 - index - 1]; if (ancestor1 != ancestor2) { *_commonAncestor = ancestors1[count1 - index]; *_inverseLockingOrder = ancestor1->id > ancestor2->id; return true; } } // one node is an ancestor of the other *_commonAncestor = ancestors1[count1 - index]; *_inverseLockingOrder = index == count1; return true; } bool FUSEVolume::_GetNodeAncestors(FUSENode* node, FUSENode** ancestors, uint32* _count) { uint32 count = 0; while (node != NULL && count < kMaxNodeTreeDepth) { ancestors[count++] = node; if (node == fRootNode) { *_count = count; return true; } node = node->Parent(); } // Either the node is not in the tree or we hit the array limit. return false; } status_t FUSEVolume::_BuildPath(FUSENode* dir, const char* entryName, char* path, size_t& pathLen) { // get the directory path status_t error = _BuildPath(dir, path, pathLen); if (error != B_OK) return error; if (path[pathLen - 1] != '/') { path[pathLen++] = '/'; if (pathLen == B_PATH_NAME_LENGTH) RETURN_ERROR(B_NAME_TOO_LONG); } // append the entry name size_t len = strlen(entryName); if (pathLen + len >= B_PATH_NAME_LENGTH) RETURN_ERROR(B_NAME_TOO_LONG); memcpy(path + pathLen, entryName, len + 1); pathLen += len; return B_OK; } status_t FUSEVolume::_BuildPath(FUSENode* node, char* path, size_t& pathLen) { if (node == fRootNode) { // we hit the root strcpy(path, "/"); pathLen = 1; return B_OK; } // get an entry for the node and get its path FUSEEntry* entry = node->entries.Head(); if (entry == NULL) RETURN_ERROR(B_ENTRY_NOT_FOUND); return _BuildPath(entry->parent, entry->name, path, pathLen); } /*static*/ int FUSEVolume::_AddReadDirEntryLowLevel(void* _buffer, char* buf, size_t bufsize, const char* name, const struct stat* st, off_t offset) { ReadDirBuffer* buffer = (ReadDirBuffer*)_buffer; ino_t nodeID = st != NULL ? st->st_ino : 0; int type = st != NULL ? st->st_mode & S_IFMT : 0; return buffer->volume->_AddReadDirEntryLowLevel(buffer, buf, bufsize, name, type, nodeID, offset); } /*static*/ int FUSEVolume::_AddReadDirEntry(void* _buffer, const char* name, const struct stat* st, off_t offset) { ReadDirBuffer* buffer = (ReadDirBuffer*)_buffer; ino_t nodeID = st != NULL ? st->st_ino : 0; int type = st != NULL ? st->st_mode & S_IFMT : 0; return buffer->volume->_AddReadDirEntry(buffer, name, type, nodeID, offset); } /*static*/ int FUSEVolume::_AddReadDirEntryGetDir(fuse_dirh_t handle, const char* name, int type, ino_t nodeID) { ReadDirBuffer* buffer = (ReadDirBuffer*)handle; return buffer->volume->_AddReadDirEntry(buffer, name, type << 12, nodeID, 0); } int FUSEVolume::_AddReadDirEntryLowLevel(ReadDirBuffer* buffer, char* buf, size_t bufsize, const char* name, int type, ino_t nodeID, off_t offset) { PRINT(("FUSEVolume::_AddReadDirEntryLowLevel(%p, \"%s\", %#x, %" B_PRId64 ", %" B_PRId64 "\n", buffer, name, type, nodeID, offset)); AutoLocker locker(fLock); size_t entryLen = 0; // create a node and an entry, if necessary ino_t dirID = buffer->directory->id; FUSEEntry* entry; if (strcmp(name, ".") == 0) { // current dir entry nodeID = dirID; type = S_IFDIR; } else if (strcmp(name, "..") == 0) { // parent dir entry FUSEEntry* parentEntry = buffer->directory->entries.Head(); if (parentEntry == NULL) { ERROR(("FUSEVolume::_AddReadDirEntry(): dir %" B_PRId64 " has no entry!\n", dirID)); return 0; } nodeID = parentEntry->parent->id; type = S_IFDIR; } else if ((entry = fEntries.Lookup(FUSEEntryRef(dirID, name))) == NULL) { // get the node FUSENode* node = NULL; if (fUseNodeIDs) node = fNodes.Lookup(nodeID); else nodeID = _GenerateNodeID(); if (node == NULL) { // no node yet -- create one // If we don't have a valid type, we need to stat the node first. if (type == 0) { struct stat st; int fuseError; if (fOps != NULL) { fuseError = fuse_ll_getattr(fOps, node->id, &st); } else { char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(buffer->directory, name, path, pathLen); if (error != B_OK) { buffer->error = error; return 0; } locker.Unlock(); // stat the path fuseError = fuse_fs_getattr(fFS, path, &st); } locker.Lock(); if (fuseError != 0) { buffer->error = fuseError; return 0; } type = st.st_mode & S_IFMT; } node = new(std::nothrow) FUSENode(nodeID, type); if (node == NULL) { buffer->error = B_NO_MEMORY; return 1; } PRINT((" -> create node: %p, id: %" B_PRId64 "\n", node, nodeID)); fNodes.Insert(node); } else { // get a node reference for the entry node->refCount++; } // create the entry entry = FUSEEntry::Create(buffer->directory, name, node); if (entry == NULL) { _PutNode(node); buffer->error = B_NO_MEMORY; return 1; } buffer->directory->refCount++; // dir reference for the entry fEntries.Insert(entry); node->entries.Add(entry); } else { // TODO: Check whether the node's ID matches the one we got (if any)! nodeID = entry->node->id; type = entry->node->type; } // fill in the dirent dirent* dirEntry = (dirent*)(buf); dirEntry->d_dev = fID; dirEntry->d_ino = nodeID; strcpy(dirEntry->d_name, name); // align the entry length, so the next dirent will be aligned entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1; entryLen = ROUNDUP(entryLen, 8); dirEntry->d_reclen = entryLen; // update the buffer buffer->usedSize += entryLen; return 0; } int FUSEVolume::_AddReadDirEntry(ReadDirBuffer* buffer, const char* name, int type, ino_t nodeID, off_t offset) { PRINT(("FUSEVolume::_AddReadDirEntry(%p, \"%s\", %#x, %" B_PRId64 ", %" B_PRId64 "\n", buffer, name, type, nodeID, offset)); AutoLocker locker(fLock); size_t entryLen = 0; if (offset != 0) { // does the caller want more entries? if (buffer->entriesRead == buffer->maxEntries) return 1; // compute the entry length and check whether the entry still fits entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1; if (buffer->usedSize + entryLen > buffer->bufferSize) return 1; } // create a node and an entry, if necessary ino_t dirID = buffer->directory->id; FUSEEntry* entry; if (strcmp(name, ".") == 0) { // current dir entry nodeID = dirID; type = S_IFDIR; } else if (strcmp(name, "..") == 0) { // parent dir entry FUSEEntry* parentEntry = buffer->directory->entries.Head(); if (parentEntry == NULL) { ERROR(("FUSEVolume::_AddReadDirEntry(): dir %" B_PRId64 " has no entry!\n", dirID)); return 0; } nodeID = parentEntry->parent->id; type = S_IFDIR; } else if ((entry = fEntries.Lookup(FUSEEntryRef(dirID, name))) == NULL) { // get the node FUSENode* node = NULL; if (fUseNodeIDs) node = fNodes.Lookup(nodeID); else nodeID = _GenerateNodeID(); if (node == NULL) { // no node yet -- create one // If we don't have a valid type, we need to stat the node first. if (type == 0) { struct stat st; int fuseError; if (fOps != NULL) { fuseError = fuse_ll_getattr(fOps, node->id, &st); } else { char path[B_PATH_NAME_LENGTH]; size_t pathLen; status_t error = _BuildPath(buffer->directory, name, path, pathLen); if (error != B_OK) { buffer->error = error; return 0; } locker.Unlock(); // stat the path fuseError = fuse_fs_getattr(fFS, path, &st); } locker.Lock(); if (fuseError != 0) { buffer->error = fuseError; return 0; } type = st.st_mode & S_IFMT; } node = new(std::nothrow) FUSENode(nodeID, type); if (node == NULL) { buffer->error = B_NO_MEMORY; return 1; } PRINT((" -> create node: %p, id: %" B_PRId64 "\n", node, nodeID)); fNodes.Insert(node); } else { // get a node reference for the entry node->refCount++; } // create the entry entry = FUSEEntry::Create(buffer->directory, name, node); if (entry == NULL) { _PutNode(node); buffer->error = B_NO_MEMORY; return 1; } buffer->directory->refCount++; // dir reference for the entry fEntries.Insert(entry); node->entries.Add(entry); } else { // TODO: Check whether the node's ID matches the one we got (if any)! nodeID = entry->node->id; type = entry->node->type; } if (offset == 0) { // cache the entry if (buffer->cookie->entryCache == NULL) { // no cache yet -- create it buffer->cookie->entryCache = new(std::nothrow) DirEntryCache; if (buffer->cookie->entryCache == NULL) { buffer->error = B_NO_MEMORY; return 1; } } status_t error = buffer->cookie->entryCache->AddEntry(nodeID, name); if (error != B_OK) { buffer->error = error; return 1; } } else { // fill in the dirent dirent* dirEntry = (dirent*)((uint8*)buffer->buffer + buffer->usedSize); dirEntry->d_dev = fID; dirEntry->d_ino = nodeID; strcpy(dirEntry->d_name, name); if (buffer->entriesRead + 1 < buffer->maxEntries) { // align the entry length, so the next dirent will be aligned entryLen = ROUNDUP(entryLen, 8); entryLen = std::min(entryLen, buffer->bufferSize - buffer->usedSize); } dirEntry->d_reclen = entryLen; // update the buffer buffer->usedSize += entryLen; buffer->entriesRead++; buffer->cookie->currentEntryOffset = offset; } return 0; } status_t FUSEVolume::_InternalIO(FUSENode* node, FileCookie* cookie, const char* path, off_t pos, char* buffer, size_t& length, bool write) { PRINT(("FUSEVolume::_InternalIO(%p, %p, %s, %" B_PRIdOFF ", %p, %" B_PRIuSIZE ", %d)\n", node, cookie, path, pos, buffer, length, write)); int fuseError; if (fOps != NULL) { if (write) fuseError = fuse_ll_write(fOps, node->id, buffer, length, pos, cookie); else fuseError = fuse_ll_read(fOps, node->id, buffer, length, pos, cookie); } else { char pathBuf[B_PATH_NAME_LENGTH]; if (path == NULL) { AutoLocker locker(fLock); size_t pathLen; status_t error = _BuildPath(node, pathBuf, pathLen); if (error != B_OK) RETURN_ERROR(error); locker.Unlock(); path = pathBuf; } if (write) fuseError = fuse_fs_write(fFS, path, buffer, length, pos, cookie); else fuseError = fuse_fs_read(fFS, path, buffer, length, pos, cookie); } if (fuseError < 0) return fuseError; length = fuseError; return B_OK; }