1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "FUSEFileSystem.h"
7
8#include <stdlib.h>
9#include <string.h>
10
11#include <new>
12
13#include "fuse_fs.h"
14#include "FUSEVolume.h"
15
16#include "../RequestThread.h"
17
18
19class FUSEFileSystem::ArgumentVector {
20private:
21	enum { MAX_ARGUMENTS = 128 };
22
23public:
24	ArgumentVector()
25		:
26		fBuffer(NULL),
27		fCount(0)
28	{
29	}
30
31	~ArgumentVector()
32	{
33		free(fBuffer);
34	}
35
36	const char* const* Arguments() const
37	{
38		return fArguments;
39	}
40
41	int ArgumentCount() const
42	{
43		return fCount;
44	}
45
46	status_t Init(const char* firstElement, const char* arguments)
47	{
48		size_t firstElementSize = firstElement != NULL
49			? strlen(firstElement) + 1 : 0;
50
51		// allocate the buffer
52		fBuffer = (char*)malloc(firstElementSize + strlen(arguments) + 1);
53		if (fBuffer == NULL)
54			return B_NO_MEMORY;
55
56		fCount = 0;
57
58		bool inArgument = false;
59		int bufferIndex = 0;
60
61		// push the first element, if given
62		if (firstElement != NULL) {
63			memcpy(fBuffer, firstElement, firstElementSize);
64			fArguments[fCount++] = fBuffer;
65			bufferIndex = firstElementSize;
66		}
67
68		// parse the given string
69		for (; *arguments != '\0'; arguments++) {
70			char c = *arguments;
71			switch (c) {
72				case ' ':
73				case '\t':
74				case '\r':
75				case '\n':
76					// white-space marks argument boundaries
77					if (inArgument) {
78						// terminate the current argument
79						fBuffer[bufferIndex++] = '\0';
80						inArgument = false;
81					}
82					break;
83				case '\\':
84					c = *++arguments;
85					if (c == '\0')
86						break;
87					// fall through
88				default:
89					if (!inArgument) {
90						// push a new argument
91						if (fCount == MAX_ARGUMENTS)
92							break;
93
94						fArguments[fCount++] = fBuffer + bufferIndex;
95						inArgument = true;
96					}
97
98					fBuffer[bufferIndex++] = c;
99					break;
100			}
101		}
102
103		// terminate the last argument
104		if (inArgument)
105			fBuffer[bufferIndex++] = '\0';
106
107		// NULL terminate the argument array
108		fArguments[fCount] = NULL;
109
110		return B_OK;
111	}
112
113private:
114	char*		fBuffer;
115	const char*	fArguments[MAX_ARGUMENTS + 1];
116	int			fCount;
117};
118
119
120FUSEFileSystem::FUSEFileSystem(const char* fsName,
121	int (*mainFunction)(int, const char* const*))
122	:
123	FileSystem(fsName),
124	fMainFunction(mainFunction),
125	fInitThread(-1),
126	fInitStatus(B_NO_INIT),
127	fInitSemaphore(-1),
128	fExitSemaphore(-1),
129	fInitParameters(NULL),
130	fFS(NULL)
131{
132	fClientFSType = CLIENT_FS_FUSE;
133
134	// FS capabilities
135	fCapabilities.ClearAll();
136	fCapabilities.Set(FS_CAPABILITY_MOUNT, true);
137}
138
139
140FUSEFileSystem::~FUSEFileSystem()
141{
142	if (fInitSemaphore >= 0)
143		delete_sem(fInitSemaphore);
144
145	if (fExitSemaphore >= 0)
146		delete_sem(fExitSemaphore);
147
148	if (fInitThread >= 0)
149		wait_for_thread(fInitThread, NULL);
150}
151
152
153status_t
154FUSEFileSystem::CreateVolume(Volume** _volume, dev_t id)
155{
156printf("FUSEFileSystem::CreateVolume()\n");
157	// Only one volume is possible
158	if (!fVolumes.IsEmpty())
159		RETURN_ERROR(B_BUSY);
160
161	// create the volume
162	FUSEVolume* volume = new(std::nothrow) FUSEVolume(this, id);
163	if (volume == NULL)
164		return B_NO_MEMORY;
165
166	status_t error = volume->Init();
167	if (error != B_OK) {
168		delete volume;
169		return error;
170	}
171
172	*_volume = volume;
173	return B_OK;
174}
175
176
177status_t
178FUSEFileSystem::DeleteVolume(Volume* volume)
179{
180	delete volume;
181	return B_OK;
182}
183
184
185void
186FUSEFileSystem::InitRequestThreadContext(RequestThreadContext* context)
187{
188	// Statically assert that fuse_context fits in the RequestThreadContext
189	// FS data. We can't include <Debug.h> as it clashes with our "Debug.h".
190	do {
191		static const int staticAssertHolds
192			= sizeof(fuse_context) <= REQUEST_THREAD_CONTEXT_FS_DATA_SIZE;
193		struct __staticAssertStruct__ {
194			char __static_assert_failed__[2 * staticAssertHolds - 1];
195		};
196	} while (false);
197
198	// init a fuse_context
199	KernelRequest* request = context->GetRequest();
200	fuse_context* fuseContext = (fuse_context*)context->GetFSData();
201	fuseContext->fuse = (struct fuse*)this;
202	fuseContext->uid = request->user;
203	fuseContext->gid = request->group;
204	fuseContext->pid = request->team;
205	fuseContext->private_data = fFS != NULL ? fFS->userData : NULL;
206}
207
208
209status_t
210FUSEFileSystem::InitClientFS(const char* parameters)
211{
212PRINT(("FUSEFileSystem::InitClientFS()\n"));
213	// create the semaphores we need
214	fInitSemaphore = create_sem(0, "FUSE init sem");
215	if (fInitSemaphore < 0)
216		RETURN_ERROR(fInitSemaphore);
217
218	fExitSemaphore = create_sem(0, "FUSE exit sem");
219	if (fExitSemaphore < 0)
220		RETURN_ERROR(fExitSemaphore);
221
222	fInitStatus = 1;
223	fInitParameters = parameters;
224
225	// Start the initialization thread -- it will call main() and won't return
226	// until unmounting.
227	fInitThread = spawn_thread(&_InitializationThreadEntry,
228		"FUSE init", B_NORMAL_PRIORITY, this);
229	if (fInitThread < 0)
230		RETURN_ERROR(fInitThread);
231
232	resume_thread(fInitThread);
233
234	// wait for the initialization to finish
235PRINT(("  waiting for init thread...\n"));
236	while (acquire_sem(fInitSemaphore) == B_INTERRUPTED) {
237	}
238
239PRINT(("  waiting for init thread done\n"));
240	fInitSemaphore = -1;
241
242	if (fInitStatus > 0)
243		RETURN_ERROR(B_ERROR);
244	if (fInitStatus != B_OK)
245		RETURN_ERROR(fInitStatus);
246
247	// initialization went fine
248	return B_OK;
249}
250
251
252void
253FUSEFileSystem::ExitClientFS(status_t status)
254{
255	// set the exit status and notify the initialization thread
256	fExitStatus = status;
257	if (fExitSemaphore >= 0)
258		delete_sem(fExitSemaphore);
259
260	if (fInitThread >= 0)
261		wait_for_thread(fInitThread, NULL);
262}
263
264
265status_t
266FUSEFileSystem::FinishInitClientFS(fuse_config* config,
267	const fuse_operations* ops, size_t opSize, void* userData)
268{
269PRINT(("FUSEFileSystem::FinishInitClientFS()\n"));
270	fExitStatus = B_ERROR;
271
272	fFUSEConfig = *config;
273
274	// do the initialization
275	fInitStatus = _InitClientFS(ops, opSize, userData);
276	return fInitStatus;
277}
278
279
280status_t
281FUSEFileSystem::MainLoop(bool multithreaded)
282{
283	// TODO: Respect the multithreaded flag!
284
285PRINT(("FUSEFileSystem::FinishMounting()\n"));
286	// notify the mount thread
287PRINT(("  notifying mount thread\n"));
288	delete_sem(fInitSemaphore);
289
290	// loop until unmounting
291PRINT(("  waiting for unmounting...\n"));
292	while (acquire_sem(fExitSemaphore) == B_INTERRUPTED) {
293	}
294PRINT(("  waiting for unmounting done\n"));
295
296	fExitSemaphore = -1;
297
298	if (fFS != NULL)
299		fuse_fs_destroy(fFS);
300
301	return fExitStatus;
302}
303
304
305/*static*/ status_t
306FUSEFileSystem::_InitializationThreadEntry(void* data)
307{
308	return ((FUSEFileSystem*)data)->_InitializationThread();
309}
310
311
312status_t
313FUSEFileSystem::_InitializationThread()
314{
315	// parse the parameters
316	ArgumentVector args;
317	status_t error = args.Init(GetName(), fInitParameters);
318	if (error != B_OK) {
319		fInitStatus = error;
320		delete_sem(fInitSemaphore);
321		return B_OK;
322	}
323
324	// call main -- should not return until unmounting
325	fMainFunction(args.ArgumentCount(), args.Arguments());
326printf("FUSEFileSystem::_InitializationThread(): main() returned!\n");
327
328	if (fInitStatus > 0 && fInitSemaphore >= 0) {
329		// something went wrong early -- main() returned without calling
330		// fuse_main()
331		fInitStatus = B_ERROR;
332		delete_sem(fInitSemaphore);
333	}
334
335	return B_OK;
336}
337
338
339status_t
340FUSEFileSystem::_InitClientFS(const fuse_operations* ops, size_t opSize,
341	void* userData)
342{
343	// create a fuse_fs object
344	fFS = fuse_fs_new(ops, opSize, userData);
345	if (fFS == NULL)
346		return B_ERROR;
347
348	_InitCapabilities();
349PRINT(("volume capabilities:\n"));
350fVolumeCapabilities.Dump();
351PRINT(("node capabilities:\n"));
352fNodeCapabilities.Dump();
353
354	// init connection info
355	fConnectionInfo.proto_major = 0;
356	fConnectionInfo.proto_minor = 0;
357	fConnectionInfo.async_read = false;
358	fConnectionInfo.max_write = 64 * 1024;
359	fConnectionInfo.max_readahead = 64 * 1024;
360
361	fuse_fs_init(fFS, &fConnectionInfo);
362
363	return B_OK;
364}
365
366
367void
368FUSEFileSystem::_InitCapabilities()
369{
370	fVolumeCapabilities.ClearAll();
371	fNodeCapabilities.ClearAll();
372
373	// Volume operations
374	fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_UNMOUNT, true);
375
376	fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fFS->ops.statfs);
377	// missing: FS_VOLUME_CAPABILITY_WRITE_FS_INFO
378	fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fFS->ops.fsync);
379		// emulated via fsync()
380
381	fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_GET_VNODE, true);
382		// emulated
383
384	// index directory & index operations
385	// missing: FS_VOLUME_CAPABILITY_OPEN_INDEX_DIR
386	// missing: FS_VOLUME_CAPABILITY_CLOSE_INDEX_DIR
387	// missing: FS_VOLUME_CAPABILITY_FREE_INDEX_DIR_COOKIE
388	// missing: FS_VOLUME_CAPABILITY_READ_INDEX_DIR
389	// missing: FS_VOLUME_CAPABILITY_REWIND_INDEX_DIR
390
391	// missing: FS_VOLUME_CAPABILITY_CREATE_INDEX
392	// missing: FS_VOLUME_CAPABILITY_REMOVE_INDEX
393	// missing: FS_VOLUME_CAPABILITY_READ_INDEX_STAT
394
395	// query operations
396	// missing: FS_VOLUME_CAPABILITY_OPEN_QUERY
397	// missing: FS_VOLUME_CAPABILITY_CLOSE_QUERY
398	// missing: FS_VOLUME_CAPABILITY_FREE_QUERY_COOKIE
399	// missing: FS_VOLUME_CAPABILITY_READ_QUERY
400	// missing: FS_VOLUME_CAPABILITY_REWIND_QUERY
401
402	// vnode operations
403	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LOOKUP, true);
404		// emulated
405	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_GET_VNODE_NAME, true);
406		// emulated
407	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_PUT_VNODE, true);
408		// emulated
409	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_VNODE, true);
410		// emulated
411
412	// VM file access
413	// missing: FS_VNODE_CAPABILITY_CAN_PAGE
414	// missing: FS_VNODE_CAPABILITY_READ_PAGES
415	// missing: FS_VNODE_CAPABILITY_WRITE_PAGES
416
417	// cache file access
418	// missing: FS_VNODE_CAPABILITY_GET_FILE_MAP
419
420	// common operations
421	// missing: FS_VNODE_CAPABILITY_IOCTL
422	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_SET_FLAGS, true);
423		// emulated
424	// missing: FS_VNODE_CAPABILITY_SELECT
425	// missing: FS_VNODE_CAPABILITY_DESELECT
426	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FSYNC, fFS->ops.fsync);
427
428	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_SYMLINK, fFS->ops.readlink);
429	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_SYMLINK, fFS->ops.symlink);
430
431	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LINK, fFS->ops.link);
432	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_UNLINK, fFS->ops.unlink);
433	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME, fFS->ops.rename);
434
435	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_ACCESS, fFS->ops.access);
436	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_STAT, fFS->ops.getattr);
437	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_STAT,
438		fFS->ops.chmod != NULL || fFS->ops.chown != NULL
439		|| fFS->ops.truncate != NULL || fFS->ops.utimens != NULL
440		|| fFS->ops.utime != NULL);
441
442	// file operations
443 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE, fFS->ops.create);
444 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN, fFS->ops.open);
445 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE, fFS->ops.flush);
446 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fFS->ops.release);
447 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ, fFS->ops.read);
448 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE, fFS->ops.write);
449
450	// directory operations
451	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fFS->ops.mkdir);
452	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fFS->ops.rmdir);
453	bool readDirSupport = fFS->ops.opendir != NULL || fFS->ops.readdir != NULL
454		|| fFS->ops.getdir;
455	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
456	// not needed: FS_VNODE_CAPABILITY_CLOSE_DIR
457	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
458	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
459 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);
460
461	// attribute directory operations
462	bool hasAttributes = fFS->ops.listxattr != NULL;
463	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, hasAttributes);
464	// not needed: FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR
465	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE,
466		hasAttributes);
467	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR, hasAttributes);
468	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, hasAttributes);
469
470	// attribute operations
471// 	// we emulate open_attr() and free_attr_dir_cookie() if either read_attr()
472// 	// or write_attr() is present
473// 	bool hasAttributes = (fFS->ops.read_attr || fFS->ops.write_attr);
474// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_ATTR, hasAttributes);
475// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR, hasAttributes);
476// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_ATTR, false);
477// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE, hasAttributes);
478// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fFS->ops.read_attr);
479// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fFS->ops.write_attr);
480
481// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT,
482// 		fFS->ops.stat_attr);
483// 	// missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
484// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fFS->ops.rename_attr);
485// 	fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_ATTR, fFS->ops.remove_attr);
486}
487
488
489// #pragma mark - bootstrapping
490
491
492status_t
493userlandfs_create_file_system(const char* fsName, image_id image,
494	FileSystem** _fileSystem)
495{
496printf("userlandfs_create_file_system()\n");
497	// look up the main() function of the add-on
498	int (*mainFunction)(int argc, const char* const* argv);
499	status_t error = get_image_symbol(image, "main", B_SYMBOL_TYPE_TEXT,
500		(void**)&mainFunction);
501	if (error != B_OK)
502		return error;
503printf("userlandfs_create_file_system(): found main: %p\n", mainFunction);
504
505	// create the file system
506	FUSEFileSystem* fileSystem = new(std::nothrow) FUSEFileSystem(fsName,
507		mainFunction);
508	if (fileSystem == NULL)
509		return B_NO_MEMORY;
510
511	*_fileSystem = fileSystem;
512	return B_OK;
513}
514