xref: /haiku/src/tools/fs_shell/fuse.cpp (revision 51555e078489f75105f30f5bb64a01d7206b2465)
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
init_kernel()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
fromFsshStatToStat(struct fssh_stat * f_stbuf,struct stat * stbuf)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
fuse_getattr(const char * path,struct stat * stbuf)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
fuse_access(const char * path,int mask)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
fuse_readlink(const char * path,char * buffer,size_t size)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
fuse_readdir(const char * path,void * buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info * fi)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
fuse_mknod(const char * path,mode_t mode,dev_t rdev)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
fuse_mkdir(const char * path,mode_t mode)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
fuse_symlink(const char * from,const char * to)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
fuse_unlink(const char * path)238 fuse_unlink(const char* path)
239 {
240 	PRINTD("##unlink\n");
241 	return _ERR(_kern_unlink(-1, path));
242 }
243 
244 
245 static int
fuse_rmdir(const char * path)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
fuse_rename(const char * from,const char * to)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
fuse_link(const char * from,const char * to)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
fuse_chmod(const char * path,mode_t mode)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
fuse_chown(const char * path,uid_t uid,gid_t gid)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
fuse_open(const char * path,struct fuse_file_info * fi)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
fuse_read(const char * path,char * buf,size_t size,off_t offset,struct fuse_file_info * fi)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
fuse_write(const char * path,const char * buf,size_t size,off_t offset,struct fuse_file_info * fi)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 int
fuse_truncate(const char * path,off_t off)345 fuse_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 
358 static void
fuse_destroy(void * priv_data)359 fuse_destroy(void* priv_data)
360 {
361 	_kern_sync();
362 }
363 
364 
365 static fssh_dev_t
get_volume_id()366 get_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 
377 static int
fuse_statfs(const char * path,struct statvfs * sfs)378 fuse_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 
400 struct fuse_operations gFUSEOperations;
401 
402 
403 static void
initialiseFuseOps(struct fuse_operations * fuseOps)404 initialiseFuseOps(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 
431 static int
mount_volume(const char * device,const char * mntPoint,const char * fsName)432 mount_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 
463 static int
unmount_volume(const char * device,const char * mntPoint)464 unmount_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 
487 static int
fssh_fuse_session(const char * device,const char * mntPoint,const char * fsName,struct fuse_args & fuseArgs)488 fssh_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 
558 using namespace FSShell;
559 
560 
561 static void
print_usage_and_exit(const char * binName)562 print_usage_and_exit(const char* binName)
563 {
564 	fprintf(stderr,"Usage: %s [-d] <device> <mount point>\n", binName);
565 	exit(1);
566 }
567 
568 
569 struct FsConfig {
570 	const char* device;
571 	const char* mntPoint;
572 };
573 
574 
575 enum {
576 	KEY_DEBUG,
577 	KEY_HELP
578 };
579 
580 
581 static int
process_options(void * data,const char * arg,int key,struct fuse_args * outArgs)582 process_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 
608 int
main(int argc,char * argv[])609 main(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