userlandfs: Support mapping FUSE files to memory

- Create a file cache for FUSE mounted files on open to allow
memory mapping these files unless direct_io is specified.
- Implement DoIO for FUSE mounted files.

This patch enables files mounted through FUSE filesystems such as
sshfs or vmhgfs to be `mmap`ed, allowing remotely cross-compiled
executables to be directly run on Haiku.

Change-Id: I364b8225eae5b1d586280c2b3301fb661581caed
Reviewed-on: https://review.haiku-os.org/c/haiku/+/6277
Tested-by: Automation <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Trung Nguyen
2023-03-31 13:42:17 +00:00
committed by waddlesplash
parent bcbe43f8d3
commit 55f3abb4c9
4 changed files with 207 additions and 44 deletions
@@ -82,6 +82,7 @@ struct FUSENode : RWLockable {
FUSEEntryList entries;
int type;
int32 refCount;
int32 cacheCount;
bool dirty;
FUSENode* hashLink;
@@ -90,6 +91,7 @@ struct FUSENode : RWLockable {
id(id),
type(type),
refCount(1),
cacheCount(0),
dirty(false)
{
}
@@ -432,6 +432,10 @@ FUSEFileSystem::_InitCapabilities()
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_VNODE, true);
// emulated
// asynchronous I/O
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_IO, true);
// emulated
// index directory & index operations
// missing: FS_VOLUME_CAPABILITY_OPEN_INDEX_DIR
// missing: FS_VOLUME_CAPABILITY_CLOSE_INDEX_DIR
@@ -14,9 +14,11 @@
#include <NodeMonitor.h>
#include <AutoDeleter.h>
#include <StackOrHeapArray.h>
#include "FUSELowLevel.h"
#include "../IORequestInfo.h"
#include "../kernel_emu.h"
#include "../RequestThread.h"
@@ -894,7 +896,7 @@ FUSEVolume::ReadFSInfo(fs_info* info)
// from statfs and make reasonable guesses for the rest of them.
struct statvfs st;
int fuseError;
if (fOps != NULL) {
fuseError = fuse_ll_statfs(fOps, FUSE_ROOT_ID, &st);
} else {
@@ -1011,6 +1013,94 @@ FUSEVolume::RemoveVNode(void* node, bool reenter)
}
// #pragma mark - asynchronous I/O
status_t
FUSEVolume::DoIO(void* _node, void* _cookie, const IORequestInfo& requestInfo)
{
FUSENode* node = (FUSENode*)_node;
FileCookie* cookie = (FileCookie*)_cookie;
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
if (!S_ISREG(node->type))
RETURN_ERROR(B_BAD_VALUE);
BStackOrHeapArray<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);
}
if (error == B_OK) {
size_t bytesTransferred = 0;
while (bytesTransferred < requestInfo.length) {
size_t bytes = requestInfo.length - bytesTransferred;
error = _InternalIO(node, cookie, path, requestInfo.offset + bytesTransferred,
buffer + bytesTransferred, bytes, requestInfo.isWrite);
if (error == B_OK)
bytesTransferred += bytes;
else
break;
}
}
if (error == B_OK && !requestInfo.isWrite) {
error = UserlandFS::KernelEmu::write_to_io_request(GetID(), requestInfo.id, buffer,
requestInfo.length);
}
UserlandFS::KernelEmu::notify_io_request(GetID(), requestInfo.id, error);
if (cookie == &alternativeCookie) {
if (fOps != NULL)
fuse_ll_release(fOps, node->id, cookie);
else
fuse_fs_release(fFS, path, cookie);
}
if (error != B_OK)
RETURN_ERROR(error);
return B_OK;
}
// #pragma mark - nodes
@@ -1718,6 +1808,39 @@ FUSEVolume::Open(void* _node, int openMode, void** _cookie)
NULL);
}
if (S_ISREG(node->type)) {
if (cookie->direct_io || llCookie.direct_io) {
if (node->cacheCount > 0) {
// In some very rare cases, for the same node, the first `open`
// indicates that caching is allowed (by not setting `direct_io`),
// but a subsequent `open` on the same node indicates that it is
// NOT allowed (by setting `direct_io` to 1).
debugger("FUSEVolume::Open(): inconsistent direct_io flags!");
UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id);
node->cacheCount = 0;
}
} else {
if (node->cacheCount == 0) {
struct stat st;
if (fOps != NULL) {
fuseError = fuse_ll_getattr(fOps, node->id, &st);
} else {
fuseError = fuse_fs_getattr(fFS, path, &st);
}
if (fuseError != 0) {
RETURN_ERROR(fuseError);
}
status_t error = UserlandFS::KernelEmu::file_cache_create(GetID(), node->id, st.st_size);
if (error != B_OK) {
RETURN_ERROR(error);
}
}
// Increment cacheCount by extra 1 if the cache is kept to prevent
// the cache from being deleted at close().
node->cacheCount += 1 + cookie->keep_cache + llCookie.keep_cache;
}
}
cookieDeleter.Detach();
*_cookie = cookie;
@@ -1760,6 +1883,13 @@ FUSEVolume::Close(void* _node, void* _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;
}
@@ -1814,36 +1944,23 @@ FUSEVolume::Read(void* _node, void* _cookie, off_t pos, void* buffer,
RWLockableReadLocker cookieLocker(this, cookie);
*_bytesRead = 0;
// lock the directory
NodeReadLocker nodeLocker(this, node, true);
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
int bytesRead;
*_bytesRead = bufferSize;
status_t error = B_OK;
if (fOps != NULL) {
bytesRead = fuse_ll_read(fOps, node->id, (char*)buffer, bufferSize, pos, cookie);
} else {
AutoLocker<Locker> locker(fLock);
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);
// 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);
if (error != B_OK)
RETURN_ERROR(error);
locker.Unlock();
// read the file
bytesRead = fuse_fs_read(fFS, path, (char*)buffer, bufferSize, pos, cookie);
}
if (bytesRead < 0)
return bytesRead;
*_bytesRead = bytesRead;
return B_OK;
}
@@ -1864,27 +1981,17 @@ FUSEVolume::Write(void* _node, void* _cookie, off_t pos, const void* buffer,
if (nodeLocker.Status() != B_OK)
RETURN_ERROR(nodeLocker.Status());
int bytesWritten;
if (fOps != NULL) {
bytesWritten = fuse_ll_write(fOps, node->id, (const char*)buffer, bufferSize, pos, cookie);
} else {
AutoLocker<Locker> locker(fLock);
*_bytesWritten = bufferSize;
status_t error = B_OK;
// 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);
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);
locker.Unlock();
// write the file
bytesWritten = fuse_fs_write(fFS, path, (const char*)buffer, bufferSize,
pos, cookie);
}
if (bytesWritten < 0)
return bytesWritten;
if (error != B_OK)
RETURN_ERROR(error);
// mark the node dirty
AutoLocker<Locker> locker(fLock);
@@ -1898,7 +2005,6 @@ FUSEVolume::Write(void* _node, void* _cookie, off_t pos, const void* buffer,
// TODO: Avoid message flooding -- use a timeout and set the
// B_STAT_INTERIM_UPDATE flag.
*_bytesWritten = bytesWritten;
return B_OK;
}
@@ -2713,6 +2819,8 @@ 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;
}
}
@@ -3499,3 +3607,46 @@ FUSEVolume::_AddReadDirEntry(ReadDirBuffer* buffer, const char* name,
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;
}
@@ -50,6 +50,10 @@ public:
virtual status_t WriteVNode(void* node, bool reenter);
virtual status_t RemoveVNode(void* node, bool reenter);
// asynchronous I/O
virtual status_t DoIO(void* node, void* cookie,
const IORequestInfo& requestInfo);
// nodes
virtual status_t SetFlags(void* node, void* cookie,
int flags);
@@ -207,7 +211,9 @@ private:
ino_t nodeID, off_t offset);
int _AddReadDirEntry(ReadDirBuffer* buffer,
const char* name, int type, ino_t nodeID,
off_t offset);
off_t offset);
status_t _InternalIO(FUSENode* node, FileCookie* cookie, const char* path,
off_t pos, char* buffer, size_t& bytes, bool isWrite);
private:
RWLockManager fLockManager;