⛏️ index : haiku.git

/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "FUSEFileSystem.h"

#include <stdlib.h>
#include <string.h>

#include <new>

#include "fuse_fs.h"
#include "FUSELowLevel.h"
#include "FUSEVolume.h"

#include "../RequestThread.h"


class FUSEFileSystem::ArgumentVector {
private:
	enum { MAX_ARGUMENTS = 128 };

public:
	ArgumentVector()
		:
		fBuffer(NULL),
		fCount(0)
	{
	}

	~ArgumentVector()
	{
		free(fBuffer);
	}

	const char* const* Arguments() const
	{
		return fArguments;
	}

	int ArgumentCount() const
	{
		return fCount;
	}

	status_t Init(const char* firstElement, const char* arguments)
	{
		size_t firstElementSize = firstElement != NULL
			? strlen(firstElement) + 1 : 0;

		// allocate the buffer
		fBuffer = (char*)malloc(firstElementSize + strlen(arguments) + 1);
		if (fBuffer == NULL)
			return B_NO_MEMORY;

		fCount = 0;

		bool inArgument = false;
		int bufferIndex = 0;

		// push the first element, if given
		if (firstElement != NULL) {
			memcpy(fBuffer, firstElement, firstElementSize);
			fArguments[fCount++] = fBuffer;
			bufferIndex = firstElementSize;
		}

		// parse the given string
		for (; *arguments != '\0'; arguments++) {
			char c = *arguments;
			switch (c) {
				case ' ':
				case '\t':
				case '\r':
				case '\n':
					// white-space marks argument boundaries
					if (inArgument) {
						// terminate the current argument
						fBuffer[bufferIndex++] = '\0';
						inArgument = false;
					}
					break;
				case '\\':
					c = *++arguments;
					if (c == '\0')
						break;
					// fall through
				default:
					if (!inArgument) {
						// push a new argument
						if (fCount == MAX_ARGUMENTS)
							break;

						fArguments[fCount++] = fBuffer + bufferIndex;
						inArgument = true;
					}

					fBuffer[bufferIndex++] = c;
					break;
			}
		}

		// terminate the last argument
		if (inArgument)
			fBuffer[bufferIndex++] = '\0';

		// NULL terminate the argument array
		fArguments[fCount] = NULL;

		return B_OK;
	}

private:
	char*		fBuffer;
	const char*	fArguments[MAX_ARGUMENTS + 1];
	int			fCount;
};


FUSEFileSystem::FUSEFileSystem(const char* fsName,
	int (*mainFunction)(int, const char* const*))
	:
	FileSystem(fsName),
	fMainFunction(mainFunction),
	fInitThread(-1),
	fInitStatus(B_NO_INIT),
	fInitSemaphore(-1),
	fExitSemaphore(-1),
	fInitParameters(NULL),
	fUserData(NULL),
	fFS(NULL)
{
	fClientFSType = CLIENT_FS_FUSE;

	// FS capabilities
	fCapabilities.ClearAll();
	fCapabilities.Set(FS_CAPABILITY_MOUNT, true);
}


FUSEFileSystem::~FUSEFileSystem()
{
	if (fInitSemaphore >= 0)
		delete_sem(fInitSemaphore);

	if (fExitSemaphore >= 0)
		delete_sem(fExitSemaphore);

	if (fInitThread >= 0)
		wait_for_thread(fInitThread, NULL);
}


status_t
FUSEFileSystem::CreateVolume(Volume** _volume, dev_t id)
{
printf("FUSEFileSystem::CreateVolume()\n");
	// Only one volume is possible
	if (!fVolumes.IsEmpty())
		RETURN_ERROR(B_BUSY);

	// create the volume
	FUSEVolume* volume = new(std::nothrow) FUSEVolume(this, id);
	if (volume == NULL)
		return B_NO_MEMORY;

	status_t error = volume->Init();
	if (error != B_OK) {
		delete volume;
		return error;
	}

	*_volume = volume;
	return B_OK;
}


status_t
FUSEFileSystem::DeleteVolume(Volume* volume)
{
	delete volume;
	return B_OK;
}


