/*
* Copyright 2001-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "FUSEVolume.h"
#include <dirent.h>
#include <file_systems/mime_ext_table.h>
#include <algorithm>
#include <fs_info.h>
#include <NodeMonitor.h>
#include <AutoDeleter.h>
#include "FUSEFileSystem.h"
#include "../kernel_emu.h"
#include "../RequestThread.h"
// 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;
}
size_t DirentLength(uint32 index) const
{
const Entry& entry = fEntries[index];
return sizeof(dirent) + entry.nameSize - 1;
}
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 = sizeof(dirent) + entry.nameSize - 1;
if (size > bufferSize)
return false;
// align the size, if requested
if (align)
size = std::min(bufferSize, (size + 7) / 8 * 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 = sizeof(dirent) + nameLen;
if (size > bufferSize)
return false;
// align the size, if requested
if (align)
size = std::min(bufferSize, (size + 7) / 8 * 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<RWLockable, RWLockableReadLocking> {
RWLockableReadLocker(FUSEVolume* volume, RWLockable* lockable)
:
AutoLocker<RWLockable, RWLockableReadLocking>(
RWLockableReadLocking(volume))
{
SetTo(lockable, false);
}
};
struct FUSEVolume::RWLockableWriteLocker
: public AutoLocker<RWLockable, RWLockableWriteLocking> {
RWLockableWriteLocker(FUSEVolume* volume, RWLockable* lockable)
:
AutoLocker<RWLockable, RWLockableWriteLocking>(
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 -
inline FUSEFileSystem*
FUSEVolume::_FileSystem() const
{
return static_cast<FUSEFileSystem*>(fFileSystem);
}
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);
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
fuse_context* context = (fuse_context*)RequestThread::GetCurrentThread()
->GetContext()->GetFSData();
context->private_data = fFS->userData;
// get the root node
struct stat st;
int 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<Locker> _(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<Locker> _(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
// 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);
int 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 (gHasHaikuFuseExtensions == 1 && fFS->ops.get_fs_info != NULL) {
int fuseError = fuse_fs_get_fs_info(fFS, info);
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.
if (fFS->ops.statfs == NULL)
return B_UNSUPPORTED;
struct statvfs st;
int 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<Locker> _(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 (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<Locker> _(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<Locker> _(fLock);
_PutNode(node);
return B_OK;
}
status_t
FUSEVolume::RemoveVNode(void* node, bool reenter)
{
// TODO: Implement for real!
return WriteVNode(node, reenter);
}
// #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());
AutoLocker<Locker> 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);
// mark the node not dirty
bool dirty = node->dirty;
node->dirty = false;
locker.Unlock();
// open, sync, and close the node
FileCookie cookie(O_RDONLY);
int 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());
AutoLocker<Locker> 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());
AutoLocker<Locker> 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
int fuseError = fuse_fs_symlink(fFS, target, path);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// TODO: Set the mode?!
// mark the dir dirty
locker.Lock();
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());
AutoLocker<Locker> 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
int fuseError = fuse_fs_link(fFS, oldPath, newPath);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// mark the dir and the node dirty
locker.Lock();
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);
AutoLocker<Locker> 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
int fuseError = fuse_fs_unlink(fFS, path);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// remove the entry
locker.Lock();
_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());
AutoLocker<Locker> 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
int fuseError = fuse_fs_rename(fFS, oldPath, newPath);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// rename the entry
locker.Lock();
_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());
AutoLocker<Locker> 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
int 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());
AutoLocker<Locker> 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();
st->st_dev = GetID();
st->st_ino = node->id;
st->st_blksize = 2048;
st->st_type = 0;
// stat the path
int 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());
AutoLocker<Locker> 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
locker.Lock();
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<FileCookie> cookieDeleter(cookie);
AutoLocker<Locker> 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
int fuseError = fuse_fs_create(fFS, path, mode, cookie);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// get the node
FUSENode* 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
locker.Lock();
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<FileCookie> cookieDeleter(cookie);
AutoLocker<Locker> 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();
// open the file
int fuseError = fuse_fs_open(fFS, path, cookie);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// truncate the file, if requested
if (truncate) {
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
locker.Lock();
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);
}
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());
AutoLocker<Locker> 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
int fuseError = fuse_fs_flush(fFS, path, cookie);
if (fuseError != 0)
return fuseError;
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<FileCookie> cookieDeleter(cookie);
AutoLocker<Locker> 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
int 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);
*_bytesRead = 0;
// lock the directory
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
AutoLocker<Locker> 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 file
int bytesRead = fuse_fs_read(fFS, path, (char*)buffer, bufferSize, pos,
cookie);
if (bytesRead < 0)
return bytesRead;
*_bytesRead = bytesRead;
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());
AutoLocker<Locker> 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();
// write the file
int bytesWritten = fuse_fs_write(fFS, path, (const char*)buffer, bufferSize,
pos, cookie);
if (bytesWritten < 0)
return bytesWritten;
// mark the node dirty
locker.Lock();
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.
*_bytesWritten = bytesWritten;
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());
AutoLocker<Locker> 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
int fuseError = fuse_fs_mkdir(fFS, path, mode);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// mark the dir dirty
locker.Lock();
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);
AutoLocker<Locker> 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
int fuseError = fuse_fs_rmdir(fFS, path);
if (fuseError != 0)
RETURN_ERROR(fuseError);
// remove the entry
locker.Lock();
_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<DirCookie> cookieDeleter(cookie);
AutoLocker<Locker> 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<DirCookie> cookieDeleter(cookie);
if (cookie->getdirInterface)
return B_OK;
AutoLocker<Locker> 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
int 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> 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);
// 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);
off_t offset = cookie->currentEntryOffset;
locker.Unlock();
// read the dir
int fuseError;
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);
}
if (fuseError != 0)
return fuseError;
locker.Lock();
countRead = readDirBuffer.entriesRead;
readDirError = readDirBuffer.error;
}
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());
AutoLocker<Locker> 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 (!cookie->IsValid()) {
// cookie not yet valid -- get the length of the list
int listSize = fuse_fs_listxattr(fFS, path, NULL, 0);
if (listSize < 0)
RETURN_ERROR(listSize);
while (true) {
// allocate space for the listing
error = cookie->Allocate(listSize);
if (error != B_OK)
RETURN_ERROR(error);
// read the listing
int 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);
}
AutoLocker<Locker> 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();
int 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 = fuse_fs_getxattr(fFS, path, name, cookie->Buffer(),
attrSize);
if (bytesRead < 0)
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> 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> 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>& 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;
}
// 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
struct stat st;
int fuseError = fuse_fs_getattr(fFS, path, &st);
if (fuseError != 0)
return fuseError;
locker.Lock();
// 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);
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> 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> 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> 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> 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::_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::_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> 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 = sizeof(dirent) + strlen(name);
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) {
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
struct stat st;
int 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 = (entryLen + 7) / 8 * 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;
}
↑ V773 The function was exited without releasing the 'cookie' pointer. A memory leak is possible.