#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 <StackOrHeapArray.h>
#include "FUSELowLevel.h"
#include "../IORequestInfo.h"
#include "../kernel_emu.h"
#include "../RequestThread.h"
#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
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)
{
if (fEntryCount == fEntryCapacity) {
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;
}
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;
}
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];
size_t size = offsetof(struct dirent, d_name) + entry.nameSize;
if (size > bufferSize)
return false;
if (align)
size = std::min(bufferSize, ROUNDUP(size, 8));
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);
size_t size = offsetof(struct dirent, d_name) + nameLen + 1;
if (size > bufferSize)
return false;
if (align)
size = std::min(bufferSize, ROUNDUP(size, 8));
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)
{
nextNode->refCount++;
if (volume->fLockManager.TryGenericLock(
nextNode == firstNode && writeLock, nextNode)) {
*_volumeUnlocked = false;
} else {
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;
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;
};
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()
{
status_t error = fLockManager.Init();
if (error != B_OK)
return error;
error = fEntries.Init();
if (error != B_OK)
return error;
error = fNodes.Init();
if (error != B_OK)
return error;
error = fLock.InitCheck();
if (error != B_OK)
return error;
return B_OK;
}
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;
if (fFS != NULL) {
fuse_context* context = (fuse_context*)RequestThread::GetCurrentThread()
->GetContext()->GetFSData();
context->private_data = fFS->userData;
}
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;
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++;
node->entries.Add(entry);
fRootNode = node;
fNodes.Insert(node);
fEntries.Insert(entry);
snprintf(fName, sizeof(fName), "%s Volume", _FileSystem()->GetName());
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");
UserlandFS::KernelEmu::put_vnode(fID, fRootNode->id);
_FileSystem()->ExitClientFS(B_OK);
return B_OK;
}
status_t
FUSEVolume::Sync()
{
PRINT(("FUSEVolume::Sync()\n"));
AutoLocker<Locker> _(fLock);
FUSENodeTable::Iterator it = fNodes.GetIterator();
while (FUSENode* node = it.Next()) {
if (!node->dirty)
continue;
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);
fuse_ll_flush(fOps, node->id, &cookie);
fuse_ll_release(fOps, node->id, &cookie);
}
} else {
char path[B_PATH_NAME_LENGTH];
size_t pathLen;
status_t error = _BuildPath(node, path, pathLen);
if (error != B_OK)
continue;
FileCookie cookie(O_RDONLY);
fuseError = fuse_fs_open(fFS, path, &cookie);
if (fuseError == 0) {
fuseError = fuse_fs_fsync(fFS, path, 0, &cookie);
fuse_fs_flush(fFS, path, &cookie);
fuse_fs_release(fFS, path, &cookie);
}
}
if (fuseError == 0) {
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;
}
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;
if ((st.f_flag & ST_RDONLY) != 0)
info->flags |= B_FS_IS_READONLY;
info->block_size = st.f_bsize;
info->io_size = 64 * 1024;
info->total_blocks = st.f_blocks;
info->free_blocks = st.f_bfree;
info->total_nodes = st.f_files;
info->free_nodes = 100;
strlcpy(info->volume_name, fName, sizeof(info->volume_name));
return B_OK;
}
status_t
FUSEVolume::Lookup(void* _dir, const char* entryName, ino_t* vnid)
{
FUSENode* dir = (FUSENode*)_dir;
NodeReadLocker nodeLocker(this, dir, false);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
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);
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<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)
{
return WriteVNode(node, reenter);
}
status_t
FUSEVolume::DoIO(void* _node, void* _cookie, const IORequestInfo& requestInfo)
{
FUSENode* node = (FUSENode*)_node;
FileCookie* cookie = (FileCookie*)_cookie;
if (requestInfo.length == 0)
return B_OK;
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<char, B_PAGE_SIZE> 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> 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;
}
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;
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
int fuseError;
bool dirty;
AutoLocker<Locker> locker(fLock);
if (fOps != NULL) {
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);
fuse_ll_flush(fOps, node->id, &cookie);
fuse_ll_release(fOps, node->id, &cookie);
}
} else {
char path[B_PATH_NAME_LENGTH];
size_t pathLen;
status_t error = _BuildPath(node, path, pathLen);
if (error != B_OK)
RETURN_ERROR(error);
dirty = node->dirty;
node->dirty = false;
locker.Unlock();
FileCookie cookie(O_RDONLY);
fuseError = fuse_fs_open(fFS, path, &cookie);
if (fuseError == 0) {
fuseError = fuse_fs_fsync(fFS, path, 0, &cookie);
fuse_fs_flush(fFS, path, &cookie);
fuse_fs_release(fFS, path, &cookie);
}
}
if (fuseError != 0) {
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;
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;
}
*_bytesRead = fuseError;
} else {
AutoLocker<Locker> locker(fLock);
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 fuseError = fuse_fs_readlink(fFS, path, buffer, bufferSize);
if (fuseError != 0) {
*_bytesRead = 0;
return fuseError;
}
*_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));
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> locker(fLock);
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();
fuseError = fuse_fs_symlink(fFS, target, path);
}
if (fuseError != 0)
RETURN_ERROR(fuseError);
AutoLocker<Locker> locker(fLock);
dir->dirty = true;
locker.Unlock();
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));
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> locker(fLock);
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();
fuseError = fuse_fs_link(fFS, oldPath, newPath);
}
if (fuseError != 0)
RETURN_ERROR(fuseError);
AutoLocker<Locker> locker(fLock);
dir->dirty = true;
node->dirty = true;
locker.Unlock();
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));
NodeWriteLocker nodeLocker(this, dir, false);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
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> locker(fLock);
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();
fuseError = fuse_fs_unlink(fFS, path);
}
if (fuseError != 0)
RETURN_ERROR(fuseError);
AutoLocker<Locker> locker(fLock);
_RemoveEntry(dir, name);
dir->dirty = true;
locker.Unlock();
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));
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> locker(fLock);
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();
fuseError = fuse_fs_rename(fFS, oldPath, newPath);
}
if (fuseError != 0)
RETURN_ERROR(fuseError);
AutoLocker<Locker> locker(fLock);
_RenameEntry(oldDir, oldName, newDir, newName);
oldDir->dirty = true;
newDir->dirty = true;
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;
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> locker(fLock);
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();
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));
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> locker(fLock);
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();
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));
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> locker(fLock);
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 ((mask & B_STAT_MODE) != 0) {
int fuseError = fuse_fs_chmod(fFS, path, st->st_mode);
if (fuseError != 0)
RETURN_ERROR(fuseError);
}
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);
}
if ((mask & B_STAT_SIZE) != 0) {
int fuseError = fuse_fs_truncate(fFS, path, st->st_size);
if (fuseError != 0)
RETURN_ERROR(fuseError);
}
if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) != 0) {
timespec tv[2] = {
{st->st_atime, 0},
{st->st_mtime, 0}
};
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);
}
}
AutoLocker<Locker> locker(fLock);
node->dirty = true;
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;
}
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));
NodeWriteLocker nodeLocker(this, dir, false);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
FileCookie* cookie = new(std::nothrow) FileCookie(openMode);
if (cookie == NULL)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<FileCookie> 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);
status_t error = _GetNode(dir, name, &node);
if (error != B_OK) {
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> locker(fLock);
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();
fuseError = fuse_fs_create(fFS, path, mode, cookie);
if (fuseError != 0)
RETURN_ERROR(fuseError);
error = _GetNode(dir, name, &node);
if (error != B_OK) {
fuse_fs_flush(fFS, path, cookie);
fuse_fs_release(fFS, path, cookie);
fuse_fs_unlink(fFS, path);
RETURN_ERROR(error);
}
}
AutoLocker<Locker> locker(fLock);
dir->dirty = true;
node->dirty = true;
locker.Unlock();
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));
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
bool truncate = (openMode & O_TRUNC) != 0;
openMode &= ~O_TRUNC;
FileCookie* cookie = new(std::nothrow) FileCookie(openMode);
if (cookie == NULL)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<FileCookie> cookieDeleter(cookie);
char path[B_PATH_NAME_LENGTH];
size_t pathLen;
int fuseError;
struct fuse_file_info llCookie = { 0 };
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> locker(fLock);
status_t 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);
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) {
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);
}
}
AutoLocker<Locker> locker(fLock);
node->dirty = true;
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)) {
#if 0
if (cookie->direct_io || llCookie.direct_io) {
if (node->cacheCount > 0) {
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);
}
}
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);
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> locker(fLock);
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();
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;
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
ObjectDeleter<FileCookie> cookieDeleter(cookie);
int fuseError;
if (fOps) {
fuseError = fuse_ll_release(fOps, node->id, cookie);
} else {
AutoLocker<Locker> locker(fLock);
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();
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);
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;
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);
AutoLocker<Locker> locker(fLock);
node->dirty = true;
UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED,
B_STAT_SIZE | B_STAT_MODIFICATION_TIME, fID, 0, 0, node->id, NULL,
NULL);
return B_OK;
}
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));
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> locker(fLock);
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();
fuseError = fuse_fs_mkdir(fFS, path, mode);
}
if (fuseError != 0)
RETURN_ERROR(fuseError);
AutoLocker<Locker> locker(fLock);
dir->dirty = true;
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));
NodeWriteLocker nodeLocker(this, dir, false);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
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> locker(fLock);
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();
fuseError = fuse_fs_rmdir(fFS, path);
}
if (fuseError != 0)
RETURN_ERROR(fuseError);
AutoLocker<Locker> locker(fLock);
_RemoveEntry(dir, name);
dir->dirty = true;
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));
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
DirCookie* cookie = new(std::nothrow) DirCookie;
if (cookie == NULL)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<DirCookie> cookieDeleter(cookie);
if (fOps) {
int fuseError = fuse_ll_opendir(fOps, node->id, cookie);
if (fuseError != 0)
return fuseError;
} else {
AutoLocker<Locker> locker(fLock);
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) {
cookie->getdirInterface = true;
} else {
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;
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
ObjectDeleter<DirCookie> cookieDeleter(cookie);
int fuseError;
if (fOps != NULL) {
fuseError = fuse_ll_releasedir(fOps, node->id, cookie);
} else {
if (cookie->getdirInterface)
return B_OK;
AutoLocker<Locker> locker(fLock);
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();
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) {
locker.Unlock();
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;
int fuseError;
if (fOps != NULL) {
locker.Unlock();
fuseError = fuse_ll_readdir(fOps, node->id, &readDirBuffer, (char*)buffer, bufferSize,
&_AddReadDirEntryLowLevel, offset, NULL);
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 {
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) {
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;
}
status_t
FUSEVolume::OpenAttrDir(void* _node, void** _cookie)
{
AttrDirCookie* cookie = new(std::nothrow) AttrDirCookie;
if (cookie == NULL)
RETURN_ERROR(B_NO_MEMORY);
*_cookie = cookie;
return B_OK;
}
status_t
FUSEVolume::CloseAttrDir(void* node, void* cookie)
{
return B_OK;
}
status_t
FUSEVolume::FreeAttrDirCookie(void* _node, void* _cookie)
{
delete (AttrDirCookie*)_cookie;
return B_OK;
}
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;
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> locker(fLock);
status_t error = _BuildPath(node, path, pathLen);
if (error != B_OK)
RETURN_ERROR(error);
locker.Unlock();
}
if (!cookie->IsValid()) {
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) {
status_t error = cookie->Allocate(listSize);
if (error != B_OK)
RETURN_ERROR(error);
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;
listSize = bytesRead;
}
cookie->SetValid(true);
}
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;
}
status_t
FUSEVolume::RewindAttrDir(void* _node, void* _cookie)
{
AttrDirCookie* cookie = (AttrDirCookie*)_cookie;
RWLockableWriteLocker cookieLocker(this, cookie);
cookie->Clear();
return B_OK;
}
status_t
FUSEVolume::OpenAttr(void* _node, const char* name, int openMode,
void** _cookie)
{
FUSENode* node = (FUSENode*)_node;
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
if (openMode != O_RDONLY) {
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> locker(fLock);
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) {
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;
}
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;
}
ino_t
FUSEVolume::_GenerateNodeID()
{
ino_t id;
do {
id = fNextNodeID++;
} while (fNodes.Lookup(id) != NULL);
return id;
}
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;
}
}
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();
void* privateNode;
error = UserlandFS::KernelEmu::get_vnode(fID, nodeID, &privateNode);
if (error != B_OK)
RETURN_ERROR(error);
locker.Lock();
if (privateNode != node) {
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;
}
_PutNode(node);
*_node = node;
return B_OK;
}
}
status_t
FUSEVolume::_InternalGetNode(FUSENode* dir, const char* entryName,
FUSENode** _node, AutoLocker<Locker>& locker)
{
if (strcmp(entryName, ".") == 0) {
if (!S_ISDIR(dir->type))
RETURN_ERROR(B_NOT_A_DIRECTORY);
dir->refCount++;
*_node = dir;
return B_OK;
}
if (strcmp(entryName, "..") == 0) {
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;
}
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 {
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();
fuseError = fuse_fs_getattr(fFS, path, &st);
}
if (fuseError != 0)
return fuseError;
entry = fEntries.Lookup(FUSEEntryRef(dir->id, entryName));
if (entry != NULL) {
if (entry->node->id == st.st_ino) {
entry->node->refCount++;
*_node = entry->node;
} else {
*_node = NULL;
}
return B_OK;
}
FUSENode* node = NULL;
if (fUseNodeIDs)
node = fNodes.Lookup(st.st_ino);
else
st.st_ino = _GenerateNodeID();
if (node == NULL) {
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 {
node->refCount++;
}
entry = FUSEEntry::Create(dir, entryName, node);
if (entry == NULL) {
_PutNode(node);
RETURN_ERROR(B_NO_MEMORY);
}
dir->refCount++;
fEntries.Insert(entry);
node->entries.Add(entry);
locker.Unlock();
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]);
}
void
FUSEVolume::_RemoveEntry(FUSEEntry* entry)
{
fEntries.Remove(entry);
entry->node->entries.Remove(entry);
_PutNode(entry->node);
_PutNode(entry->parent);
delete entry;
}
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;
}
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;
FUSENode* node = entry->node;
node->refCount++;
_RemoveEntry(entry);
_RemoveEntry(newDir, newName);
entry = FUSEEntry::Create(newDir, newName, node);
if (entry == NULL) {
_PutNode(node);
RETURN_ERROR(B_NO_MEMORY);
}
newDir->refCount++;
fEntries.Insert(entry);
node->entries.Add(entry);
return B_OK;
}
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) {
if (lockParent && originalNode->Parent() != node) {
node = originalNode->Parent();
iterator.SetTo(this, node, writeLock, NULL);
}
}
} while (!done);
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);
}
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)
{
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)
{
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);
FUSENode* commonAncestor;
bool inverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &commonAncestor,
&inverseLockingOrder)) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
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 (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) {
if ((lockParent1 && originalNode1->Parent() != node1)
|| (lockParent2 && originalNode2->Parent() != node2)) {
*_retry = true;
return B_OK;
}
FUSENode* newCommonParent;
bool newInverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &newCommonParent,
&newInverseLockingOrder)) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
if (newCommonParent != commonAncestor
|| inverseLockingOrder != newInverseLockingOrder) {
*_retry = true;
return B_OK;
}
}
} while (!done);
}
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) {
if ((lockParent1 && originalNode1->Parent() != node1)
|| (lockParent2 && originalNode2->Parent() != node2)) {
*_retry = true;
return B_OK;
}
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) {
*_retry = true;
return B_OK;
}
}
}
} while (!done);
if (iterator.lastLockedNode != fRootNode)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
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;
FUSENode* commonAncestor;
bool inverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &commonAncestor,
&inverseLockingOrder)) {
return;
}
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)
{
if (node1 == node2) {
*_commonAncestor = node1;
*_inverseLockingOrder = false;
return true;
}
FUSENode* ancestors1[kMaxNodeTreeDepth];
FUSENode* ancestors2[kMaxNodeTreeDepth];
uint32 count1;
uint32 count2;
if (!_GetNodeAncestors(node1, ancestors1, &count1)
|| !_GetNodeAncestors(node2, ancestors2, &count2)) {
return false;
}
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;
}
}
*_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();
}
return false;
}
status_t
FUSEVolume::_BuildPath(FUSENode* dir, const char* entryName, char* path,
size_t& pathLen)
{
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);
}
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) {
strcpy(path, "/");
pathLen = 1;
return B_OK;
}
FUSEEntry* entry = node->entries.Head();
if (entry == NULL)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
return _BuildPath(entry->parent, entry->name, path, pathLen);
}
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);
}
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);
}
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> locker(fLock);
size_t entryLen = 0;
ino_t dirID = buffer->directory->id;
FUSEEntry* entry;
if (strcmp(name, ".") == 0) {
nodeID = dirID;
type = S_IFDIR;
} else if (strcmp(name, "..") == 0) {
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) {
FUSENode* node = NULL;
if (fUseNodeIDs)
node = fNodes.Lookup(nodeID);
else
nodeID = _GenerateNodeID();
if (node == NULL) {
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();
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 {
node->refCount++;
}
entry = FUSEEntry::Create(buffer->directory, name, node);
if (entry == NULL) {
_PutNode(node);
buffer->error = B_NO_MEMORY;
return 1;
}
buffer->directory->refCount++;
fEntries.Insert(entry);
node->entries.Add(entry);
} else {
nodeID = entry->node->id;
type = entry->node->type;
}
dirent* dirEntry = (dirent*)(buf);
dirEntry->d_dev = fID;
dirEntry->d_ino = nodeID;
strcpy(dirEntry->d_name, name);
entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1;
entryLen = ROUNDUP(entryLen, 8);
dirEntry->d_reclen = entryLen;
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> locker(fLock);
size_t entryLen = 0;
if (offset != 0) {
if (buffer->entriesRead == buffer->maxEntries)
return 1;
entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1;
if (buffer->usedSize + entryLen > buffer->bufferSize)
return 1;
}
ino_t dirID = buffer->directory->id;
FUSEEntry* entry;
if (strcmp(name, ".") == 0) {
nodeID = dirID;
type = S_IFDIR;
} else if (strcmp(name, "..") == 0) {
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) {
FUSENode* node = NULL;
if (fUseNodeIDs)
node = fNodes.Lookup(nodeID);
else
nodeID = _GenerateNodeID();
if (node == NULL) {
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();
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 {
node->refCount++;
}
entry = FUSEEntry::Create(buffer->directory, name, node);
if (entry == NULL) {
_PutNode(node);
buffer->error = B_NO_MEMORY;
return 1;
}
buffer->directory->refCount++;
fEntries.Insert(entry);
node->entries.Add(entry);
} else {
nodeID = entry->node->id;
type = entry->node->type;
}
if (offset == 0) {
if (buffer->cookie->entryCache == NULL) {
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 {
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) {
entryLen = ROUNDUP(entryLen, 8);
entryLen = std::min(entryLen,
buffer->bufferSize - buffer->usedSize);
}
dirEntry->d_reclen = entryLen;
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> 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;
}