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