xref: /haiku/src/tools/fs_shell/fuse.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 
34 extern fssh_module_info *modules[];
35 
36 extern fssh_file_system_module_info gRootFileSystem;
37 
38 namespace FSShell {
39 
40 const char* kMountPoint = "/myfs";
41 
42 static mode_t sUmask = 0022;
43 
44 #define PRINTD(x) if (gIsDebug) fprintf(stderr, x)
45 
46 bool gIsDebug = false;
47 
48 static fssh_status_t
49 init_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 
113 static void
114 fromFsshStatToStat(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 
137 int
138 fuse_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 
151 static int
152 fuse_access(const char* path, int mask)
153 {
154 	PRINTD("##access\n");
155 	return _ERR(_kern_access(path, mask));
156 }
157 
158 
159 static int
160 fuse_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 
171 static int
172 fuse_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 
203 static int
204 fuse_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 
220 static int
221 fuse_mkdir(const char* path, mode_t mode)
222 {
223 	PRINTD("##mkdir\n");
224 	return _ERR(_kern_create_dir(-1, path, mode));
225 }
226 
227 
228 static int
229 fuse_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 
237 static int
238 fuse_unlink(const char* path)
239 {
240 	PRINTD("##unlink\n");
241 	return _ERR(_kern_unlink(-1, path));
242 }
243 
244 
245 static int
246 fuse_rmdir(const char* path)
247 {
248 	PRINTD("##rmdir\n");
249 	return _ERR(_kern_remove_dir(-1, path));
250 }
251 
252 
253 static int
254 fuse_rename(const char* from, const char* to)
255 {
256 	PRINTD("##rename\n");
257 	return _ERR(_kern_rename(-1, from, -1, to));
258 }
259 
260 
261 static int
262 fuse_link(const char* from, const char* to)
263 {
264 	PRINTD("##link\n");
265 	return _ERR(_kern_create_link(to, from));
266 }
267 
268 
269 static int
270 fuse_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 
280 static int
281 fuse_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 
292 static int
293 fuse_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 
308 static int
309 fuse_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 
326 static int
327 fuse_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 
344 static void
345 fuse_destroy(void* priv_data)
346 {
347 	_kern_sync();
348 }
349 
350 
351 static fssh_dev_t
352 get_volume_id()
353 {
354 	struct fssh_stat st;
355 	fssh_status_t error = _kern_read_stat(-1, kMountPoint, false, &st,
356 		sizeof(st));
357 	if (error != FSSH_B_OK)
358 		return error;
359 	return st.fssh_st_dev;
360 }
361 
362 
363 static int
364 fuse_statfs(const char *path __attribute__((unused)),
365                             struct statvfs *sfs)
366 {
367 	PRINTD("##statfs\n");
368 
369 	fssh_dev_t volumeID = get_volume_id();
370 	if (volumeID < 0)
371 		return _ERR(volumeID);
372 
373 	fssh_fs_info info;
374 	fssh_status_t status = _kern_read_fs_info(volumeID, &info);
375 	if (status != FSSH_B_OK)
376 		return _ERR(status);
377 
378 	sfs->f_bsize = sfs->f_frsize = info.block_size;
379 	sfs->f_blocks = info.total_blocks;
380 	sfs->f_bavail = sfs->f_bfree = info.free_blocks;
381 
382 	return 0;
383 }
384 
385 
386 struct fuse_operations gFUSEOperations;
387 
388 
389 static void
390 initialiseFuseOps(struct fuse_operations* fuseOps)
391 {
392 	fuseOps->getattr	= fuse_getattr;
393 	fuseOps->access		= fuse_access;
394 	fuseOps->readlink	= fuse_readlink;
395 	fuseOps->readdir	= fuse_readdir;
396 	fuseOps->mknod		= fuse_mknod;
397 	fuseOps->mkdir		= fuse_mkdir;
398 	fuseOps->symlink	= fuse_symlink;
399 	fuseOps->unlink		= fuse_unlink;
400 	fuseOps->rmdir		= fuse_rmdir;
401 	fuseOps->rename		= fuse_rename;
402 	fuseOps->link		= fuse_link;
403 	fuseOps->chmod		= fuse_chmod;
404 	fuseOps->chown		= fuse_chown;
405 	fuseOps->truncate	= NULL;
406 	fuseOps->utimens	= NULL;
407 	fuseOps->open		= fuse_open;
408 	fuseOps->read		= fuse_read;
409 	fuseOps->write		= fuse_write;
410 	fuseOps->statfs		= fuse_statfs;
411 	fuseOps->release	= NULL;
412 	fuseOps->fsync		= NULL;
413 	fuseOps->destroy	= fuse_destroy;
414 }
415 
416 
417 static int
418 mount_volume(const char* device, const char* mntPoint, const char* fsName)
419 {
420 	// Mount the volume in the root FS.
421 	fssh_dev_t fsDev = _kern_mount(kMountPoint, device, fsName, 0, NULL, 0);
422 	if (fsDev < 0) {
423 		fprintf(stderr, "Error: Mounting FS failed: %s\n",
424 			fssh_strerror(fsDev));
425 		return 1;
426 	}
427 
428 	if (!gIsDebug) {
429 		bool isErr = false;
430 		fssh_dev_t volumeID = get_volume_id();
431 		if (volumeID < 0)
432 			isErr = true;
433 		fssh_fs_info info;
434 		if (!isErr) {
435 			fssh_status_t status = _kern_read_fs_info(volumeID, &info);
436 			if (status != FSSH_B_OK)
437 				isErr = true;
438 		}
439 		syslog(LOG_INFO, "Mounted %s (%s) to %s",
440 			device,
441 			isErr ? "unknown" : info.volume_name,
442 			mntPoint);
443 	}
444 
445 	return 0;
446 }
447 
448 
449 static int
450 unmount_volume(const char* device, const char* mntPoint)
451 {
452 	// Unmount the volume again.
453 	// Avoid a "busy" vnode.
454 	_kern_setcwd(-1, "/");
455 	fssh_status_t error = _kern_unmount(kMountPoint, 0);
456 	if (error != FSSH_B_OK) {
457 		if (gIsDebug)
458 			fprintf(stderr, "Error: Unmounting FS failed: %s\n",
459 				fssh_strerror(error));
460 		else
461 			syslog(LOG_INFO, "Error: Unmounting FS failed: %s",
462 				fssh_strerror(error));
463 		return 1;
464 	}
465 
466 	if (!gIsDebug)
467 		syslog(LOG_INFO, "UnMounted %s from %s", device, mntPoint);
468 
469 	return 0;
470 }
471 
472 
473 static int
474 fssh_fuse_session(const char* device, const char* mntPoint, const char* fsName,
475 	struct fuse_args& fuseArgs)
476 {
477 	int ret;
478 
479 	ret = mount_volume(device, mntPoint, fsName);
480 	if (ret != 0)
481 		return ret;
482 
483 	if (getuid() == 0 && geteuid() == 0 && getgid() == 0 && getegid() == 0) {
484 		// only add FUSE options when user is root
485 
486 		char* fuseOptions = NULL;
487 
488 		// default FUSE options
489 		char* fsNameOption = NULL;
490 		if (fuse_opt_add_opt(&fuseOptions, "allow_other") < 0
491 			|| asprintf(&fsNameOption, "fsname=%s", device) < 0
492 			|| fuse_opt_add_opt(&fuseOptions, fsNameOption) < 0) {
493 			unmount_volume(device, mntPoint);
494 			return 1;
495 		}
496 
497 		struct stat sbuf;
498 		if ((stat(device, &sbuf) == 0) && S_ISBLK(sbuf.st_mode)) {
499 			int blkSize = 512;
500 			fssh_dev_t volumeID = get_volume_id();
501 			if (volumeID >= 0) {
502 				fssh_fs_info info;
503 				if (_kern_read_fs_info(volumeID, &info) == FSSH_B_OK)
504 					blkSize = info.block_size;
505 			}
506 
507 			char* blkSizeOption = NULL;
508 			if (fuse_opt_add_opt(&fuseOptions, "blkdev") < 0
509 				|| asprintf(&blkSizeOption, "blksize=%i", blkSize) < 0
510 				|| fuse_opt_add_opt(&fuseOptions, blkSizeOption) < 0) {
511 				unmount_volume(device, mntPoint);
512 				return 1;
513 			}
514 		}
515 
516 		if (fuse_opt_add_arg(&fuseArgs, "-o") < 0
517 			|| fuse_opt_add_arg(&fuseArgs, fuseOptions) < 0) {
518 			unmount_volume(device, mntPoint);
519 			return 1;
520 		}
521 	}
522 
523  	// Run the fuse_main() loop.
524 	if (fuse_opt_add_arg(&fuseArgs, "-s") < 0) {
525 		unmount_volume(device, mntPoint);
526 		return 1;
527 	}
528 
529 	initialiseFuseOps(&gFUSEOperations);
530 
531 	int res = fuse_main(fuseArgs.argc, fuseArgs.argv, &gFUSEOperations, NULL);
532 
533 	ret = unmount_volume(device, mntPoint);
534 	if (ret != 0)
535 		return ret;
536 
537 	return res;
538 }
539 
540 
541 }	// namespace FSShell
542 
543 
544 using namespace FSShell;
545 
546 
547 static void
548 print_usage_and_exit(const char* binName)
549 {
550 	fprintf(stderr,"Usage: %s [-d] <device> <mount point>\n", binName);
551 	exit(1);
552 }
553 
554 
555 struct FsConfig {
556 	const char* device;
557 	const char* mntPoint;
558 };
559 
560 
561 enum {
562 	KEY_DEBUG,
563 	KEY_HELP
564 };
565 
566 
567 static int
568 process_options(void* data, const char* arg, int key, struct fuse_args* outArgs)
569 {
570 	struct FsConfig* config = (FsConfig*) data;
571 
572 	switch (key) {
573 		case FUSE_OPT_KEY_NONOPT:
574 			if (!config->device) {
575 				config->device = arg;
576 				return 0;
577 					// don't pass the device path to fuse_main()
578 			} else if (!config->mntPoint)
579 				config->mntPoint = arg;
580 			else
581 				print_usage_and_exit(outArgs->argv[0]);
582 			break;
583 		case KEY_DEBUG:
584 			gIsDebug = true;
585 			break;
586 		case KEY_HELP:
587 			print_usage_and_exit(outArgs->argv[0]);
588 	}
589 
590 	return 1;
591 }
592 
593 
594 int
595 main(int argc, char* argv[])
596 {
597 	struct fuse_args fuseArgs = FUSE_ARGS_INIT(argc, argv);
598 	struct FsConfig config;
599 	memset(&config, 0, sizeof(config));
600 	const struct fuse_opt fsOptions[] = {
601 		FUSE_OPT_KEY("uhelper=",	FUSE_OPT_KEY_DISCARD),
602 			// fuse_main() throws an error about this unknown option
603 			// TODO: do not use fuse_main to mount filesystem, instead use
604 			// fuse_mount, fuse_new, fuse_set_signal_handlers and fuse_loop
605 		FUSE_OPT_KEY("-d",			KEY_DEBUG),
606 		FUSE_OPT_KEY("-h",			KEY_HELP),
607 		FUSE_OPT_KEY("--help",		KEY_HELP),
608 		FUSE_OPT_END
609 	};
610 
611 	if (fuse_opt_parse(&fuseArgs, &config, fsOptions, process_options) < 0)
612 		return 1;
613 
614 	if (!config.mntPoint)
615 		print_usage_and_exit(fuseArgs.argv[0]);
616 
617 	if (!modules[0]) {
618 		fprintf(stderr, "Error: Couldn't find FS module!\n");
619 		return 1;
620 	}
621 
622 	fssh_status_t error = init_kernel();
623 	if (error != FSSH_B_OK) {
624 		fprintf(stderr, "Error: Initializing kernel failed: %s\n",
625 			fssh_strerror(error));
626 		return error;
627 	}
628 
629 	const char* fsName = modules[0]->name;
630 	return fssh_fuse_session(config.device, config.mntPoint, fsName, fuseArgs);
631 }
632 
633