* More work on the file system initialization. Mounting/unmounting works now
with sshfs.
* Added basic node and entry management, which we need, since the FUSE
interface works with paths only, while our VFS plays with node IDs and
node cookies.
* Implemented most of the mandatory hooks (vnode operations, lookup, read stat,
open/close/read dir). I was hoping to get directory listings with sshfs now,
but as I had to find out, it implements the deprecated getdir() while we
only support the new {open,read,release}dir() interface yet.
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29619 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
|
#include "fuse_fs.h"
|
||||||
#include "FUSEVolume.h"
|
#include "FUSEVolume.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -126,6 +127,11 @@ FUSEFileSystem::FUSEFileSystem(const char* fsName,
|
|||||||
fInitParameters(NULL),
|
fInitParameters(NULL),
|
||||||
fFS(NULL)
|
fFS(NULL)
|
||||||
{
|
{
|
||||||
|
fClientFSType = CLIENT_FS_FUSE;
|
||||||
|
|
||||||
|
// FS capabilities
|
||||||
|
fCapabilities.ClearAll();
|
||||||
|
fCapabilities.Set(FS_CAPABILITY_MOUNT, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -155,6 +161,12 @@ printf("FUSEFileSystem::CreateVolume()\n");
|
|||||||
if (volume == NULL)
|
if (volume == NULL)
|
||||||
return B_NO_MEMORY;
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
status_t error = volume->Init();
|
||||||
|
if (error != B_OK) {
|
||||||
|
delete volume;
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
*_volume = volume;
|
*_volume = volume;
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
@@ -171,6 +183,7 @@ FUSEFileSystem::DeleteVolume(Volume* volume)
|
|||||||
status_t
|
status_t
|
||||||
FUSEFileSystem::InitClientFS(const char* parameters)
|
FUSEFileSystem::InitClientFS(const char* parameters)
|
||||||
{
|
{
|
||||||
|
PRINT(("FUSEFileSystem::InitClientFS()\n"));
|
||||||
// create the semaphores we need
|
// create the semaphores we need
|
||||||
fInitSemaphore = create_sem(0, "FUSE init sem");
|
fInitSemaphore = create_sem(0, "FUSE init sem");
|
||||||
if (fInitSemaphore < 0)
|
if (fInitSemaphore < 0)
|
||||||
@@ -193,9 +206,11 @@ FUSEFileSystem::InitClientFS(const char* parameters)
|
|||||||
resume_thread(fInitThread);
|
resume_thread(fInitThread);
|
||||||
|
|
||||||
// wait for the initialization to finish
|
// wait for the initialization to finish
|
||||||
|
PRINT((" waiting for init thread...\n"));
|
||||||
while (acquire_sem(fInitSemaphore) == B_INTERRUPTED) {
|
while (acquire_sem(fInitSemaphore) == B_INTERRUPTED) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PRINT((" waiting for init thread done\n"));
|
||||||
fInitSemaphore = -1;
|
fInitSemaphore = -1;
|
||||||
|
|
||||||
if (fInitStatus > 0)
|
if (fInitStatus > 0)
|
||||||
@@ -215,6 +230,9 @@ FUSEFileSystem::ExitClientFS(status_t status)
|
|||||||
fExitStatus = status;
|
fExitStatus = status;
|
||||||
if (fExitSemaphore >= 0)
|
if (fExitSemaphore >= 0)
|
||||||
delete_sem(fExitSemaphore);
|
delete_sem(fExitSemaphore);
|
||||||
|
|
||||||
|
if (fInitThread >= 0)
|
||||||
|
wait_for_thread(fInitThread, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -222,18 +240,22 @@ status_t
|
|||||||
FUSEFileSystem::FinishInitClientFS(const fuse_operations* ops, size_t opSize,
|
FUSEFileSystem::FinishInitClientFS(const fuse_operations* ops, size_t opSize,
|
||||||
void* userData)
|
void* userData)
|
||||||
{
|
{
|
||||||
|
PRINT(("FUSEFileSystem::FinishInitClientFS()\n"));
|
||||||
fExitStatus = B_ERROR;
|
fExitStatus = B_ERROR;
|
||||||
|
|
||||||
// do the initialization
|
// do the initialization
|
||||||
status_t error = _InitClientFS(ops, opSize, userData);
|
status_t error = _InitClientFS(ops, opSize, userData);
|
||||||
|
|
||||||
// notify the mount thread
|
// notify the mount thread
|
||||||
|
PRINT((" notifying mount thread\n"));
|
||||||
fInitStatus = error;
|
fInitStatus = error;
|
||||||
delete_sem(fInitSemaphore);
|
delete_sem(fInitSemaphore);
|
||||||
|
|
||||||
// loop until unmounting
|
// loop until unmounting
|
||||||
|
PRINT((" waiting for unmounting...\n"));
|
||||||
while (acquire_sem(fExitSemaphore) == B_INTERRUPTED) {
|
while (acquire_sem(fExitSemaphore) == B_INTERRUPTED) {
|
||||||
}
|
}
|
||||||
|
PRINT((" waiting for unmounting done\n"));
|
||||||
|
|
||||||
fExitSemaphore = -1;
|
fExitSemaphore = -1;
|
||||||
|
|
||||||
@@ -287,6 +309,12 @@ FUSEFileSystem::_InitClientFS(const fuse_operations* ops, size_t opSize,
|
|||||||
if (fFS == NULL)
|
if (fFS == NULL)
|
||||||
return B_ERROR;
|
return B_ERROR;
|
||||||
|
|
||||||
|
_InitCapabilities();
|
||||||
|
PRINT(("volume capabilities:\n"));
|
||||||
|
fVolumeCapabilities.Dump();
|
||||||
|
PRINT(("node capabilities:\n"));
|
||||||
|
fNodeCapabilities.Dump();
|
||||||
|
|
||||||
// init connection info
|
// init connection info
|
||||||
fConnectionInfo.proto_major = 0;
|
fConnectionInfo.proto_major = 0;
|
||||||
fConnectionInfo.proto_minor = 0;
|
fConnectionInfo.proto_minor = 0;
|
||||||
@@ -300,6 +328,138 @@ FUSEFileSystem::_InitClientFS(const fuse_operations* ops, size_t opSize,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
FUSEFileSystem::_InitCapabilities()
|
||||||
|
{
|
||||||
|
fVolumeCapabilities.ClearAll();
|
||||||
|
fNodeCapabilities.ClearAll();
|
||||||
|
|
||||||
|
// Volume operations
|
||||||
|
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_UNMOUNT, true);
|
||||||
|
|
||||||
|
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fFS->ops.statfs);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_WRITE_FS_INFO,
|
||||||
|
// fFS->ops.wfsstat);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fFS->ops.sync);
|
||||||
|
//
|
||||||
|
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_GET_VNODE, true);
|
||||||
|
// emulated
|
||||||
|
//
|
||||||
|
// // index directory & index operations
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_OPEN_INDEX_DIR,
|
||||||
|
// fFS->ops.open_indexdir);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_CLOSE_INDEX_DIR,
|
||||||
|
// fFS->ops.close_indexdir);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_FREE_INDEX_DIR_COOKIE,
|
||||||
|
// fFS->ops.free_indexdircookie);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_INDEX_DIR,
|
||||||
|
// fFS->ops.read_indexdir);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_REWIND_INDEX_DIR,
|
||||||
|
// fFS->ops.rewind_indexdir);
|
||||||
|
//
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_CREATE_INDEX,
|
||||||
|
// fFS->ops.create_index);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_REMOVE_INDEX,
|
||||||
|
// fFS->ops.remove_index);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_INDEX_STAT,
|
||||||
|
// fFS->ops.stat_index);
|
||||||
|
//
|
||||||
|
// // query operations
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_OPEN_QUERY,
|
||||||
|
// fFS->ops.open_query);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_CLOSE_QUERY,
|
||||||
|
// fFS->ops.close_query);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_FREE_QUERY_COOKIE,
|
||||||
|
// fFS->ops.free_querycookie);
|
||||||
|
// fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_QUERY,
|
||||||
|
// fFS->ops.read_query);
|
||||||
|
// // missing: FS_VOLUME_CAPABILITY_REWIND_QUERY,
|
||||||
|
//
|
||||||
|
// // vnode operations
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LOOKUP, true);
|
||||||
|
// emulated
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_GET_VNODE_NAME, true);
|
||||||
|
// emulated
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_PUT_VNODE, true);
|
||||||
|
// emulated
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_VNODE, true);
|
||||||
|
// emulated
|
||||||
|
|
||||||
|
// // VM file access
|
||||||
|
// // missing: FS_VNODE_CAPABILITY_CAN_PAGE,
|
||||||
|
// // missing: FS_VNODE_CAPABILITY_READ_PAGES,
|
||||||
|
// // missing: FS_VNODE_CAPABILITY_WRITE_PAGES,
|
||||||
|
//
|
||||||
|
// // cache file access
|
||||||
|
// // missing: FS_VNODE_CAPABILITY_GET_FILE_MAP,
|
||||||
|
//
|
||||||
|
// // common operations
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_IOCTL, fFS->ops.ioctl);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_SET_FLAGS, fFS->ops.setflags);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_SELECT, fFS->ops.select);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_DESELECT, fFS->ops.deselect);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FSYNC, fFS->ops.fsync);
|
||||||
|
//
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_SYMLINK, fFS->ops.readlink);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_SYMLINK, fFS->ops.symlink);
|
||||||
|
//
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LINK, fFS->ops.link);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_UNLINK, fFS->ops.unlink);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME, fFS->ops.rename);
|
||||||
|
//
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_ACCESS, fFS->ops.access);
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_STAT, fFS->ops.getattr);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_STAT, fFS->ops.wstat);
|
||||||
|
//
|
||||||
|
// // file operations
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE, fFS->ops.create);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN, fFS->ops.open);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE, fFS->ops.close);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fFS->ops.free_cookie);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ, fFS->ops.read);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE, fFS->ops.write);
|
||||||
|
//
|
||||||
|
// // directory operations
|
||||||
|
// 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_OPEN_DIR, fFS->ops.opendir);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_DIR, true);
|
||||||
|
// not needed
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, true);
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, fFS->ops.readdir);
|
||||||
|
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, fFS->ops.readdir);
|
||||||
|
//
|
||||||
|
// // attribute directory operations
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR,
|
||||||
|
// fFS->ops.open_attrdir);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR,
|
||||||
|
// fFS->ops.close_attrdir);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE,
|
||||||
|
// fFS->ops.free_attrdircookie);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR,
|
||||||
|
// fFS->ops.read_attrdir);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR,
|
||||||
|
// fFS->ops.rewind_attrdir);
|
||||||
|
//
|
||||||
|
// // attribute operations
|
||||||
|
// // we emulate open_attr() and free_attr_dir_cookie() if either read_attr()
|
||||||
|
// // or write_attr() is present
|
||||||
|
// bool hasAttributes = (fFS->ops.read_attr || fFS->ops.write_attr);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_ATTR, hasAttributes);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR, hasAttributes);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_ATTR, false);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE, hasAttributes);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fFS->ops.read_attr);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fFS->ops.write_attr);
|
||||||
|
//
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT,
|
||||||
|
// fFS->ops.stat_attr);
|
||||||
|
// // missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fFS->ops.rename_attr);
|
||||||
|
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_ATTR, fFS->ops.remove_attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark - bootstrapping
|
// #pragma mark - bootstrapping
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ public:
|
|||||||
|
|
||||||
static FUSEFileSystem* GetInstance();
|
static FUSEFileSystem* GetInstance();
|
||||||
|
|
||||||
|
void GetVolumeCapabilities(
|
||||||
|
FSVolumeCapabilities& capabilities) const
|
||||||
|
{ capabilities = fVolumeCapabilities; }
|
||||||
|
void GetNodeCapabilities(
|
||||||
|
FSVNodeCapabilities& capabilities) const
|
||||||
|
{ capabilities = fNodeCapabilities; }
|
||||||
|
const FSVNodeCapabilities& GetNodeCapabilities() const
|
||||||
|
{ return fNodeCapabilities; }
|
||||||
|
|
||||||
|
fuse_fs* GetFS() const { return fFS; }
|
||||||
|
|
||||||
virtual status_t CreateVolume(Volume** _volume, dev_t id);
|
virtual status_t CreateVolume(Volume** _volume, dev_t id);
|
||||||
virtual status_t DeleteVolume(Volume* volume);
|
virtual status_t DeleteVolume(Volume* volume);
|
||||||
|
|
||||||
@@ -40,6 +51,8 @@ private:
|
|||||||
status_t _InitClientFS(const fuse_operations* ops,
|
status_t _InitClientFS(const fuse_operations* ops,
|
||||||
size_t opSize, void* userData);
|
size_t opSize, void* userData);
|
||||||
|
|
||||||
|
void _InitCapabilities();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int (*fMainFunction)(int, const char* const*);
|
int (*fMainFunction)(int, const char* const*);
|
||||||
thread_id fInitThread;
|
thread_id fInitThread;
|
||||||
@@ -51,6 +64,9 @@ private:
|
|||||||
const char* fInitParameters;
|
const char* fInitParameters;
|
||||||
fuse_fs* fFS;
|
fuse_fs* fFS;
|
||||||
fuse_conn_info fConnectionInfo;
|
fuse_conn_info fConnectionInfo;
|
||||||
|
|
||||||
|
FSVolumeCapabilities fVolumeCapabilities;
|
||||||
|
FSVNodeCapabilities fNodeCapabilities;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UserlandFS
|
} // namespace UserlandFS
|
||||||
|
|||||||
@@ -5,8 +5,73 @@
|
|||||||
|
|
||||||
#include "FUSEVolume.h"
|
#include "FUSEVolume.h"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <fs_info.h>
|
||||||
|
|
||||||
|
#include <AutoDeleter.h>
|
||||||
|
|
||||||
#include "FUSEFileSystem.h"
|
#include "FUSEFileSystem.h"
|
||||||
|
|
||||||
|
#include "../kernel_emu.h"
|
||||||
|
|
||||||
|
|
||||||
|
static inline status_t
|
||||||
|
from_fuse_error(int error)
|
||||||
|
{
|
||||||
|
return error < 0 ? error : -error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct FUSEVolume::DirCookie : fuse_file_info {
|
||||||
|
off_t currentEntryOffset;
|
||||||
|
|
||||||
|
DirCookie()
|
||||||
|
:
|
||||||
|
currentEntryOffset(0)
|
||||||
|
{
|
||||||
|
flags = 0;
|
||||||
|
fh_old = 0;
|
||||||
|
writepage = 0;
|
||||||
|
direct_io = 0;
|
||||||
|
keep_cache = 0;
|
||||||
|
flush = 0;
|
||||||
|
fh = 0;
|
||||||
|
lock_owner = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct FUSEVolume::ReadDirBuffer {
|
||||||
|
FUSEVolume* volume;
|
||||||
|
FUSENode* directory;
|
||||||
|
DirCookie* cookie;
|
||||||
|
void* buffer;
|
||||||
|
size_t bufferSize;
|
||||||
|
size_t usedSize;
|
||||||
|
uint32 entriesRead;
|
||||||
|
uint32 maxEntries;
|
||||||
|
off_t lastOffset;
|
||||||
|
status_t error;
|
||||||
|
|
||||||
|
ReadDirBuffer(FUSEVolume* volume, FUSENode* directory, DirCookie* cookie,
|
||||||
|
void* buffer, size_t bufferSize, uint32 maxEntries)
|
||||||
|
:
|
||||||
|
directory(directory),
|
||||||
|
cookie(cookie),
|
||||||
|
buffer(buffer),
|
||||||
|
bufferSize(bufferSize),
|
||||||
|
usedSize(0),
|
||||||
|
entriesRead(0),
|
||||||
|
maxEntries(maxEntries),
|
||||||
|
lastOffset(0),
|
||||||
|
error(B_OK)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
inline FUSEFileSystem*
|
inline FUSEFileSystem*
|
||||||
FUSEVolume::_FileSystem() const
|
FUSEVolume::_FileSystem() const
|
||||||
@@ -17,7 +82,11 @@ FUSEVolume::_FileSystem() const
|
|||||||
|
|
||||||
FUSEVolume::FUSEVolume(FUSEFileSystem* fileSystem, dev_t id)
|
FUSEVolume::FUSEVolume(FUSEFileSystem* fileSystem, dev_t id)
|
||||||
:
|
:
|
||||||
Volume(fileSystem, id)
|
Volume(fileSystem, id),
|
||||||
|
fFS(NULL),
|
||||||
|
fRootNode(NULL),
|
||||||
|
fNextNodeID(FUSE_ROOT_ID + 1),
|
||||||
|
fUseNodeIDs(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +96,27 @@ FUSEVolume::~FUSEVolume()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
FUSEVolume::Init()
|
||||||
|
{
|
||||||
|
// init entry and node tables
|
||||||
|
status_t 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
|
// #pragma mark - FS
|
||||||
|
|
||||||
|
|
||||||
@@ -39,9 +129,47 @@ printf("FUSEVolume::Mount()\n");
|
|||||||
if (error != B_OK)
|
if (error != B_OK)
|
||||||
RETURN_ERROR(error);
|
RETURN_ERROR(error);
|
||||||
|
|
||||||
_FileSystem()->ExitClientFS(B_UNSUPPORTED);
|
fFS = _FileSystem()->GetFS();
|
||||||
|
_FileSystem()->GetVolumeCapabilities(fCapabilities);
|
||||||
|
|
||||||
return B_UNSUPPORTED;
|
// get the root node
|
||||||
|
struct stat st;
|
||||||
|
int fuseError = fuse_fs_getattr(fFS, "/", &st);
|
||||||
|
if (fuseError != 0)
|
||||||
|
RETURN_ERROR(from_fuse_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->entries.Add(entry);
|
||||||
|
fRootNode = node;
|
||||||
|
|
||||||
|
// insert the node and the entry
|
||||||
|
fNodes.Insert(node);
|
||||||
|
fEntries.Insert(entry);
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -49,13 +177,16 @@ status_t
|
|||||||
FUSEVolume::Unmount()
|
FUSEVolume::Unmount()
|
||||||
{
|
{
|
||||||
printf("FUSEVolume::Unmount()\n");
|
printf("FUSEVolume::Unmount()\n");
|
||||||
return B_UNSUPPORTED;
|
_FileSystem()->ExitClientFS(B_OK);
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::Sync()
|
FUSEVolume::Sync()
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
|
// NOTE: There's no hook for sync'ing the whole FS.
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +194,32 @@ FUSEVolume::Sync()
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::ReadFSInfo(fs_info* info)
|
FUSEVolume::ReadFSInfo(fs_info* info)
|
||||||
{
|
{
|
||||||
|
if (fFS->ops.statfs == NULL)
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
|
|
||||||
|
struct statvfs st;
|
||||||
|
int fuseError = fuse_fs_statfs(fFS, "/", &st);
|
||||||
|
if (fuseError != 0)
|
||||||
|
return from_fuse_error(fuseError);
|
||||||
|
|
||||||
|
info->flags = ((st.f_fsid & ST_RDONLY) != 0 ? B_FS_IS_READONLY : 0)
|
||||||
|
| 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 = st.f_favail;
|
||||||
|
info->volume_name[0] = '\0'; // no way to get the name (if any)
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::WriteFSInfo(const struct fs_info* info, uint32 mask)
|
FUSEVolume::WriteFSInfo(const struct fs_info* info, uint32 mask)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,45 +228,76 @@ FUSEVolume::WriteFSInfo(const struct fs_info* info, uint32 mask)
|
|||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::Lookup(void* dir, const char* entryName, ino_t* vnid)
|
FUSEVolume::Lookup(void* _dir, const char* entryName, ino_t* vnid)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
FUSENode* dir = (FUSENode*)_dir;
|
||||||
|
|
||||||
|
// 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
|
status_t
|
||||||
FUSEVolume::GetVNodeType(void* node, int* type)
|
FUSEVolume::GetVNodeName(void* _node, char* buffer, size_t bufferSize)
|
||||||
{
|
{
|
||||||
return B_NOT_SUPPORTED;
|
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
|
status_t
|
||||||
FUSEVolume::GetVNodeName(void* node, char* buffer, size_t bufferSize)
|
FUSEVolume::ReadVNode(ino_t vnid, bool reenter, void** _node, int* type,
|
||||||
{
|
|
||||||
return B_NOT_SUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
FUSEVolume::ReadVNode(ino_t vnid, bool reenter, void** node, int* type,
|
|
||||||
uint32* flags, FSVNodeCapabilities* _capabilities)
|
uint32* flags, FSVNodeCapabilities* _capabilities)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
AutoLocker<Locker> _(fLock);
|
||||||
|
|
||||||
|
FUSENode* node = fNodes.Lookup(vnid);
|
||||||
|
if (node == NULL)
|
||||||
|
RETURN_ERROR(B_ENTRY_NOT_FOUND);
|
||||||
|
|
||||||
|
node->refCount++;
|
||||||
|
*_capabilities = _FileSystem()->GetNodeCapabilities();
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::WriteVNode(void* node, bool reenter)
|
FUSEVolume::WriteVNode(void* _node, bool reenter)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
FUSENode* node = (FUSENode*)_node;
|
||||||
|
|
||||||
|
AutoLocker<Locker> _(fLock);
|
||||||
|
|
||||||
|
_PutNode(node);
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::RemoveVNode(void* node, bool reenter)
|
FUSEVolume::RemoveVNode(void* node, bool reenter)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
// TODO: Implement for real!
|
||||||
|
return WriteVNode(node, reenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -126,6 +307,7 @@ FUSEVolume::RemoveVNode(void* node, bool reenter)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::DoIO(void* node, void* cookie, const IORequestInfo& requestInfo)
|
FUSEVolume::DoIO(void* node, void* cookie, const IORequestInfo& requestInfo)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +315,7 @@ FUSEVolume::DoIO(void* node, void* cookie, const IORequestInfo& requestInfo)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::CancelIO(void* node, void* cookie, int32 ioRequestID)
|
FUSEVolume::CancelIO(void* node, void* cookie, int32 ioRequestID)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +324,7 @@ status_t
|
|||||||
FUSEVolume::IterativeIOGetVecs(void* cookie, int32 requestID, off_t offset,
|
FUSEVolume::IterativeIOGetVecs(void* cookie, int32 requestID, off_t offset,
|
||||||
size_t size, struct file_io_vec* vecs, size_t* _count)
|
size_t size, struct file_io_vec* vecs, size_t* _count)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +333,7 @@ status_t
|
|||||||
FUSEVolume::IterativeIOFinished(void* cookie, int32 requestID, status_t status,
|
FUSEVolume::IterativeIOFinished(void* cookie, int32 requestID, status_t status,
|
||||||
bool partialTransfer, size_t bytesTransferred)
|
bool partialTransfer, size_t bytesTransferred)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +345,7 @@ status_t
|
|||||||
FUSEVolume::IOCtl(void* node, void* cookie, uint32 command, void *buffer,
|
FUSEVolume::IOCtl(void* node, void* cookie, uint32 command, void *buffer,
|
||||||
size_t size)
|
size_t size)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +353,7 @@ FUSEVolume::IOCtl(void* node, void* cookie, uint32 command, void *buffer,
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::SetFlags(void* node, void* cookie, int flags)
|
FUSEVolume::SetFlags(void* node, void* cookie, int flags)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +361,7 @@ FUSEVolume::SetFlags(void* node, void* cookie, int flags)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Select(void* node, void* cookie, uint8 event, selectsync* sync)
|
FUSEVolume::Select(void* node, void* cookie, uint8 event, selectsync* sync)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +369,7 @@ FUSEVolume::Select(void* node, void* cookie, uint8 event, selectsync* sync)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Deselect(void* node, void* cookie, uint8 event, selectsync* sync)
|
FUSEVolume::Deselect(void* node, void* cookie, uint8 event, selectsync* sync)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +377,7 @@ FUSEVolume::Deselect(void* node, void* cookie, uint8 event, selectsync* sync)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::FSync(void* node)
|
FUSEVolume::FSync(void* node)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +386,7 @@ status_t
|
|||||||
FUSEVolume::ReadSymlink(void* node, char* buffer, size_t bufferSize,
|
FUSEVolume::ReadSymlink(void* node, char* buffer, size_t bufferSize,
|
||||||
size_t* bytesRead)
|
size_t* bytesRead)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +395,7 @@ status_t
|
|||||||
FUSEVolume::CreateSymlink(void* dir, const char* name, const char* target,
|
FUSEVolume::CreateSymlink(void* dir, const char* name, const char* target,
|
||||||
int mode)
|
int mode)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +403,7 @@ FUSEVolume::CreateSymlink(void* dir, const char* name, const char* target,
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Link(void* dir, const char* name, void* node)
|
FUSEVolume::Link(void* dir, const char* name, void* node)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +411,7 @@ FUSEVolume::Link(void* dir, const char* name, void* node)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Unlink(void* dir, const char* name)
|
FUSEVolume::Unlink(void* dir, const char* name)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +420,7 @@ status_t
|
|||||||
FUSEVolume::Rename(void* oldDir, const char* oldName, void* newDir,
|
FUSEVolume::Rename(void* oldDir, const char* oldName, void* newDir,
|
||||||
const char* newName)
|
const char* newName)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,20 +428,41 @@ FUSEVolume::Rename(void* oldDir, const char* oldName, void* newDir,
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Access(void* node, int mode)
|
FUSEVolume::Access(void* node, int mode)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::ReadStat(void* node, struct stat* st)
|
FUSEVolume::ReadStat(void* _node, struct stat* st)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
FUSENode* node = (FUSENode*)_node;
|
||||||
|
PRINT(("FUSEVolume::ReadStat(%p (%lld), %p)\n", node, node->id, st));
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// stat the path
|
||||||
|
int fuseError = fuse_fs_getattr(fFS, path, st);
|
||||||
|
if (fuseError != 0)
|
||||||
|
return from_fuse_error(fuseError);
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::WriteStat(void* node, const struct stat *st, uint32 mask)
|
FUSEVolume::WriteStat(void* node, const struct stat *st, uint32 mask)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +474,7 @@ status_t
|
|||||||
FUSEVolume::Create(void* dir, const char* name, int openMode, int mode,
|
FUSEVolume::Create(void* dir, const char* name, int openMode, int mode,
|
||||||
void** cookie, ino_t* vnid)
|
void** cookie, ino_t* vnid)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +482,7 @@ FUSEVolume::Create(void* dir, const char* name, int openMode, int mode,
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Open(void* node, int openMode, void** cookie)
|
FUSEVolume::Open(void* node, int openMode, void** cookie)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +490,7 @@ FUSEVolume::Open(void* node, int openMode, void** cookie)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::Close(void* node, void* cookie)
|
FUSEVolume::Close(void* node, void* cookie)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +498,7 @@ FUSEVolume::Close(void* node, void* cookie)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::FreeCookie(void* node, void* cookie)
|
FUSEVolume::FreeCookie(void* node, void* cookie)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +507,7 @@ status_t
|
|||||||
FUSEVolume::Read(void* node, void* cookie, off_t pos, void* buffer,
|
FUSEVolume::Read(void* node, void* cookie, off_t pos, void* buffer,
|
||||||
size_t bufferSize, size_t* bytesRead)
|
size_t bufferSize, size_t* bytesRead)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +516,7 @@ status_t
|
|||||||
FUSEVolume::Write(void* node, void* cookie, off_t pos, const void* buffer,
|
FUSEVolume::Write(void* node, void* cookie, off_t pos, const void* buffer,
|
||||||
size_t bufferSize, size_t* bytesWritten)
|
size_t bufferSize, size_t* bytesWritten)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +527,7 @@ FUSEVolume::Write(void* node, void* cookie, off_t pos, const void* buffer,
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::CreateDir(void* dir, const char* name, int mode, ino_t *newDir)
|
FUSEVolume::CreateDir(void* dir, const char* name, int mode, ino_t *newDir)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,41 +535,433 @@ FUSEVolume::CreateDir(void* dir, const char* name, int mode, ino_t *newDir)
|
|||||||
status_t
|
status_t
|
||||||
FUSEVolume::RemoveDir(void* dir, const char* name)
|
FUSEVolume::RemoveDir(void* dir, const char* name)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::OpenDir(void* node, void** cookie)
|
FUSEVolume::OpenDir(void* _node, void** _cookie)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
FUSENode* node = (FUSENode*)_node;
|
||||||
|
PRINT(("FUSEVolume::OpenDir(%p (%lld), %p)\n", node, node->id, _cookie));
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// open the dir
|
||||||
|
int fuseError = fuse_fs_opendir(fFS, path, cookie);
|
||||||
|
if (fuseError != 0)
|
||||||
|
return from_fuse_error(fuseError);
|
||||||
|
|
||||||
|
cookieDeleter.Detach();
|
||||||
|
*_cookie = cookie;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::CloseDir(void* node, void* cookie)
|
FUSEVolume::CloseDir(void* node, void* _cookie)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::FreeDirCookie(void* node, void* cookie)
|
FUSEVolume::FreeDirCookie(void* _node, void* _cookie)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
FUSENode* node = (FUSENode*)_node;
|
||||||
|
DirCookie* cookie = (DirCookie*)_cookie;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// release the dir
|
||||||
|
int fuseError = fuse_fs_releasedir(fFS, path, cookie);
|
||||||
|
if (fuseError != 0)
|
||||||
|
return from_fuse_error(fuseError);
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
return B_UNSUPPORTED;
|
FUSENode* node = (FUSENode*)_node;
|
||||||
|
DirCookie* cookie = (DirCookie*)_cookie;
|
||||||
|
|
||||||
|
AutoLocker<Locker> locker(fLock);
|
||||||
|
|
||||||
|
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 = fuse_fs_readdir(fFS, path, &readDirBuffer,
|
||||||
|
&_AddReadDirEntry, offset, cookie);
|
||||||
|
if (fuseError != 0)
|
||||||
|
return from_fuse_error(fuseError);
|
||||||
|
|
||||||
|
locker.Lock();
|
||||||
|
|
||||||
|
// everything went fine
|
||||||
|
cookie->currentEntryOffset = readDirBuffer.lastOffset;
|
||||||
|
*countRead = readDirBuffer.entriesRead;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
FUSEVolume::RewindDir(void* node, void* cookie)
|
FUSEVolume::RewindDir(void* node, void* cookie)
|
||||||
{
|
{
|
||||||
|
// TODO: Implement!
|
||||||
return B_UNSUPPORTED;
|
return B_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
|
ino_t
|
||||||
|
FUSEVolume::_GenerateNodeID()
|
||||||
|
{
|
||||||
|
ino_t id;
|
||||||
|
do {
|
||||||
|
id = fNextNodeID++;
|
||||||
|
} while (fNodes.Lookup(id) != NULL);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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!
|
||||||
|
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 from_fuse_error(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
FUSEVolume* self = buffer->volume;
|
||||||
|
|
||||||
|
AutoLocker<Locker> _(self->fLock);
|
||||||
|
|
||||||
|
if (offset == 0) {
|
||||||
|
ERROR(("FUSEVolume::_AddReadDirEntry(): Old interface not "
|
||||||
|
"supported!\n"));
|
||||||
|
buffer->error = B_ERROR;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer->entriesRead == buffer->maxEntries)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// compute the entry length and check whether the entry still fits
|
||||||
|
size_t nameLen = 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)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// create a node and an entry, if necessary
|
||||||
|
FUSEEntry* entry = self->fEntries.Lookup(
|
||||||
|
FUSEEntryRef(buffer->directory->id, name));
|
||||||
|
if (entry == NULL) {
|
||||||
|
// get the node
|
||||||
|
ino_t nodeID = st->st_ino;
|
||||||
|
FUSENode* node = NULL;
|
||||||
|
if (self->fUseNodeIDs)
|
||||||
|
node = self->fNodes.Lookup(st->st_ino);
|
||||||
|
else
|
||||||
|
nodeID = self->_GenerateNodeID();
|
||||||
|
|
||||||
|
if (node == NULL) {
|
||||||
|
// no node yet -- create one
|
||||||
|
node = new(std::nothrow) FUSENode(nodeID, st->st_mode & S_IFMT);
|
||||||
|
if (node == NULL) {
|
||||||
|
buffer->error = B_NO_MEMORY;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->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) {
|
||||||
|
self->_PutNode(node);
|
||||||
|
buffer->error = B_NO_MEMORY;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->fEntries.Insert(entry);
|
||||||
|
node->entries.Add(entry);
|
||||||
|
} else {
|
||||||
|
// TODO: Check whether the node's ID matches the one we got!
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in the dirent
|
||||||
|
dirEntry->d_dev = self->fID;
|
||||||
|
dirEntry->d_ino = entry->node->id;
|
||||||
|
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->lastOffset = offset;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
#ifndef USERLAND_FS_FUSE_VOLUME_H
|
#ifndef USERLAND_FS_FUSE_VOLUME_H
|
||||||
#define USERLAND_FS_FUSE_VOLUME_H
|
#define USERLAND_FS_FUSE_VOLUME_H
|
||||||
|
|
||||||
|
#include <AutoLocker.h>
|
||||||
|
|
||||||
|
#include "Locker.h"
|
||||||
|
|
||||||
|
#include "fuse_fs.h"
|
||||||
|
#include "FUSEEntry.h"
|
||||||
|
|
||||||
#include "../Volume.h"
|
#include "../Volume.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +26,10 @@ public:
|
|||||||
dev_t id);
|
dev_t id);
|
||||||
virtual ~FUSEVolume();
|
virtual ~FUSEVolume();
|
||||||
|
|
||||||
|
status_t Init();
|
||||||
|
|
||||||
|
void SetFS(fuse_fs* fs) { fFS = fs; }
|
||||||
|
|
||||||
// FS
|
// FS
|
||||||
virtual status_t Mount(const char* device, uint32 flags,
|
virtual status_t Mount(const char* device, uint32 flags,
|
||||||
const char* parameters, ino_t* rootID);
|
const char* parameters, ino_t* rootID);
|
||||||
@@ -31,10 +42,6 @@ public:
|
|||||||
// vnodes
|
// vnodes
|
||||||
virtual status_t Lookup(void* dir, const char* entryName,
|
virtual status_t Lookup(void* dir, const char* entryName,
|
||||||
ino_t* vnid);
|
ino_t* vnid);
|
||||||
virtual status_t GetVNodeType(void* node, int* type);
|
|
||||||
// Only needs to be implemented when
|
|
||||||
// the three parameters publish_vnode() is
|
|
||||||
// used.
|
|
||||||
virtual status_t GetVNodeName(void* node, char* buffer,
|
virtual status_t GetVNodeName(void* node, char* buffer,
|
||||||
size_t bufferSize);
|
size_t bufferSize);
|
||||||
virtual status_t ReadVNode(ino_t vnid, bool reenter,
|
virtual status_t ReadVNode(ino_t vnid, bool reenter,
|
||||||
@@ -111,8 +118,39 @@ public:
|
|||||||
uint32 count, uint32* countRead);
|
uint32 count, uint32* countRead);
|
||||||
virtual status_t RewindDir(void* node, void* cookie);
|
virtual status_t RewindDir(void* node, void* cookie);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct DirCookie;
|
||||||
|
struct ReadDirBuffer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline FUSEFileSystem* _FileSystem() const;
|
inline FUSEFileSystem* _FileSystem() const;
|
||||||
|
|
||||||
|
ino_t _GenerateNodeID();
|
||||||
|
|
||||||
|
status_t _GetNode(FUSENode* dir, const char* entryName,
|
||||||
|
FUSENode** _node);
|
||||||
|
status_t _InternalGetNode(FUSENode* dir,
|
||||||
|
const char* entryName, FUSENode** _node,
|
||||||
|
AutoLocker<Locker>& locker);
|
||||||
|
void _PutNode(FUSENode* node);
|
||||||
|
|
||||||
|
status_t _BuildPath(FUSENode* dir, const char* entryName,
|
||||||
|
char* path, size_t& pathLen);
|
||||||
|
status_t _BuildPath(FUSENode* node, char* path,
|
||||||
|
size_t& pathLen);
|
||||||
|
|
||||||
|
static int _AddReadDirEntry(void* buffer, const char* name,
|
||||||
|
const struct stat* st, off_t offset);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Locker fLock;
|
||||||
|
fuse_fs* fFS;
|
||||||
|
FUSEEntryTable fEntries;
|
||||||
|
FUSENodeTable fNodes;
|
||||||
|
FUSENode* fRootNode;
|
||||||
|
ino_t fNextNodeID;
|
||||||
|
bool fUseNodeIDs; // TODO: Actually read the
|
||||||
|
// option!
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UserlandFS
|
} // namespace UserlandFS
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ local userlandFSTop = [ FDirName $(HAIKU_TOP) src add-ons kernel
|
|||||||
file_systems userlandfs ] ;
|
file_systems userlandfs ] ;
|
||||||
local userlandFSIncludes = [ PrivateHeaders userlandfs ] ;
|
local userlandFSIncludes = [ PrivateHeaders userlandfs ] ;
|
||||||
|
|
||||||
|
UsePrivateHeaders kernel shared ;
|
||||||
SubDirSysHdrs [ FDirName $(userlandFSIncludes) ] ;
|
SubDirSysHdrs [ FDirName $(userlandFSIncludes) ] ;
|
||||||
SubDirSysHdrs [ FDirName $(userlandFSIncludes) fuse ] ;
|
SubDirSysHdrs [ FDirName $(userlandFSIncludes) fuse ] ;
|
||||||
SubDirHdrs [ FDirName $(userlandFSIncludes) private ] ;
|
SubDirHdrs [ FDirName $(userlandFSIncludes) private ] ;
|
||||||
@@ -26,5 +27,5 @@ SharedLibrary libuserlandfs_fuse.so
|
|||||||
FUSEVolume.cpp
|
FUSEVolume.cpp
|
||||||
|
|
||||||
:
|
:
|
||||||
<nogrist>userlandfs_server
|
<nogrist>userlandfs_server be
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -8,5 +8,6 @@
|
|||||||
#define FUSE_USE_VERSION FUSE_VERSION
|
#define FUSE_USE_VERSION FUSE_VERSION
|
||||||
|
|
||||||
#include <fuse.h>
|
#include <fuse.h>
|
||||||
|
#include <fuse_lowlevel.h>
|
||||||
|
|
||||||
#endif // USERLAND_FS_FUSE_FUSE_API_H
|
#endif // USERLAND_FS_FUSE_FUSE_API_H
|
||||||
|
|||||||
Reference in New Issue
Block a user