xref: /aosp_15_r20/external/erofs-utils/fuse/main.c (revision 33b1fccf6a0fada2c2875d400ed01119b7676ee5)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Created by Li Guifu <[email protected]>
4  * Lowlevel added by Li Yiyan <[email protected]>
5  */
6 #include <stdlib.h>
7 #include <string.h>
8 #include <signal.h>
9 #include <libgen.h>
10 #include "macosx.h"
11 #include "erofs/config.h"
12 #include "erofs/print.h"
13 #include "erofs/dir.h"
14 #include "erofs/inode.h"
15 
16 #include <float.h>
17 #include <fuse.h>
18 #include <fuse_lowlevel.h>
19 
20 #define EROFSFUSE_TIMEOUT DBL_MAX
21 
22 struct erofsfuse_readdir_context {
23 	struct erofs_dir_context ctx;
24 
25 	fuse_req_t req;
26 	void *buf;
27 	int is_plus;
28 	size_t index;
29 	size_t buf_rem;
30 	size_t offset;
31 	struct fuse_file_info *fi;
32 };
33 
34 struct erofsfuse_lookupdir_context {
35 	struct erofs_dir_context ctx;
36 
37 	const char *target_name;
38 	struct fuse_entry_param *ent;
39 };
40 
erofsfuse_to_nid(fuse_ino_t ino)41 static inline erofs_nid_t erofsfuse_to_nid(fuse_ino_t ino)
42 {
43 	if (ino == FUSE_ROOT_ID)
44 		return g_sbi.root_nid;
45 	return (erofs_nid_t)(ino - FUSE_ROOT_ID);
46 }
47 
erofsfuse_to_ino(erofs_nid_t nid)48 static inline fuse_ino_t erofsfuse_to_ino(erofs_nid_t nid)
49 {
50 	if (nid == g_sbi.root_nid)
51 		return FUSE_ROOT_ID;
52 	return (nid + FUSE_ROOT_ID);
53 }
54 
erofsfuse_fill_stat(struct erofs_inode * vi,struct stat * stbuf)55 static void erofsfuse_fill_stat(struct erofs_inode *vi, struct stat *stbuf)
56 {
57 	if (S_ISBLK(vi->i_mode) || S_ISCHR(vi->i_mode))
58 		stbuf->st_rdev = vi->u.i_rdev;
59 
60 	stbuf->st_mode = vi->i_mode;
61 	stbuf->st_nlink = vi->i_nlink;
62 	stbuf->st_size = vi->i_size;
63 	stbuf->st_blocks = roundup(vi->i_size, erofs_blksiz(&g_sbi)) >> 9;
64 	stbuf->st_uid = vi->i_uid;
65 	stbuf->st_gid = vi->i_gid;
66 	stbuf->st_ctime = vi->i_mtime;
67 	stbuf->st_mtime = stbuf->st_ctime;
68 	stbuf->st_atime = stbuf->st_ctime;
69 }
70 
erofsfuse_add_dentry(struct erofs_dir_context * ctx)71 static int erofsfuse_add_dentry(struct erofs_dir_context *ctx)
72 {
73 	size_t entsize = 0;
74 	char dname[EROFS_NAME_LEN + 1];
75 	struct erofsfuse_readdir_context *readdir_ctx = (void *)ctx;
76 
77 	if (readdir_ctx->index < readdir_ctx->offset) {
78 		readdir_ctx->index++;
79 		return 0;
80 	}
81 
82 	strncpy(dname, ctx->dname, ctx->de_namelen);
83 	dname[ctx->de_namelen] = '\0';
84 
85 	if (!readdir_ctx->is_plus) { /* fuse 3 still use non-plus readdir */
86 		struct stat st = { 0 };
87 
88 		st.st_mode = erofs_ftype_to_mode(ctx->de_ftype, 0);
89 		st.st_ino = erofsfuse_to_ino(ctx->de_nid);
90 		entsize = fuse_add_direntry(readdir_ctx->req, readdir_ctx->buf,
91 					 readdir_ctx->buf_rem, dname, &st,
92 					 readdir_ctx->index + 1);
93 	} else {
94 #if FUSE_MAJOR_VERSION >= 3
95 		int ret;
96 		struct erofs_inode vi = {
97 			.sbi = &g_sbi,
98 			.nid = ctx->de_nid
99 		};
100 
101 		ret = erofs_read_inode_from_disk(&vi);
102 		if (ret < 0)
103 			return ret;
104 
105 		struct fuse_entry_param param = {
106 			.ino = erofsfuse_to_ino(ctx->de_nid),
107 			.attr.st_ino = erofsfuse_to_ino(ctx->de_nid),
108 			.generation = 0,
109 
110 			.attr_timeout = EROFSFUSE_TIMEOUT,
111 			.entry_timeout = EROFSFUSE_TIMEOUT,
112 		};
113 		erofsfuse_fill_stat(&vi, &(param.attr));
114 
115 		entsize = fuse_add_direntry_plus(readdir_ctx->req,
116 					      readdir_ctx->buf,
117 					      readdir_ctx->buf_rem, dname,
118 					      &param, readdir_ctx->index + 1);
119 #else
120 		return -EOPNOTSUPP;
121 #endif
122 	}
123 
124 	if (entsize > readdir_ctx->buf_rem)
125 		return 1;
126 	readdir_ctx->index++;
127 	readdir_ctx->buf += entsize;
128 	readdir_ctx->buf_rem -= entsize;
129 	return 0;
130 }
131 
erofsfuse_lookup_dentry(struct erofs_dir_context * ctx)132 static int erofsfuse_lookup_dentry(struct erofs_dir_context *ctx)
133 {
134 	struct erofsfuse_lookupdir_context *lookup_ctx = (void *)ctx;
135 
136 	if (lookup_ctx->ent->ino != 0 ||
137 	    strlen(lookup_ctx->target_name) != ctx->de_namelen)
138 		return 0;
139 
140 	if (!strncmp(lookup_ctx->target_name, ctx->dname, ctx->de_namelen)) {
141 		int ret;
142 		struct erofs_inode vi = {
143 			.sbi = &g_sbi,
144 			.nid = (erofs_nid_t)ctx->de_nid,
145 		};
146 
147 		ret = erofs_read_inode_from_disk(&vi);
148 		if (ret < 0)
149 			return ret;
150 
151 		lookup_ctx->ent->ino = erofsfuse_to_ino(ctx->de_nid);
152 		lookup_ctx->ent->attr.st_ino = erofsfuse_to_ino(ctx->de_nid);
153 
154 		erofsfuse_fill_stat(&vi, &(lookup_ctx->ent->attr));
155 	}
156 	return 0;
157 }
158 
erofsfuse_readdir_general(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi,int plus)159 static inline void erofsfuse_readdir_general(fuse_req_t req, fuse_ino_t ino,
160 					     size_t size, off_t off,
161 					     struct fuse_file_info *fi,
162 					     int plus)
163 {
164 	int ret = 0;
165 	char *buf = NULL;
166 	struct erofsfuse_readdir_context ctx = { 0 };
167 	struct erofs_inode *vi = (struct erofs_inode *)fi->fh;
168 
169 	erofs_dbg("readdir(%llu): size: %zu, off: %lu, plus: %d", ino | 0ULL,
170 		  size, off, plus);
171 
172 	buf = malloc(size);
173 	if (!buf) {
174 		fuse_reply_err(req, ENOMEM);
175 		return;
176 	}
177 	ctx.ctx.dir = vi;
178 	ctx.ctx.cb = erofsfuse_add_dentry;
179 
180 	ctx.fi = fi;
181 	ctx.buf = buf;
182 	ctx.buf_rem = size;
183 	ctx.req = req;
184 	ctx.index = 0;
185 	ctx.offset = off;
186 	ctx.is_plus = plus;
187 
188 #ifdef NDEBUG
189 	ret = erofs_iterate_dir(&ctx.ctx, false);
190 #else
191 	ret = erofs_iterate_dir(&ctx.ctx, true);
192 #endif
193 
194 	if (ret < 0) /* if buffer insufficient, return 1 */
195 		fuse_reply_err(req, -ret);
196 	else
197 		fuse_reply_buf(req, buf, size - ctx.buf_rem);
198 
199 	free(buf);
200 }
201 
erofsfuse_readdir(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)202 static void erofsfuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
203 			      off_t off, struct fuse_file_info *fi)
204 {
205 	erofsfuse_readdir_general(req, ino, size, off, fi, 0);
206 }
207 
208 #if FUSE_MAJOR_VERSION >= 3
erofsfuse_readdirplus(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)209 static void erofsfuse_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
210 				  off_t off, struct fuse_file_info *fi)
211 {
212 	erofsfuse_readdir_general(req, ino, size, off, fi, 1);
213 }
214 #endif
215 
erofsfuse_init(void * userdata,struct fuse_conn_info * conn)216 static void erofsfuse_init(void *userdata, struct fuse_conn_info *conn)
217 {
218 	erofs_info("Using FUSE protocol %d.%d", conn->proto_major,
219 		   conn->proto_minor);
220 }
221 
erofsfuse_open(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)222 static void erofsfuse_open(fuse_req_t req, fuse_ino_t ino,
223 			   struct fuse_file_info *fi)
224 {
225 	int ret = 0;
226 	struct erofs_inode *vi;
227 
228 	if (fi->flags & (O_WRONLY | O_RDWR)) {
229 		fuse_reply_err(req, EROFS);
230 		return;
231 	}
232 
233 	vi = (struct erofs_inode *)malloc(sizeof(struct erofs_inode));
234 	if (!vi) {
235 		fuse_reply_err(req, ENOMEM);
236 		return;
237 	}
238 
239 	vi->sbi = &g_sbi;
240 	vi->nid = erofsfuse_to_nid(ino);
241 	ret = erofs_read_inode_from_disk(vi);
242 	if (ret < 0) {
243 		fuse_reply_err(req, -ret);
244 		goto out;
245 	}
246 
247 	if (!S_ISREG(vi->i_mode)) {
248 		fuse_reply_err(req, EISDIR);
249 	} else {
250 		fi->fh = (uint64_t)vi;
251 		fi->keep_cache = 1;
252 		fuse_reply_open(req, fi);
253 		return;
254 	}
255 
256 out:
257 	free(vi);
258 }
259 
erofsfuse_getattr(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)260 static void erofsfuse_getattr(fuse_req_t req, fuse_ino_t ino,
261 			      struct fuse_file_info *fi)
262 {
263 	int ret;
264 	struct stat stbuf = { 0 };
265 	struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) };
266 
267 	ret = erofs_read_inode_from_disk(&vi);
268 	if (ret < 0)
269 		fuse_reply_err(req, -ret);
270 
271 	erofsfuse_fill_stat(&vi, &stbuf);
272 	stbuf.st_ino = ino;
273 
274 	fuse_reply_attr(req, &stbuf, EROFSFUSE_TIMEOUT);
275 }
276 
erofsfuse_opendir(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)277 static void erofsfuse_opendir(fuse_req_t req, fuse_ino_t ino,
278 			      struct fuse_file_info *fi)
279 {
280 	int ret;
281 	struct erofs_inode *vi;
282 
283 	vi = (struct erofs_inode *)malloc(sizeof(struct erofs_inode));
284 	if (!vi) {
285 		fuse_reply_err(req, ENOMEM);
286 		return;
287 	}
288 
289 	vi->sbi = &g_sbi;
290 	vi->nid = erofsfuse_to_nid(ino);
291 	ret = erofs_read_inode_from_disk(vi);
292 	if (ret < 0) {
293 		fuse_reply_err(req, -ret);
294 		goto out;
295 	}
296 
297 	if (!S_ISDIR(vi->i_mode)) {
298 		fuse_reply_err(req, ENOTDIR);
299 		goto out;
300 	}
301 
302 	fi->fh = (uint64_t)vi;
303 	fuse_reply_open(req, fi);
304 	return;
305 
306 out:
307 	free(vi);
308 }
309 
erofsfuse_release(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)310 static void erofsfuse_release(fuse_req_t req, fuse_ino_t ino,
311 			      struct fuse_file_info *fi)
312 {
313 	free((struct erofs_inode *)fi->fh);
314 	fi->fh = 0;
315 	fuse_reply_err(req, 0);
316 }
317 
erofsfuse_lookup(fuse_req_t req,fuse_ino_t parent,const char * name)318 static void erofsfuse_lookup(fuse_req_t req, fuse_ino_t parent,
319 			     const char *name)
320 {
321 	int ret;
322 	struct erofs_inode *vi;
323 	struct fuse_entry_param fentry = { 0 };
324 	struct erofsfuse_lookupdir_context ctx = { 0 };
325 
326 	vi = (struct erofs_inode *)malloc(sizeof(struct erofs_inode));
327 	if (!vi) {
328 		fuse_reply_err(req, ENOMEM);
329 		return;
330 	}
331 
332 	vi->sbi = &g_sbi;
333 	vi->nid = erofsfuse_to_nid(parent);
334 	ret = erofs_read_inode_from_disk(vi);
335 	if (ret < 0) {
336 		fuse_reply_err(req, -ret);
337 		goto out;
338 	}
339 
340 	memset(&fentry, 0, sizeof(fentry));
341 	fentry.ino = 0;
342 	fentry.attr_timeout = fentry.entry_timeout = EROFSFUSE_TIMEOUT;
343 	ctx.ctx.dir = vi;
344 	ctx.ctx.cb = erofsfuse_lookup_dentry;
345 
346 	ctx.ent = &fentry;
347 	ctx.target_name = name;
348 
349 #ifdef NDEBUG
350 	ret = erofs_iterate_dir(&ctx.ctx, false);
351 #else
352 	ret = erofs_iterate_dir(&ctx.ctx, true);
353 #endif
354 
355 	if (ret < 0) {
356 		fuse_reply_err(req, -ret);
357 		goto out;
358 	}
359 	fuse_reply_entry(req, &fentry);
360 
361 out:
362 	free(vi);
363 }
364 
erofsfuse_read(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)365 static void erofsfuse_read(fuse_req_t req, fuse_ino_t ino, size_t size,
366 			   off_t off, struct fuse_file_info *fi)
367 {
368 	int ret;
369 	char *buf = NULL;
370 	struct erofs_inode *vi = (struct erofs_inode *)fi->fh;
371 
372 	erofs_dbg("read(%llu): size = %zu, off = %lu", ino | 0ULL, size, off);
373 
374 	buf = malloc(size);
375 	if (!buf) {
376 		fuse_reply_err(req, ENOMEM);
377 		return;
378 	}
379 
380 	ret = erofs_pread(vi, buf, size, off);
381 	if (ret) {
382 		fuse_reply_err(req, -ret);
383 		goto out;
384 	}
385 	if (off >= vi->i_size)
386 		ret = 0;
387 	else if (off + size > vi->i_size)
388 		ret = vi->i_size - off;
389 	else
390 		ret = size;
391 
392 	fuse_reply_buf(req, buf, ret);
393 
394 out:
395 	free(buf);
396 }
397 
erofsfuse_readlink(fuse_req_t req,fuse_ino_t ino)398 static void erofsfuse_readlink(fuse_req_t req, fuse_ino_t ino)
399 {
400 	int ret;
401 	char *buf = NULL;
402 	struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) };
403 
404 	ret = erofs_read_inode_from_disk(&vi);
405 	if (ret < 0) {
406 		fuse_reply_err(req, -ret);
407 		return;
408 	}
409 
410 	buf = malloc(vi.i_size + 1);
411 	if (!buf) {
412 		fuse_reply_err(req, ENOMEM);
413 		return;
414 	}
415 
416 	ret = erofs_pread(&vi, buf, vi.i_size, 0);
417 	if (ret < 0) {
418 		fuse_reply_err(req, -ret);
419 		goto out;
420 	}
421 
422 	buf[vi.i_size] = '\0';
423 	fuse_reply_readlink(req, buf);
424 
425 out:
426 	free(buf);
427 }
428 
erofsfuse_getxattr(fuse_req_t req,fuse_ino_t ino,const char * name,size_t size,uint32_t position)429 static void erofsfuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
430 			       size_t size
431 #ifdef __APPLE__
432 			       , uint32_t position)
433 #else
434 			       )
435 #endif
436 {
437 	int ret;
438 	char *buf = NULL;
439 	struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) };
440 
441 	erofs_dbg("getattr(%llu): name = %s, size = %zu", ino | 0ULL, name, size);
442 
443 	ret = erofs_read_inode_from_disk(&vi);
444 	if (ret < 0) {
445 		fuse_reply_err(req, -ret);
446 		return;
447 	}
448 
449 	if (size != 0) {
450 		buf = malloc(size);
451 		if (!buf) {
452 			fuse_reply_err(req, ENOMEM);
453 			return;
454 		}
455 	}
456 
457 	ret = erofs_getxattr(&vi, name, buf, size);
458 	if (ret < 0)
459 		fuse_reply_err(req, -ret);
460 	else if (size == 0)
461 		fuse_reply_xattr(req, ret);
462 	else
463 		fuse_reply_buf(req, buf, ret);
464 
465 	free(buf);
466 }
467 
erofsfuse_listxattr(fuse_req_t req,fuse_ino_t ino,size_t size)468 static void erofsfuse_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
469 {
470 	int ret;
471 	char *buf = NULL;
472 	struct erofs_inode vi = { .sbi = &g_sbi, .nid = erofsfuse_to_nid(ino) };
473 
474 	erofs_dbg("listxattr(%llu): size = %zu", ino | 0ULL, size);
475 
476 	ret = erofs_read_inode_from_disk(&vi);
477 	if (ret < 0) {
478 		fuse_reply_err(req, -ret);
479 		return;
480 	}
481 
482 	if (size != 0) {
483 		buf = malloc(size);
484 		if (!buf) {
485 			fuse_reply_err(req, ENOMEM);
486 			return;
487 		}
488 	}
489 
490 	ret = erofs_listxattr(&vi, buf, size);
491 	if (ret < 0)
492 		fuse_reply_err(req, -ret);
493 	else if (size == 0)
494 		fuse_reply_xattr(req, ret);
495 	else
496 		fuse_reply_buf(req, buf, ret);
497 
498 	free(buf);
499 }
500 
501 static struct fuse_lowlevel_ops erofsfuse_lops = {
502 	.getxattr = erofsfuse_getxattr,
503 	.opendir = erofsfuse_opendir,
504 	.releasedir = erofsfuse_release,
505 	.release = erofsfuse_release,
506 	.lookup = erofsfuse_lookup,
507 	.listxattr = erofsfuse_listxattr,
508 	.readlink = erofsfuse_readlink,
509 	.getattr = erofsfuse_getattr,
510 	.readdir = erofsfuse_readdir,
511 #if FUSE_MAJOR_VERSION >= 3
512 	.readdirplus = erofsfuse_readdirplus,
513 #endif
514 	.open = erofsfuse_open,
515 	.read = erofsfuse_read,
516 	.init = erofsfuse_init,
517 };
518 
519 static struct options {
520 	const char *disk;
521 	const char *mountpoint;
522 	u64 offset;
523 	unsigned int debug_lvl;
524 	bool show_help;
525 	bool show_version;
526 	bool odebug;
527 } fusecfg;
528 
529 #define OPTION(t, p) { t, offsetof(struct options, p), 1 }
530 static const struct fuse_opt option_spec[] = {
531 	OPTION("--offset=%lu", offset),
532 	OPTION("--dbglevel=%u", debug_lvl),
533 	OPTION("--help", show_help),
534 	OPTION("--version", show_version),
535 	FUSE_OPT_KEY("--device=", 1),
536 	FUSE_OPT_END
537 };
538 
usage(void)539 static void usage(void)
540 {
541 #if FUSE_MAJOR_VERSION < 3
542 	struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
543 
544 #else
545 	fuse_lowlevel_version();
546 #endif
547 	fputs("usage: [options] IMAGE MOUNTPOINT\n\n"
548 	      "Options:\n"
549 	      "    --offset=#             skip # bytes at the beginning of IMAGE\n"
550 	      "    --dbglevel=#           set output message level to # (maximum 9)\n"
551 	      "    --device=#             specify an extra device to be used together\n"
552 #if FUSE_MAJOR_VERSION < 3
553 	      "    --help                 display this help and exit\n"
554 	      "    --version              display erofsfuse version\n"
555 #endif
556 	      "\n", stderr);
557 
558 #if FUSE_MAJOR_VERSION >= 3
559 	fputs("\nFUSE options:\n", stderr);
560 	fuse_cmdline_help();
561 #else
562 	fuse_opt_add_arg(&args, ""); /* progname */
563 	fuse_opt_add_arg(&args, "-ho"); /* progname */
564 	fuse_parse_cmdline(&args, NULL, NULL, NULL);
565 #endif
566 	exit(EXIT_FAILURE);
567 }
568 
optional_opt_func(void * data,const char * arg,int key,struct fuse_args * outargs)569 static int optional_opt_func(void *data, const char *arg, int key,
570 			     struct fuse_args *outargs)
571 {
572 	int ret;
573 
574 	switch (key) {
575 	case 1:
576 		ret = erofs_blob_open_ro(&g_sbi, arg + sizeof("--device=") - 1);
577 		if (ret)
578 			return -1;
579 		++g_sbi.extra_devices;
580 		return 0;
581 	case FUSE_OPT_KEY_NONOPT:
582 		if (fusecfg.mountpoint)
583 			return -1; /* Too many args */
584 
585 		if (!fusecfg.disk) {
586 			fusecfg.disk = strdup(arg);
587 			return 0;
588 		}
589 		if (!fusecfg.mountpoint)
590 			fusecfg.mountpoint = strdup(arg);
591 	case FUSE_OPT_KEY_OPT:
592 		if (!strcmp(arg, "-d"))
593 			fusecfg.odebug = true;
594 		if (!strcmp(arg, "-h"))
595 			fusecfg.show_help = true;
596 		if (!strcmp(arg, "-V"))
597 			fusecfg.show_version = true;
598 	}
599 	return 1; // keep arg
600 }
601 
602 #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
603 #include <execinfo.h>
604 
signal_handle_sigsegv(int signal)605 static void signal_handle_sigsegv(int signal)
606 {
607 	void *array[10];
608 	size_t nptrs;
609 	char **strings;
610 	size_t i;
611 
612 	erofs_dump("========================================\n");
613 	erofs_dump("Segmentation Fault.  Starting backtrace:\n");
614 	nptrs = backtrace(array, 10);
615 	strings = backtrace_symbols(array, nptrs);
616 	if (strings) {
617 		for (i = 0; i < nptrs; i++)
618 			erofs_dump("%s\n", strings[i]);
619 		free(strings);
620 	}
621 	erofs_dump("========================================\n");
622 	abort();
623 }
624 #endif
625 
626 #define EROFSFUSE_MOUNT_MSG	\
627 	erofs_warn("%s mounted on %s with offset %u",	\
628 		   fusecfg.disk, fusecfg.mountpoint, fusecfg.offset);
629 
main(int argc,char * argv[])630 int main(int argc, char *argv[])
631 {
632 	int ret;
633 	struct fuse_session *se;
634 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
635 #if FUSE_MAJOR_VERSION >= 3
636 	struct fuse_cmdline_opts opts = {};
637 #else
638 	struct fuse_chan *ch;
639 	struct {
640 		char *mountpoint;
641 		int mt, foreground;
642 	} opts = {};
643 #endif
644 
645 	erofs_init_configure();
646 	fusecfg.debug_lvl = cfg.c_dbg_lvl;
647 	printf("erofsfuse %s\n", cfg.c_version);
648 
649 #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
650 	if (signal(SIGSEGV, signal_handle_sigsegv) == SIG_ERR) {
651 		fprintf(stderr, "failed to initialize signals\n");
652 		ret = -errno;
653 		goto err;
654 	}
655 #endif
656 
657 	/* parse options */
658 	ret = fuse_opt_parse(&args, &fusecfg, option_spec, optional_opt_func);
659 	if (ret)
660 		goto err;
661 
662 #if FUSE_MAJOR_VERSION >= 3
663 	ret = fuse_parse_cmdline(&args, &opts);
664 #else
665 	ret = (fuse_parse_cmdline(&args, &opts.mountpoint, &opts.mt,
666 				  &opts.foreground) < 0);
667 #endif
668 	if (ret)
669 		goto err_fuse_free_args;
670 
671 	if (fusecfg.show_help || fusecfg.show_version || !opts.mountpoint)
672 		usage();
673 	cfg.c_dbg_lvl = fusecfg.debug_lvl;
674 
675 	if (fusecfg.odebug && cfg.c_dbg_lvl < EROFS_DBG)
676 		cfg.c_dbg_lvl = EROFS_DBG;
677 
678 	g_sbi.bdev.offset = fusecfg.offset;
679 	ret = erofs_dev_open(&g_sbi, fusecfg.disk, O_RDONLY);
680 	if (ret) {
681 		fprintf(stderr, "failed to open: %s\n", fusecfg.disk);
682 		goto err_fuse_free_args;
683 	}
684 
685 	ret = erofs_read_superblock(&g_sbi);
686 	if (ret) {
687 		fprintf(stderr, "failed to read erofs super block\n");
688 		goto err_dev_close;
689 	}
690 
691 #if FUSE_MAJOR_VERSION >= 3
692 	se = fuse_session_new(&args, &erofsfuse_lops, sizeof(erofsfuse_lops),
693 			      NULL);
694 	if (!se)
695 		goto err_super_put;
696 
697 	if (fuse_session_mount(se, opts.mountpoint) >= 0) {
698 		EROFSFUSE_MOUNT_MSG
699 		if (fuse_daemonize(opts.foreground) >= 0) {
700 			if (fuse_set_signal_handlers(se) >= 0) {
701 				if (opts.singlethread) {
702 					ret = fuse_session_loop(se);
703 				} else {
704 #if FUSE_USE_VERSION == 30
705 					ret = fuse_session_loop_mt(se, opts.clone_fd);
706 #elif FUSE_USE_VERSION == 32
707 					struct fuse_loop_config config = {
708 						.clone_fd = opts.clone_fd,
709 						.max_idle_threads = opts.max_idle_threads
710 					};
711 					ret = fuse_session_loop_mt(se, &config);
712 #else
713 #error "FUSE_USE_VERSION not supported"
714 #endif
715 				}
716 				fuse_remove_signal_handlers(se);
717 			}
718 			fuse_session_unmount(se);
719 			fuse_session_destroy(se);
720 		}
721 	}
722 #else
723 	ch = fuse_mount(opts.mountpoint, &args);
724 	if (!ch)
725 		goto err_super_put;
726 	EROFSFUSE_MOUNT_MSG
727 	se = fuse_lowlevel_new(&args, &erofsfuse_lops, sizeof(erofsfuse_lops),
728 			       NULL);
729 	if (se) {
730 		if (fuse_daemonize(opts.foreground) != -1) {
731 			if (fuse_set_signal_handlers(se) != -1) {
732 				fuse_session_add_chan(se, ch);
733 				if (opts.mt)
734 					ret = fuse_session_loop_mt(se);
735 				else
736 					ret = fuse_session_loop(se);
737 				fuse_remove_signal_handlers(se);
738 				fuse_session_remove_chan(ch);
739 			}
740 		}
741 		fuse_session_destroy(se);
742 	}
743 	fuse_unmount(opts.mountpoint, ch);
744 #endif
745 
746 err_super_put:
747 	erofs_put_super(&g_sbi);
748 err_dev_close:
749 	erofs_blob_closeall(&g_sbi);
750 	erofs_dev_close(&g_sbi);
751 err_fuse_free_args:
752 	free(opts.mountpoint);
753 	fuse_opt_free_args(&args);
754 err:
755 	erofs_exit_configure();
756 	return ret ? EXIT_FAILURE : EXIT_SUCCESS;
757 }
758