void
FUSEFileSystem::InitRequestThreadContext(RequestThreadContext* context)
{
	// Statically assert that fuse_context fits in the RequestThreadContext
	// FS data. We can't include <Debug.h> as it clashes with our "Debug.h".
	do {
		static const int staticAssertHolds
			= sizeof(fuse_context) <= REQUEST_THREAD_CONTEXT_FS_DATA_SIZE;
		struct __staticAssertStruct__ {
			char __static_assert_failed__[2 * staticAssertHolds - 1];
		};
	} while (false);

	// init a fuse_context
	KernelRequest* request = context->GetRequest();
	fuse_context* fuseContext = (fuse_context*)context->GetFSData();
	fuseContext->fuse = (struct fuse*)this;
	fuseContext->uid = request->user;
	fuseContext->gid = request->group;
	fuseContext->pid = request->team;
	fuseContext->private_data = fFS != NULL ? fFS->userData : NULL;
}


status_t
FUSEFileSystem::InitClientFS(const char* parameters)
{
PRINT(("FUSEFileSystem::InitClientFS()\n"));
	// create the semaphores we need
	fInitSemaphore = create_sem(0, "FUSE init sem");
	if (fInitSemaphore < 0)
		RETURN_ERROR(fInitSemaphore);

	fExitSemaphore = create_sem(0, "FUSE exit sem");
	if (fExitSemaphore < 0)
		RETURN_ERROR(fExitSemaphore);

	fInitStatus = 1;
	fInitParameters = parameters;

	// Start the initialization thread -- it will call main() and won't return
	// until unmounting.
	fInitThread = spawn_thread(&_InitializationThreadEntry,
		"FUSE init", B_NORMAL_PRIORITY, this);
	if (fInitThread < 0)
		RETURN_ERROR(fInitThread);

	resume_thread(fInitThread);

	// wait for the initialization to finish
PRINT(("  waiting for init thread...\n"));
	while (acquire_sem(fInitSemaphore) == B_INTERRUPTED) {
	}

PRINT(("  waiting for init thread done\n"));
	fInitSemaphore = -1;

	if (fInitStatus > 0)
		RETURN_ERROR(B_ERROR);
	if (fInitStatus != B_OK)
		RETURN_ERROR(fInitStatus);

	// initialization went fine
	return B_OK;
}


void
FUSEFileSystem::ExitClientFS(status_t status)
{
	// set the exit status and notify the initialization thread
	fExitStatus = status;
	if (fExitSemaphore >= 0)
		delete_sem(fExitSemaphore);

	if (fInitThread >= 0)
		wait_for_thread(fInitThread, NULL);
}


status_t
FUSEFileSystem::FinishInitClientFS(fuse_config* config,
	const fuse_operations* ops, size_t opSize, void* userData)
{
PRINT(("FUSEFileSystem::FinishInitClientFS()\n"));
	fExitStatus = B_ERROR;

	fFUSEConfig = *config;

	// do the initialization
	fInitStatus = _InitClientFS(ops, opSize, userData);
	return fInitStatus;
}


status_t
FUSEFileSystem::FinishInitClientFS(fuse_config* config,
	const fuse_lowlevel_ops* ops, size_t opSize, void* userData)
{
PRINT(("FUSEFileSystem::FinishInitClientFS()\n"));
	fExitStatus = B_ERROR;

	fFUSEConfig = *config;

	// do the initialization
	fInitStatus = _InitClientFS(ops, opSize, userData);
	return fInitStatus;
}


status_t
FUSEFileSystem::MainLoop(bool multithreaded)
{
	// TODO: Respect the multithreaded flag!

PRINT(("FUSEFileSystem::FinishMounting()\n"));
	// notify the mount thread
PRINT(("  notifying mount thread\n"));
	delete_sem(fInitSemaphore);

	// loop until unmounting
PRINT(("  waiting for unmounting...\n"));
	while (acquire_sem(fExitSemaphore) == B_INTERRUPTED) {
	}
PRINT(("  waiting for unmounting done\n"));

	fExitSemaphore = -1;

	if (fFS != NULL)
		fuse_fs_destroy(fFS);
	else
		fuse_ll_destroy(&fLowLevelOps, fUserData);

	return fExitStatus;
}


