1/*
2 * Copyright 2009, Raghuram Nagireddy <raghuram87@gmail.com>.
3 * Distributed under the terms of the MIT License.
4 */
5
6#define FUSE_USE_VERSION 27
7
8#include <fuse/fuse.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <syslog.h>
12#include <unistd.h>
13
14#include "fssh.h"
15
16#include "driver_settings.h"
17#include "external_commands.h"
18#include "fd.h"
19#include "fssh_dirent.h"
20#include "fssh_errno.h"
21#include "fssh_errors.h"
22#include "fssh_fcntl.h"
23#include "fssh_fs_info.h"
24#include "fssh_module.h"
25#include "fssh_node_monitor.h"
26#include "fssh_stat.h"
27#include "fssh_string.h"
28#include "fssh_type_constants.h"
29#include "module.h"
30#include "syscalls.h"
31#include "vfs.h"
32
33
34extern fssh_module_info *modules[];
35
36extern fssh_file_system_module_info gRootFileSystem;
37
38namespace FSShell {
39
40const char* kMountPoint = "/myfs";
41
42static mode_t sUmask = 0022;
43
44#define PRINTD(x) if (gIsDebug) fprintf(stderr, x)
45
46bool gIsDebug = false;
47
48static fssh_status_t
49init_kernel()
50{
51	fssh_status_t error;
52
53	// init module subsystem
54	error = module_init(NULL);
55	if (error != FSSH_B_OK) {
56		fprintf(stderr, "module_init() failed: %s\n", fssh_strerror(error));
57		return error;
58	}
59
60	// init driver settings
61	error = driver_settings_init();
62	if (error != FSSH_B_OK) {
63		fprintf(stderr, "initializing driver settings failed: %s\n",
64			fssh_strerror(error));
65		return error;
66	}
67
68	// register built-in modules, i.e. the rootfs and the client FS
69	register_builtin_module(&gRootFileSystem.info);
70	for (int i = 0; modules[i]; i++)
71		register_builtin_module(modules[i]);
72
73	// init VFS
74	error = vfs_init(NULL);
75	if (error != FSSH_B_OK) {
76		fprintf(stderr, "initializing VFS failed: %s\n", fssh_strerror(error));
77		return error;
78	}
79
80	// init kernel IO context
81	gKernelIOContext = (io_context*)vfs_new_io_context(NULL);
82	if (!gKernelIOContext) {
83		fprintf(stderr, "creating IO context failed!\n");
84		return FSSH_B_NO_MEMORY;
85	}
86
87	// mount root FS
88	fssh_dev_t rootDev = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
89	if (rootDev < 0) {
90		fprintf(stderr, "mounting rootfs failed: %s\n", fssh_strerror(rootDev));
91		return rootDev;
92	}
93
94	// set cwd to "/"
95	error = _kern_setcwd(-1, "/");
96	if (error != FSSH_B_OK) {
97		fprintf(stderr, "setting cwd failed: %s\n", fssh_strerror(error));
98		return error;
99	}
100
101	// create mount point for the client FS
102	error = _kern_create_dir(-1, kMountPoint, 0775);
103	if (error != FSSH_B_OK) {
104		fprintf(stderr, "creating mount point failed: %s\n",
105			fssh_strerror(error));
106		return error;
107	}
108
109	return FSSH_B_OK;
110}
111
112
113static void
114fromFsshStatToStat(struct fssh_stat* f_stbuf, struct stat* stbuf)
115{
116	stbuf->st_dev = f_stbuf->fssh_st_dev;
117	stbuf->st_ino = f_stbuf->fssh_st_ino;
118	stbuf->st_mode = f_stbuf->fssh_st_mode;
119	stbuf->st_nlink = f_stbuf->fssh_st_nlink;
120	stbuf->st_uid = f_stbuf->fssh_st_uid;
121	stbuf->st_gid = f_stbuf->fssh_st_gid;
122	stbuf->st_rdev = f_stbuf->fssh_st_rdev;
123	stbuf->st_size = f_stbuf->fssh_st_size;
124	stbuf->st_blksize = f_stbuf->fssh_st_blksize;
125	stbuf->st_blocks = f_stbuf->fssh_st_blocks;
126	stbuf->st_atime = f_stbuf->fssh_st_atime;
127	stbuf->st_mtime = f_stbuf->fssh_st_mtime;
128	stbuf->st_ctime = f_stbuf->fssh_st_ctime;
129}
130
131#define _ERR(x) (-1 * fssh_to_host_error(x))
132
133
134// pragma mark - FUSE functions
135
136
137int
138fuse_getattr(const char* path, struct stat* stbuf)
139{
140	PRINTD("##getattr\n");
141	struct fssh_stat f_stbuf;
142	fssh_status_t status = _kern_read_stat(-1, path, false, &f_stbuf,
143			sizeof(f_stbuf));
144	fromFsshStatToStat(&f_stbuf, stbuf);
145	if (gIsDebug)
146		printf("GETATTR returned: %d\n", status);
147	return _ERR(status);
148}
149
150
151static int
152fuse_access(const char* path, int mask)
153{
154	PRINTD("##access\n");
155	return _ERR(_kern_access(path, mask));
156}
157
158
159static int
160fuse_readlink(const char* path, char* buffer, size_t size)
161{
162	PRINTD("##readlink\n");
163	fssh_size_t n_size = size - 1;
164	fssh_status_t st = _kern_read_link(-1, path, buffer, &n_size);
165	if (st >= FSSH_B_OK)
166		buffer[n_size] = '\0';
167	return _ERR(st);
168}
169
170
171static int
172fuse_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
173	off_t offset, struct fuse_file_info* fi)
174{
175	PRINTD("##readdir\n");
176	int dfp = _kern_open_dir(-1, path);
177	if (dfp < FSSH_B_OK)
178		return _ERR(dfp);
179
180	fssh_ssize_t entriesRead = 0;
181	struct fssh_stat f_st;
182	struct stat st;
183	char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
184	fssh_dirent* dirEntry = (fssh_dirent*)buffer;
185	while ((entriesRead = _kern_read_dir(dfp, dirEntry,
186			sizeof(buffer), 1)) == 1) {
187		fssh_memset(&st, 0, sizeof(st));
188		fssh_memset(&f_st, 0, sizeof(f_st));
189		fssh_status_t status = _kern_read_stat(dfp, dirEntry->d_name,
190			false, &f_st, sizeof(f_st));
191		if (status >= FSSH_B_OK) {
192			fromFsshStatToStat(&f_st, &st);
193			if (filler(buf, dirEntry->d_name, &st, 0))
194				break;
195		}
196	}
197	_kern_close(dfp);
198	//TODO: check _kern_close
199	return 0;
200}
201
202
203static int
204fuse_mknod(const char* path, mode_t mode, dev_t rdev)
205{
206	PRINTD("##mknod\n");
207	if (S_ISREG(mode)) {
208		int fd = _kern_open(-1, path,
209			FSSH_O_CREAT | FSSH_O_EXCL | FSSH_O_WRONLY, mode);
210		if (fd >= FSSH_B_OK)
211			return _ERR(_kern_close(fd));
212		return _ERR(fd);
213	} else if (S_ISFIFO(mode))
214		return _ERR(FSSH_EINVAL);
215	else
216		return _ERR(FSSH_EINVAL);
217}
218
219
220static int
221fuse_mkdir(const char* path, mode_t mode)
222{
223	PRINTD("##mkdir\n");
224	return _ERR(_kern_create_dir(-1, path, mode));
225}
226
227
228static int
229fuse_symlink(const char* from, const char* to)
230{
231	PRINTD("##symlink\n");
232	return _ERR(_kern_create_symlink(-1, to, from,
233		FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO));
234}
235
236
237static int
238fuse_unlink(const char* path)
239{
240	PRINTD("##unlink\n");
241	return _ERR(_kern_unlink(-1, path));
242}
243
244
245static int
246fuse_rmdir(const char* path)
247{
248	PRINTD("##rmdir\n");
249	return _ERR(_kern_remove_dir(-1, path));
250}
251
252
253static int
254fuse_rename(const char* from, const char* to)
255{
256	PRINTD("##rename\n");
257	return _ERR(_kern_rename(-1, from, -1, to));
258}
259
260
261static int
262fuse_link(const char* from, const char* to)
263{
264	PRINTD("##link\n");
265	return _ERR(_kern_create_link(to, from));
266}
267
268
269static int
270fuse_chmod(const char* path, mode_t mode)
271{
272	PRINTD("##chmod\n");
273	fssh_struct_stat st;
274	st.fssh_st_mode = mode;
275	return _ERR(_kern_write_stat(-1, path, false, &st, sizeof(st),
276			FSSH_B_STAT_MODE));
277}
278
279
280static int
281fuse_chown(const char* path, uid_t uid, gid_t gid)
282{
283	PRINTD("##chown\n");
284	fssh_struct_stat st;
285	st.fssh_st_uid = uid;
286	st.fssh_st_gid = gid;
287	return _ERR(_kern_write_stat(-1, path, false, &st, sizeof(st),
288			FSSH_B_STAT_UID|FSSH_B_STAT_GID));
289}
290
291
292static int
293fuse_open(const char* path, struct fuse_file_info* fi)
294{
295	PRINTD("##open\n");
296	// TODO: Do we have a syscall similar to the open syscall in linux which
297	// takes only two args: path and flags with no mask/perms?
298	int fd = _kern_open(-1, path, fi->flags,
299		(FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
300	_kern_close(fd);
301	if (fd < FSSH_B_OK)
302		return _ERR(fd);
303	else
304		return 0;
305}
306
307
308static int
309fuse_read(const char* path, char* buf, size_t size, off_t offset,
310	struct fuse_file_info* fi)
311{
312	PRINTD("##read\n");
313	int fd = _kern_open(-1, path, FSSH_O_RDONLY,
314		(FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
315	if (fd < FSSH_B_OK)
316		return _ERR(fd);
317
318	int res = _kern_read(fd, offset, buf, size);
319	_kern_close(fd);
320	if (res < FSSH_B_OK)
321		res = _ERR(res);
322	return res;
323}
324
325
326static int
327fuse_write(const char* path, const char* buf, size_t size, off_t offset,
328	struct fuse_file_info* fi)
329{
330	PRINTD("##write\n");
331	int fd = _kern_open(-1, path, FSSH_O_WRONLY,
332		(FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
333	if (fd < FSSH_B_OK)
334		return _ERR(fd);
335
336	int res = _kern_write(fd, offset, buf, size);
337	_kern_close(fd);
338	if (res < FSSH_B_OK)
339		res = _ERR(res);
340	return res;
341}
342
343
344static int
345fuse_truncate(const char *path, off_t off)
346{
347	PRINTD("##truncate\n");
348
349	struct fssh_stat st;
350	st.fssh_st_size = off;
351	fssh_status_t res = _kern_write_stat(-1, path, true, &st, sizeof(st), FSSH_B_STAT_SIZE);
352	if (res < FSSH_B_OK)
353		return _ERR(res);
354	return 0;
355}
356
357
358static void
359fuse_destroy(void* priv_data)
360{
361	_kern_sync();
362}
363
364
365static fssh_dev_t
366get_volume_id()
367{
368	struct fssh_stat st;
369	fssh_status_t error = _kern_read_stat(-1, kMountPoint, false, &st,
370		sizeof(st));
371	if (error != FSSH_B_OK)
372		return error;
373	return st.fssh_st_dev;
374}
375
376
377static int
378fuse_statfs(const char *path __attribute__((unused)),
379                            struct statvfs *sfs)
380{
381	PRINTD("##statfs\n");
382
383	fssh_dev_t volumeID = get_volume_id();
384	if (volumeID < 0)
385		return _ERR(volumeID);
386
387	fssh_fs_info info;
388	fssh_status_t status = _kern_read_fs_info(volumeID, &info);
389	if (status != FSSH_B_OK)
390		return _ERR(status);
391
392	sfs->f_bsize = sfs->f_frsize = info.block_size;
393	sfs->f_blocks = info.total_blocks;
394	sfs->f_bavail = sfs->f_bfree = info.free_blocks;
395
396	return 0;
397}
398
399
400struct fuse_operations gFUSEOperations;
401
402
403static void
404initialiseFuseOps(struct fuse_operations* fuseOps)
405{
406	fuseOps->getattr	= fuse_getattr;
407	fuseOps->access		= fuse_access;
408	fuseOps->readlink	= fuse_readlink;
409	fuseOps->readdir	= fuse_readdir;
410	fuseOps->mknod		= fuse_mknod;
411	fuseOps->mkdir		= fuse_mkdir;
412	fuseOps->symlink	= fuse_symlink;
413	fuseOps->unlink		= fuse_unlink;
414	fuseOps->rmdir		= fuse_rmdir;
415	fuseOps->rename		= fuse_rename;
416	fuseOps->link		= fuse_link;
417	fuseOps->chmod		= fuse_chmod;
418	fuseOps->chown		= fuse_chown;
419	fuseOps->truncate	= fuse_truncate;
420	fuseOps->utimens	= NULL;
421	fuseOps->open		= fuse_open;
422	fuseOps->read		= fuse_read;
423	fuseOps->write		= fuse_write;
424	fuseOps->statfs		= fuse_statfs;
425	fuseOps->release	= NULL;
426	fuseOps->fsync		= NULL;
427	fuseOps->destroy	= fuse_destroy;
428}
429
430
431static int
432mount_volume(const char* device, const char* mntPoint, const char* fsName)
433{
434	// Mount the volume in the root FS.
435	fssh_dev_t fsDev = _kern_mount(kMountPoint, device, fsName, 0, NULL, 0);
436	if (fsDev < 0) {
437		fprintf(stderr, "Error: Mounting FS failed: %s\n",
438			fssh_strerror(fsDev));
439		return 1;
440	}
441
442	if (!gIsDebug) {
443		bool isErr = false;
444		fssh_dev_t volumeID = get_volume_id();
445		if (volumeID < 0)
446			isErr = true;
447		fssh_fs_info info;
448		if (!isErr) {
449			fssh_status_t status = _kern_read_fs_info(volumeID, &info);
450			if (status != FSSH_B_OK)
451				isErr = true;
452		}
453		syslog(LOG_INFO, "Mounted %s (%s) to %s",
454			device,
455			isErr ? "unknown" : info.volume_name,
456			mntPoint);
457	}
458
459	return 0;
460}
461
462
463static int
464unmount_volume(const char* device, const char* mntPoint)
465{
466	// Unmount the volume again.
467	// Avoid a "busy" vnode.
468	_kern_setcwd(-1, "/");
469	fssh_status_t error = _kern_unmount(kMountPoint, 0);
470	if (error != FSSH_B_OK) {
471		if (gIsDebug)
472			fprintf(stderr, "Error: Unmounting FS failed: %s\n",
473				fssh_strerror(error));
474		else
475			syslog(LOG_INFO, "Error: Unmounting FS failed: %s",
476				fssh_strerror(error));
477		return 1;
478	}
479
480	if (!gIsDebug)
481		syslog(LOG_INFO, "UnMounted %s from %s", device, mntPoint);
482
483	return 0;
484}
485
486
487static int
488fssh_fuse_session(const char* device, const char* mntPoint, const char* fsName,
489	struct fuse_args& fuseArgs)
490{
491	int ret;
492
493	ret = mount_volume(device, mntPoint, fsName);
494	if (ret != 0)
495		return ret;
496
497	if (getuid() == 0 && geteuid() == 0 && getgid() == 0 && getegid() == 0) {
498		// only add FUSE options when user is root
499
500		char* fuseOptions = NULL;
501
502		// default FUSE options
503		char* fsNameOption = NULL;
504		if (fuse_opt_add_opt(&fuseOptions, "allow_other") < 0
505			|| asprintf(&fsNameOption, "fsname=%s", device) < 0
506			|| fuse_opt_add_opt(&fuseOptions, fsNameOption) < 0) {
507			unmount_volume(device, mntPoint);
508			return 1;
509		}
510
511		struct stat sbuf;
512		if ((stat(device, &sbuf) == 0) && S_ISBLK(sbuf.st_mode)) {
513			int blkSize = 512;
514			fssh_dev_t volumeID = get_volume_id();
515			if (volumeID >= 0) {
516				fssh_fs_info info;
517				if (_kern_read_fs_info(volumeID, &info) == FSSH_B_OK)
518					blkSize = info.block_size;
519			}
520
521			char* blkSizeOption = NULL;
522			if (fuse_opt_add_opt(&fuseOptions, "blkdev") < 0
523				|| asprintf(&blkSizeOption, "blksize=%i", blkSize) < 0
524				|| fuse_opt_add_opt(&fuseOptions, blkSizeOption) < 0) {
525				unmount_volume(device, mntPoint);
526				return 1;
527			}
528		}
529
530		if (fuse_opt_add_arg(&fuseArgs, "-o") < 0
531			|| fuse_opt_add_arg(&fuseArgs, fuseOptions) < 0) {
532			unmount_volume(device, mntPoint);
533			return 1;
534		}
535	}
536
537 	// Run the fuse_main() loop.
538	if (fuse_opt_add_arg(&fuseArgs, "-s") < 0) {
539		unmount_volume(device, mntPoint);
540		return 1;
541	}
542
543	initialiseFuseOps(&gFUSEOperations);
544
545	int res = fuse_main(fuseArgs.argc, fuseArgs.argv, &gFUSEOperations, NULL);
546
547	ret = unmount_volume(device, mntPoint);
548	if (ret != 0)
549		return ret;
550
551	return res;
552}
553
554
555}	// namespace FSShell
556
557
558using namespace FSShell;
559
560
561static void
562print_usage_and_exit(const char* binName)
563{
564	fprintf(stderr,"Usage: %s [-d] <device> <mount point>\n", binName);
565	exit(1);
566}
567
568
569struct FsConfig {
570	const char* device;
571	const char* mntPoint;
572};
573
574
575enum {
576	KEY_DEBUG,
577	KEY_HELP
578};
579
580
581static int
582process_options(void* data, const char* arg, int key, struct fuse_args* outArgs)
583{
584	struct FsConfig* config = (FsConfig*) data;
585
586	switch (key) {
587		case FUSE_OPT_KEY_NONOPT:
588			if (!config->device) {
589				config->device = arg;
590				return 0;
591					// don't pass the device path to fuse_main()
592			} else if (!config->mntPoint)
593				config->mntPoint = arg;
594			else
595				print_usage_and_exit(outArgs->argv[0]);
596			break;
597		case KEY_DEBUG:
598			gIsDebug = true;
599			break;
600		case KEY_HELP:
601			print_usage_and_exit(outArgs->argv[0]);
602	}
603
604	return 1;
605}
606
607
608int
609main(int argc, char* argv[])
610{
611	struct fuse_args fuseArgs = FUSE_ARGS_INIT(argc, argv);
612	struct FsConfig config;
613	memset(&config, 0, sizeof(config));
614	const struct fuse_opt fsOptions[] = {
615		FUSE_OPT_KEY("uhelper=",	FUSE_OPT_KEY_DISCARD),
616			// fuse_main() throws an error about this unknown option
617			// TODO: do not use fuse_main to mount filesystem, instead use
618			// fuse_mount, fuse_new, fuse_set_signal_handlers and fuse_loop
619		FUSE_OPT_KEY("-d",			KEY_DEBUG),
620		FUSE_OPT_KEY("-h",			KEY_HELP),
621		FUSE_OPT_KEY("--help",		KEY_HELP),
622		FUSE_OPT_END
623	};
624
625	if (fuse_opt_parse(&fuseArgs, &config, fsOptions, process_options) < 0)
626		return 1;
627
628	if (!config.mntPoint)
629		print_usage_and_exit(fuseArgs.argv[0]);
630
631	if (!modules[0]) {
632		fprintf(stderr, "Error: Couldn't find FS module!\n");
633		return 1;
634	}
635
636	fssh_status_t error = init_kernel();
637	if (error != FSSH_B_OK) {
638		fprintf(stderr, "Error: Initializing kernel failed: %s\n",
639			fssh_strerror(error));
640		return error;
641	}
642
643	const char* fsName = modules[0]->name;
644	return fssh_fuse_session(config.device, config.mntPoint, fsName, fuseArgs);
645}
646
647