* Added support for the zero offset method for the readdir() hook's filler

callback.
* Added support for the deprecated getdir() hook.
* Fixed various bugs.

Listing the root directory with sshfs does now work. For subdirectories
stat()ing the entries seems to fail, which might be due to the yet missing
open() support.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29623 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2009-03-20 16:13:12 +00:00
parent b4d95b2afa
commit 8d0f474ca1
3 changed files with 318 additions and 78 deletions
@@ -422,12 +422,14 @@ FUSEFileSystem::_InitCapabilities()
// // directory operations // // directory operations
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fFS->ops.mkdir); // fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fFS->ops.mkdir);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fFS->ops.rmdir); // fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fFS->ops.rmdir);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, fFS->ops.opendir); bool readDirSupport = fFS->ops.opendir != NULL || fFS->ops.readdir != NULL
|| fFS->ops.getdir;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_DIR, true); // fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_DIR, true);
// not needed // not needed
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, true); fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, fFS->ops.readdir); fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, fFS->ops.readdir); fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);
// //
// // attribute directory operations // // attribute directory operations
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, // fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR,
@@ -25,12 +25,133 @@ from_fuse_error(int error)
} }
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 { struct FUSEVolume::DirCookie : fuse_file_info {
union {
off_t currentEntryOffset; off_t currentEntryOffset;
uint32 currentEntryIndex;
};
DirEntryCache* entryCache;
bool getdirInterface;
DirCookie() DirCookie()
: :
currentEntryOffset(0) currentEntryOffset(0),
entryCache(NULL),
getdirInterface(false)
{ {
flags = 0; flags = 0;
fh_old = 0; fh_old = 0;
@@ -41,6 +162,11 @@ struct FUSEVolume::DirCookie : fuse_file_info {
fh = 0; fh = 0;
lock_owner = 0; lock_owner = 0;
} }
~DirCookie()
{
delete entryCache;
}
}; };
@@ -53,12 +179,12 @@ struct FUSEVolume::ReadDirBuffer {
size_t usedSize; size_t usedSize;
uint32 entriesRead; uint32 entriesRead;
uint32 maxEntries; uint32 maxEntries;
off_t lastOffset;
status_t error; status_t error;
ReadDirBuffer(FUSEVolume* volume, FUSENode* directory, DirCookie* cookie, ReadDirBuffer(FUSEVolume* volume, FUSENode* directory, DirCookie* cookie,
void* buffer, size_t bufferSize, uint32 maxEntries) void* buffer, size_t bufferSize, uint32 maxEntries)
: :
volume(volume),
directory(directory), directory(directory),
cookie(cookie), cookie(cookie),
buffer(buffer), buffer(buffer),
@@ -66,7 +192,6 @@ struct FUSEVolume::ReadDirBuffer {
usedSize(0), usedSize(0),
entriesRead(0), entriesRead(0),
maxEntries(maxEntries), maxEntries(maxEntries),
lastOffset(0),
error(B_OK) error(B_OK)
{ {
} }
@@ -274,6 +399,9 @@ FUSEVolume::ReadVNode(ino_t vnid, bool reenter, void** _node, int* type,
RETURN_ERROR(B_ENTRY_NOT_FOUND); RETURN_ERROR(B_ENTRY_NOT_FOUND);
node->refCount++; node->refCount++;
*_node = node;
*type = node->type;
*flags = 0;
*_capabilities = _FileSystem()->GetNodeCapabilities(); *_capabilities = _FileSystem()->GetNodeCapabilities();
return B_OK; return B_OK;
@@ -563,10 +691,16 @@ PRINT(("FUSEVolume::OpenDir(%p (%lld), %p)\n", node, node->id, _cookie));
locker.Unlock(); 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 // open the dir
int fuseError = fuse_fs_opendir(fFS, path, cookie); int fuseError = fuse_fs_opendir(fFS, path, cookie);
if (fuseError != 0) if (fuseError != 0)
return from_fuse_error(fuseError); return from_fuse_error(fuseError);
}
cookieDeleter.Detach(); cookieDeleter.Detach();
*_cookie = cookie; *_cookie = cookie;
@@ -590,6 +724,9 @@ FUSEVolume::FreeDirCookie(void* _node, void* _cookie)
ObjectDeleter<DirCookie> cookieDeleter(cookie); ObjectDeleter<DirCookie> cookieDeleter(cookie);
if (cookie->getdirInterface)
return B_OK;
AutoLocker<Locker> locker(fLock); AutoLocker<Locker> locker(fLock);
// get a path for the node // get a path for the node
@@ -612,14 +749,25 @@ FUSEVolume::FreeDirCookie(void* _node, void* _cookie)
status_t status_t
FUSEVolume::ReadDir(void* _node, void* _cookie, void* buffer, size_t bufferSize, FUSEVolume::ReadDir(void* _node, void* _cookie, void* buffer, size_t bufferSize,
uint32 count, uint32* countRead) uint32 count, uint32* _countRead)
{ {
PRINT(("FUSEVolume::ReadDir(%p, %p, %p, %lu, %ld)\n", _node, _cookie, buffer,
bufferSize, count));
*_countRead = 0;
FUSENode* node = (FUSENode*)_node; FUSENode* node = (FUSENode*)_node;
DirCookie* cookie = (DirCookie*)_cookie; DirCookie* cookie = (DirCookie*)_cookie;
uint32 countRead = 0;
status_t readDirError = B_OK;
AutoLocker<Locker> locker(fLock); AutoLocker<Locker> locker(fLock);
ReadDirBuffer readDirBuffer(this, node, cookie, buffer, bufferSize, count); if (cookie->entryCache == NULL) {
// We don't have an entry cache (yet), so we need to ask the client
// file system to read the directory.
ReadDirBuffer readDirBuffer(this, node, cookie, buffer, bufferSize,
count);
// get a path for the node // get a path for the node
char path[B_PATH_NAME_LENGTH]; char path[B_PATH_NAME_LENGTH];
@@ -633,26 +781,61 @@ FUSEVolume::ReadDir(void* _node, void* _cookie, void* buffer, size_t bufferSize,
locker.Unlock(); locker.Unlock();
// read the dir // read the dir
int fuseError = fuse_fs_readdir(fFS, path, &readDirBuffer, 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); &_AddReadDirEntry, offset, cookie);
}
if (fuseError != 0) if (fuseError != 0)
return from_fuse_error(fuseError); return from_fuse_error(fuseError);
locker.Lock(); locker.Lock();
// everything went fine countRead = readDirBuffer.entriesRead;
cookie->currentEntryOffset = readDirBuffer.lastOffset; readDirError = readDirBuffer.error;
*countRead = readDirBuffer.entriesRead; }
return B_OK; 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 status_t
FUSEVolume::RewindDir(void* node, void* cookie) FUSEVolume::RewindDir(void* _node, void* _cookie)
{ {
// TODO: Implement! PRINT(("FUSEVolume::RewindDir(%p, %p)\n", _node, _cookie));
return B_UNSUPPORTED; DirCookie* cookie = (DirCookie*)_cookie;
AutoLocker<Locker> locker(fLock);
if (cookie->getdirInterface) {
delete cookie->entryCache;
cookie->entryCache = NULL;
cookie->currentEntryIndex = 0;
} else {
cookie->currentEntryOffset = 0;
}
return B_OK;
} }
@@ -699,6 +882,8 @@ FUSEVolume::_GetNode(FUSENode* dir, const char* entryName, FUSENode** _node)
if (privateNode != node) { if (privateNode != node) {
// weird, the node changed! // weird, the node changed!
ERROR(("FUSEVolume::_GetNode(): cookie for node %lld changed: "
"expected: %p, got: %p\n", nodeID, node, privateNode));
UserlandFS::KernelEmu::put_vnode(fID, nodeID); UserlandFS::KernelEmu::put_vnode(fID, nodeID);
_PutNode(node); _PutNode(node);
continue; continue;
@@ -884,48 +1069,75 @@ FUSEVolume::_AddReadDirEntry(void* _buffer, const char* name,
const struct stat* st, off_t offset) const struct stat* st, off_t offset)
{ {
ReadDirBuffer* buffer = (ReadDirBuffer*)_buffer; ReadDirBuffer* buffer = (ReadDirBuffer*)_buffer;
FUSEVolume* self = buffer->volume; return buffer->volume->_AddReadDirEntry(buffer, name, st->st_mode & S_IFMT,
st->st_ino, offset);
}
AutoLocker<Locker> _(self->fLock);
if (offset == 0) { /*static*/ int
ERROR(("FUSEVolume::_AddReadDirEntry(): Old interface not " FUSEVolume::_AddReadDirEntryGetDir(fuse_dirh_t handle, const char* name,
"supported!\n")); int type, ino_t nodeID)
buffer->error = B_ERROR; {
return 1; ReadDirBuffer* buffer = (ReadDirBuffer*)handle;
} return buffer->volume->_AddReadDirEntry(buffer, name, type & S_IFMT, nodeID,
0);
}
int
FUSEVolume::_AddReadDirEntry(ReadDirBuffer* buffer, const char* name, int type,
ino_t nodeID, off_t offset)
{
PRINT(("FUSEVolume::_AddReadDirEntry(%p, \"%s\", %#x, %lld, %lld\n", buffer,
name, type, nodeID, offset));
AutoLocker<Locker> _(fLock);
size_t entryLen;
if (offset != 0) {
// does the caller want more entries?
if (buffer->entriesRead == buffer->maxEntries) if (buffer->entriesRead == buffer->maxEntries)
return 1; return 1;
// compute the entry length and check whether the entry still fits // compute the entry length and check whether the entry still fits
size_t nameLen = strlen(name); entryLen = sizeof(dirent) + strlen(name);
dirent* dirEntry = (dirent*)((uint8*)buffer->buffer + buffer->usedSize);
size_t entryLen = dirEntry->d_name + nameLen + 1 - (char*)dirEntry;
if (buffer->usedSize + entryLen > buffer->bufferSize) if (buffer->usedSize + entryLen > buffer->bufferSize)
return 1; return 1;
}
// create a node and an entry, if necessary // create a node and an entry, if necessary
FUSEEntry* entry = self->fEntries.Lookup( ino_t dirID = buffer->directory->id;
FUSEEntryRef(buffer->directory->id, name)); FUSEEntry* entry;
if (entry == NULL) { if (strcmp(name, ".") == 0) {
// current dir entry
nodeID = dirID;
} else if (strcmp(name, "..") == 0) {
// parent dir entry
FUSEEntry* parentEntry = buffer->directory->entries.Head();
if (parentEntry == NULL) {
ERROR(("FUSEVolume::_AddReadDirEntry(): dir %lld has no entry!\n",
dirID));
return 0;
}
nodeID = parentEntry->parent->id;
} else if ((entry = fEntries.Lookup(FUSEEntryRef(dirID, name))) == NULL) {
// get the node // get the node
ino_t nodeID = st->st_ino;
FUSENode* node = NULL; FUSENode* node = NULL;
if (self->fUseNodeIDs) if (fUseNodeIDs)
node = self->fNodes.Lookup(st->st_ino); node = fNodes.Lookup(nodeID);
else else
nodeID = self->_GenerateNodeID(); nodeID = _GenerateNodeID();
if (node == NULL) { if (node == NULL) {
// no node yet -- create one // no node yet -- create one
node = new(std::nothrow) FUSENode(nodeID, st->st_mode & S_IFMT); node = new(std::nothrow) FUSENode(nodeID, type);
if (node == NULL) { if (node == NULL) {
buffer->error = B_NO_MEMORY; buffer->error = B_NO_MEMORY;
return 1; return 1;
} }
PRINT((" -> create node: %p, id: %lld\n", node, nodeID));
self->fNodes.Insert(node); fNodes.Insert(node);
} else { } else {
// get a node reference for the entry // get a node reference for the entry
node->refCount++; node->refCount++;
@@ -934,26 +1146,45 @@ FUSEVolume::_AddReadDirEntry(void* _buffer, const char* name,
// create the entry // create the entry
entry = FUSEEntry::Create(buffer->directory, name, node); entry = FUSEEntry::Create(buffer->directory, name, node);
if (entry == NULL) { if (entry == NULL) {
self->_PutNode(node); _PutNode(node);
buffer->error = B_NO_MEMORY; buffer->error = B_NO_MEMORY;
return 1; return 1;
} }
self->fEntries.Insert(entry); fEntries.Insert(entry);
node->entries.Add(entry); node->entries.Add(entry);
} else { } else {
// TODO: Check whether the node's ID matches the one we got! // TODO: Check whether the node's ID matches the one we got!
} }
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 // fill in the dirent
dirEntry->d_dev = self->fID; dirent* dirEntry = (dirent*)((uint8*)buffer->buffer + buffer->usedSize);
dirEntry->d_ino = entry->node->id; dirEntry->d_dev = fID;
dirEntry->d_ino = nodeID;
strcpy(dirEntry->d_name, name); strcpy(dirEntry->d_name, name);
if (buffer->entriesRead + 1 < buffer->maxEntries) { if (buffer->entriesRead + 1 < buffer->maxEntries) {
// align the entry length, so the next dirent will be aligned // align the entry length, so the next dirent will be aligned
entryLen = (entryLen + 7) / 8 * 8; entryLen = (entryLen + 7) / 8 * 8;
entryLen = std::min(entryLen, buffer->bufferSize - buffer->usedSize); entryLen = std::min(entryLen,
buffer->bufferSize - buffer->usedSize);
} }
dirEntry->d_reclen = entryLen; dirEntry->d_reclen = entryLen;
@@ -961,7 +1192,8 @@ FUSEVolume::_AddReadDirEntry(void* _buffer, const char* name,
// update the buffer // update the buffer
buffer->usedSize += entryLen; buffer->usedSize += entryLen;
buffer->entriesRead++; buffer->entriesRead++;
buffer->lastOffset = offset; buffer->cookie->currentEntryOffset = offset;
}
return 0; return 0;
} }
@@ -119,6 +119,7 @@ public:
virtual status_t RewindDir(void* node, void* cookie); virtual status_t RewindDir(void* node, void* cookie);
private: private:
struct DirEntryCache;
struct DirCookie; struct DirCookie;
struct ReadDirBuffer; struct ReadDirBuffer;
@@ -141,6 +142,11 @@ private:
static int _AddReadDirEntry(void* buffer, const char* name, static int _AddReadDirEntry(void* buffer, const char* name,
const struct stat* st, off_t offset); const struct stat* st, off_t offset);
static int _AddReadDirEntryGetDir(fuse_dirh_t handle,
const char* name, int type, ino_t nodeID);
int _AddReadDirEntry(ReadDirBuffer* buffer,
const char* name, int type, ino_t nodeID,
off_t offset);
private: private:
Locker fLock; Locker fLock;