/*static*/ status_t
FUSEFileSystem::_InitializationThreadEntry(void* data)
{
	return ((FUSEFileSystem*)data)->_InitializationThread();
}


status_t
FUSEFileSystem::_InitializationThread()
{
	// parse the parameters
	ArgumentVector args;
	status_t error = args.Init(GetName(), fInitParameters);
	if (error != B_OK) {
		fInitStatus = error;
		delete_sem(fInitSemaphore);
		return B_OK;
	}

	// call main -- should not return until unmounting
	fMainFunction(args.ArgumentCount(), args.Arguments());
printf("FUSEFileSystem::_InitializationThread(): main() returned!\n");

	if (fInitStatus > 0 && fInitSemaphore >= 0) {
		// something went wrong early -- main() returned without calling
		// fuse_main()
		fInitStatus = B_ERROR;
		delete_sem(fInitSemaphore);
	}

	return B_OK;
}


status_t
FUSEFileSystem::_InitClientFS(const fuse_operations* ops, size_t opSize,
	void* userData)
{
	// create a fuse_fs object
	fFS = fuse_fs_new(ops, opSize, userData);
	if (fFS == NULL)
		return B_ERROR;

	_InitCapabilities();
PRINT(("volume capabilities:\n"));
fVolumeCapabilities.Dump();
PRINT(("node capabilities:\n"));
fNodeCapabilities.Dump();

	// init connection info
	fConnectionInfo.proto_major = 0;
	fConnectionInfo.proto_minor = 0;
	fConnectionInfo.async_read = false;
	fConnectionInfo.max_write = 64 * 1024;
	fConnectionInfo.max_readahead = 64 * 1024;
	fConnectionInfo.capable = FUSE_CAP_ATOMIC_O_TRUNC | FUSE_CAP_BIG_WRITES | FUSE_CAP_IOCTL_DIR
		| FUSE_CAP_HAIKU_FUSE_EXTENSIONS;

	fuse_fs_init(fFS, &fConnectionInfo);

	return B_OK;
}


status_t
FUSEFileSystem::_InitClientFS(const fuse_lowlevel_ops* lowLevelOps, size_t lowLevelOpSize,
	void* userData)
{
	fLowLevelOps = *lowLevelOps;

	_InitCapabilities();
PRINT(("volume capabilities:\n"));
fVolumeCapabilities.Dump();
PRINT(("node capabilities:\n"));
fNodeCapabilities.Dump();

	// init connection info
	fConnectionInfo.proto_major = 0;
	fConnectionInfo.proto_minor = 0;
	fConnectionInfo.async_read = false;
	fConnectionInfo.max_write = 64 * 1024;
	fConnectionInfo.max_readahead = 64 * 1024;
	fUserData = userData;

	fuse_ll_init(&fLowLevelOps, userData, &fConnectionInfo);

	return B_OK;
}


void
FUSEFileSystem::_InitCapabilities()
{
	fVolumeCapabilities.ClearAll();
	fNodeCapabilities.ClearAll();

	// Volume operations
	fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_UNMOUNT, true);
	fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_GET_VNODE, true);
		// emulated

	// 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

	// 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
	// missing: FS_VOLUME_CAPABILITY_FREE_INDEX_DIR_COOKIE
	// missing: FS_VOLUME_CAPABILITY_READ_INDEX_DIR
	// missing: FS_VOLUME_CAPABILITY_REWIND_INDEX_DIR

	// missing: FS_VOLUME_CAPABILITY_CREATE_INDEX
	// missing: FS_VOLUME_CAPABILITY_REMOVE_INDEX
	// missing: FS_VOLUME_CAPABILITY_READ_INDEX_STAT

	// query operations
	// missing: FS_VOLUME_CAPABILITY_OPEN_QUERY
	// missing: FS_VOLUME_CAPABILITY_CLOSE_QUERY
	// missing: FS_VOLUME_CAPABILITY_FREE_QUERY_COOKIE
	// missing: FS_VOLUME_CAPABILITY_READ_QUERY
	// missing: FS_VOLUME_CAPABILITY_REWIND_QUERY

	// 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
	// missing: FS_VNODE_CAPABILITY_IOCTL
	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_SET_FLAGS, true);
		// emulated
	// missing: FS_VNODE_CAPABILITY_SELECT
	// missing: FS_VNODE_CAPABILITY_DESELECT
	
	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_ATTR, false);

	if (fFS == NULL) {
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FSYNC, fLowLevelOps.fsync);

		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_SYMLINK, fLowLevelOps.readlink);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_SYMLINK, fLowLevelOps.symlink);

		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LINK, fLowLevelOps.link);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_UNLINK, fLowLevelOps.unlink);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME, fLowLevelOps.rename);

		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_ACCESS, fLowLevelOps.access);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_STAT, fLowLevelOps.getattr);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_STAT, fLowLevelOps.setattr != NULL);

		fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fLowLevelOps.statfs);
		// missing: FS_VOLUME_CAPABILITY_WRITE_FS_INFO
		fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fLowLevelOps.fsync);
		// emulated via fsync()

		// file operations
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE, fLowLevelOps.create);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN, fLowLevelOps.open);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE, fLowLevelOps.flush);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fLowLevelOps.release);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ, fLowLevelOps.read);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE, fLowLevelOps.write);

		// directory operations
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fLowLevelOps.mkdir);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fLowLevelOps.rmdir);
		bool readDirSupport = fLowLevelOps.opendir != NULL || fLowLevelOps.readdir != NULL;
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
		// not needed: FS_VNODE_CAPABILITY_CLOSE_DIR
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);

		// attribute directory operations
		bool hasAttributes = fLowLevelOps.listxattr != NULL;
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, hasAttributes);
		// not needed: FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, hasAttributes);

		// attribute operations
		// 	// we emulate open_attr() and free_attr_dir_cookie() if either read_attr()
		// 	// or write_attr() is present
		// 	bool hasAttributes = (fLowLevelOps.read_attr || fLowLevelOps.write_attr);
		// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_ATTR, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fLowLevelOps.getxattr);
		// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fLowLevelOps.write_attr);

		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT, fLowLevelOps.getxattr);
// 		// missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
// 		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fLowLevelOps.rename_attr);
	} else {
		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.chmod != NULL || fFS->ops.chown != NULL
				|| fFS->ops.truncate != NULL || fFS->ops.utimens != NULL
				|| fFS->ops.utime != NULL);

		fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fFS->ops.statfs);
		// missing: FS_VOLUME_CAPABILITY_WRITE_FS_INFO
		fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fFS->ops.fsync);
		// emulated via fsync()

		// 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.flush);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fFS->ops.release);
		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);
		bool readDirSupport = fFS->ops.opendir != NULL || fFS->ops.readdir != NULL
			|| fFS->ops.getdir;
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
		// not needed: FS_VNODE_CAPABILITY_CLOSE_DIR
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);

		// attribute directory operations
		bool hasAttributes = fFS->ops.listxattr != NULL;
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, hasAttributes);
		// not needed: FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE,
				hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, hasAttributes);

		// 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_FREE_ATTR_COOKIE, hasAttributes);
		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fFS->ops.getxattr);
		// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fFS->ops.write_attr);

		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT,
		fFS->ops.getxattr);
// 		// missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
// 		fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fFS->ops.rename_attr);
	}
}


// #pragma mark - bootstrapping


status_t
userlandfs_create_file_system(const char* fsName, image_id image,
	FileSystem** _fileSystem)
{
printf("userlandfs_create_file_system()\n");
	// look up the main() function of the add-on
	int (*mainFunction)(int argc, const char* const* argv);
	status_t error = get_image_symbol(image, "main", B_SYMBOL_TYPE_TEXT,
		(void**)&mainFunction);
	if (error != B_OK)
		return error;
printf("userlandfs_create_file_system(): found main: %p\n", mainFunction);

	// create the file system
	FUSEFileSystem* fileSystem = new(std::nothrow) FUSEFileSystem(fsName,
		mainFunction);
	if (fileSystem == NULL)
		return B_NO_MEMORY;

	*_fileSystem = fileSystem;
	return B_OK;
}