1 /*
2  * Copyright (c) 2019 Jan Van Winkel <jan.van_winkel@dxplore.eu>
3  * Copyright (c) 2025 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #if defined(CONFIG_FUSE_LIBRARY_V3)
9 #define FUSE_USE_VERSION 30
10 #else
11 #define FUSE_USE_VERSION 26
12 #endif
13 
14 #undef _XOPEN_SOURCE
15 #define _XOPEN_SOURCE 700
16 
17 #include <stddef.h>
18 #include <stdbool.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <pthread.h>
25 #include <semaphore.h>
26 #include <fuse.h>
27 #include <libgen.h>
28 #include <linux/limits.h>
29 #include <unistd.h>
30 #include <sys/mount.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <nsi_tracing.h>
35 #include <nsi_utils.h>
36 #include <nsi_errno.h>
37 #include "fuse_fs_access_bottom.h"
38 
39 
40 #define S_IRWX_DIR (0775)
41 #define S_IRW_FILE (0664)
42 
43 #define DIR_END '\0'
44 
45 static pthread_t fuse_thread;
46 static struct ffa_op_callbacks *op_callbacks;
47 #if defined(CONFIG_FUSE_LIBRARY_V3)
48 static sem_t fuse_started; /* semaphore to signal fuse_main has started */
49 #endif
50 
51 /* Pending operation the bottom/fuse thread is queuing into the Zephyr thread */
52 struct {
53 	int op;        /* One of OP_**/
54 	void *args;    /* Pointer to arguments structure, one of op_args_* or a simple argument */
55 	int ret;       /* Return from the operation */
56 	bool pending;  /* Is there a pending operation */
57 	sem_t op_done; /* semaphore to signal the job is done */
58 } op_queue;
59 
60 #define OP_STAT              offsetof(struct ffa_op_callbacks, stat)
61 #define OP_READMOUNT         offsetof(struct ffa_op_callbacks, readmount)
62 #define OP_READDIR_START     offsetof(struct ffa_op_callbacks, readdir_start)
63 #define OP_READDIR_READ_NEXT offsetof(struct ffa_op_callbacks, readdir_read_next)
64 #define OP_READDIR_END       offsetof(struct ffa_op_callbacks, readdir_end)
65 #define OP_MKDIR             offsetof(struct ffa_op_callbacks, mkdir)
66 #define OP_CREATE            offsetof(struct ffa_op_callbacks, create)
67 #define OP_RELEASE           offsetof(struct ffa_op_callbacks, release)
68 #define OP_READ              offsetof(struct ffa_op_callbacks, read)
69 #define OP_WRITE             offsetof(struct ffa_op_callbacks, write)
70 #define OP_FTRUNCATE         offsetof(struct ffa_op_callbacks, ftruncate)
71 #define OP_TRUNCATE          offsetof(struct ffa_op_callbacks, truncate)
72 #define OP_UNLINK            offsetof(struct ffa_op_callbacks, unlink)
73 #define OP_RMDIR             offsetof(struct ffa_op_callbacks, rmdir)
74 
75 struct op_args_truncate {
76 	const char *path;
77 	off_t size;
78 };
79 struct op_args_ftruncate {
80 	uint64_t fh;
81 	off_t size;
82 };
83 struct op_args_readwrite {
84 	uint64_t fh;
85 	char *buf;
86 	off_t size;
87 	off_t off;
88 };
89 struct op_args_create {
90 	const char *path;
91 	uint64_t *fh_p;
92 };
93 struct op_args_readmount {
94 	int *mnt_nbr_p;
95 	const char **mnt_name_p;
96 };
97 struct op_args_stat {
98 	const char *path;
99 	struct ffa_dirent *entry_p;
100 };
101 
queue_op(int op,void * args)102 static inline int queue_op(int op, void *args)
103 {
104 	op_queue.op = op;
105 	op_queue.args = args;
106 	op_queue.pending = true;
107 
108 	sem_wait(&op_queue.op_done);
109 
110 	return op_queue.ret;
111 }
112 
ffa_is_op_pended(void)113 bool ffa_is_op_pended(void)
114 {
115 	return op_queue.pending;
116 }
117 
ffa_run_pending_op(void)118 void ffa_run_pending_op(void)
119 {
120 	switch ((intptr_t)op_queue.op) {
121 	case OP_RMDIR:
122 		op_queue.ret = op_callbacks->rmdir((const char *)op_queue.args);
123 		break;
124 	case OP_UNLINK:
125 		op_queue.ret = op_callbacks->unlink((const char *)op_queue.args);
126 		break;
127 	case OP_TRUNCATE: {
128 		struct op_args_truncate *args = op_queue.args;
129 
130 		op_queue.ret = op_callbacks->truncate(args->path, args->size);
131 		break;
132 	}
133 	case OP_FTRUNCATE: {
134 		struct op_args_ftruncate *args = op_queue.args;
135 
136 		op_queue.ret = op_callbacks->ftruncate(args->fh, args->size);
137 		break;
138 	}
139 	case OP_WRITE: {
140 		struct op_args_readwrite *args = op_queue.args;
141 
142 		op_queue.ret = op_callbacks->write(args->fh, args->buf, args->size, args->off);
143 		break;
144 	}
145 	case OP_READ: {
146 		struct op_args_readwrite *args = op_queue.args;
147 
148 		op_queue.ret = op_callbacks->read(args->fh, args->buf, args->size, args->off);
149 		break;
150 	}
151 	case OP_RELEASE:
152 		op_queue.ret = op_callbacks->release(*(uint64_t *)op_queue.args);
153 		break;
154 	case OP_CREATE: {
155 		struct op_args_create *args = op_queue.args;
156 
157 		op_queue.ret = op_callbacks->create(args->path, args->fh_p);
158 		break;
159 	}
160 	case OP_MKDIR:
161 		op_queue.ret = op_callbacks->mkdir((const char *)op_queue.args);
162 		break;
163 	case OP_READDIR_END:
164 		op_callbacks->readdir_end();
165 		break;
166 	case OP_READDIR_READ_NEXT:
167 		op_queue.ret = op_callbacks->readdir_read_next((struct ffa_dirent *)op_queue.args);
168 		break;
169 	case OP_READDIR_START:
170 		op_queue.ret = op_callbacks->readdir_start((const char *)op_queue.args);
171 		break;
172 	case OP_READMOUNT: {
173 		struct op_args_readmount *args = op_queue.args;
174 
175 		op_queue.ret = op_callbacks->readmount(args->mnt_nbr_p, args->mnt_name_p);
176 		break;
177 	}
178 	case OP_STAT: {
179 		struct op_args_stat *args = op_queue.args;
180 
181 		op_queue.ret = op_callbacks->stat(args->path, args->entry_p);
182 		break;
183 	}
184 	default:
185 		nsi_print_error_and_exit("Programming error, unknown queued operation\n");
186 		break;
187 	}
188 	op_queue.pending = false;
189 	sem_post(&op_queue.op_done);
190 }
191 
is_mount_point(const char * path)192 static bool is_mount_point(const char *path)
193 {
194 	char dir_path[PATH_MAX];
195 	size_t len;
196 
197 	len = strlen(path);
198 	if (len >= sizeof(dir_path)) {
199 		return false;
200 	}
201 
202 	memcpy(dir_path, path, len);
203 	dir_path[len] = '\0';
204 	return strcmp(dirname(dir_path), "/") == 0;
205 }
206 
207 #if defined(CONFIG_FUSE_LIBRARY_V3)
fuse_fs_access_getattr(const char * path,struct stat * st,struct fuse_file_info * fi)208 static int fuse_fs_access_getattr(const char *path, struct stat *st, struct fuse_file_info *fi)
209 {
210 	NSI_ARG_UNUSED(fi);
211 #else
212 static int fuse_fs_access_getattr(const char *path, struct stat *st)
213 {
214 #endif
215 	struct ffa_dirent entry;
216 	int err;
217 
218 	st->st_dev = 0;
219 	st->st_ino = 0;
220 	st->st_nlink = 0;
221 	st->st_uid = getuid();
222 	st->st_gid = getgid();
223 	st->st_rdev = 0;
224 	st->st_blksize = 0;
225 	st->st_blocks = 0;
226 	st->st_atime = 0;
227 	st->st_mtime = 0;
228 	st->st_ctime = 0;
229 
230 	if ((strcmp(path, "/") == 0) || is_mount_point(path)) {
231 		if (strstr(path, "/.") != NULL) {
232 			return -ENOENT;
233 		}
234 		st->st_mode = S_IFDIR | S_IRWX_DIR;
235 		st->st_size = 0;
236 		return 0;
237 	}
238 
239 	struct op_args_stat args;
240 
241 	args.path = path;
242 	args.entry_p = &entry;
243 
244 	err = queue_op(OP_STAT, (void *)&args);
245 
246 	if (err != 0) {
247 		return -nsi_errno_from_mid(err);
248 	}
249 
250 	if (entry.is_directory) {
251 		st->st_mode = S_IFDIR | S_IRWX_DIR;
252 		st->st_size = 0;
253 	} else {
254 		st->st_mode = S_IFREG | S_IRW_FILE;
255 		st->st_size = entry.size;
256 	}
257 
258 	return 0;
259 }
260 
261 static int fuse_fs_access_readmount(void *buf, fuse_fill_dir_t filler)
262 {
263 	int mnt_nbr = 0;
264 	const char *mnt_name;
265 	struct stat st;
266 	int err;
267 
268 	st.st_dev = 0;
269 	st.st_ino = 0;
270 	st.st_nlink = 0;
271 	st.st_uid = getuid();
272 	st.st_gid = getgid();
273 	st.st_rdev = 0;
274 	st.st_atime = 0;
275 	st.st_mtime = 0;
276 	st.st_ctime = 0;
277 	st.st_mode = S_IFDIR | S_IRWX_DIR;
278 	st.st_size = 0;
279 	st.st_blksize = 0;
280 	st.st_blocks = 0;
281 
282 #if defined(CONFIG_FUSE_LIBRARY_V3)
283 	filler(buf, ".", &st, 0, 0);
284 	filler(buf, "..", NULL, 0, 0);
285 #else
286 	filler(buf, ".", &st, 0);
287 	filler(buf, "..", NULL, 0);
288 #endif
289 
290 	do {
291 		struct op_args_readmount args;
292 
293 		args.mnt_nbr_p = &mnt_nbr;
294 		args.mnt_name_p = &mnt_name;
295 
296 		err = queue_op(OP_READMOUNT, (void *)&args);
297 		err = -nsi_errno_from_mid(err);
298 
299 		if (err < 0) {
300 			break;
301 		}
302 
303 #if defined(CONFIG_FUSE_LIBRARY_V3)
304 		filler(buf, &mnt_name[1], &st, 0, 0);
305 #else
306 		filler(buf, &mnt_name[1], &st, 0);
307 #endif
308 
309 	} while (true);
310 
311 	if (err == -ENOENT) {
312 		err = 0;
313 	}
314 
315 	return err;
316 }
317 
318 #if defined(CONFIG_FUSE_LIBRARY_V3)
319 static int fuse_fs_access_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t off,
320 				  struct fuse_file_info *fi, enum fuse_readdir_flags flags)
321 {
322 	NSI_ARG_UNUSED(flags);
323 #else
324 static int fuse_fs_access_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t off,
325 				  struct fuse_file_info *fi)
326 {
327 #endif
328 	NSI_ARG_UNUSED(off);
329 	NSI_ARG_UNUSED(fi);
330 
331 	struct ffa_dirent entry;
332 	int err;
333 	struct stat st;
334 
335 	if (strcmp(path, "/") == 0) {
336 		err = fuse_fs_access_readmount(buf, filler);
337 		return -nsi_errno_from_mid(err);
338 	}
339 
340 	if (is_mount_point(path)) {
341 		/* File system API expects trailing slash for a mount point
342 		 * directory but FUSE strips the trailing slashes from
343 		 * directory names so add it back.
344 		 */
345 		char mount_path[PATH_MAX] = {0};
346 		size_t len = strlen(path);
347 
348 		if (len >= (PATH_MAX - 2)) {
349 			return -ENOMEM;
350 		}
351 
352 		memcpy(mount_path, path, len);
353 		mount_path[len] = '/';
354 		err = queue_op(OP_READDIR_START, (void *)mount_path);
355 	} else {
356 		err = queue_op(OP_READDIR_START, (void *)path);
357 	}
358 
359 	if (err) {
360 		return -ENOEXEC;
361 	}
362 
363 	st.st_dev = 0;
364 	st.st_ino = 0;
365 	st.st_nlink = 0;
366 	st.st_uid = getuid();
367 	st.st_gid = getgid();
368 	st.st_rdev = 0;
369 	st.st_atime = 0;
370 	st.st_mtime = 0;
371 	st.st_ctime = 0;
372 	st.st_mode = S_IFDIR | S_IRWX_DIR;
373 	st.st_size = 0;
374 	st.st_blksize = 0;
375 	st.st_blocks = 0;
376 
377 #if defined(CONFIG_FUSE_LIBRARY_V3)
378 	filler(buf, ".", &st, 0, 0);
379 	filler(buf, "..", &st, 0, 0);
380 #else
381 	filler(buf, ".", &st, 0);
382 	filler(buf, "..", &st, 0);
383 #endif
384 
385 	do {
386 		err = queue_op(OP_READDIR_READ_NEXT, (void *)&entry);
387 		if (err) {
388 			break;
389 		}
390 
391 		if (entry.name[0] == DIR_END) {
392 			break;
393 		}
394 
395 		if (entry.is_directory) {
396 			st.st_mode = S_IFDIR | S_IRWX_DIR;
397 			st.st_size = 0;
398 		} else {
399 			st.st_mode = S_IFREG | S_IRW_FILE;
400 			st.st_size = entry.size;
401 		}
402 
403 #if defined(CONFIG_FUSE_LIBRARY_V3)
404 		bool full = filler(buf, entry.name, &st, 0, 0);
405 #else
406 		bool full = filler(buf, entry.name, &st, 0);
407 #endif
408 
409 		if (full) {
410 			break;
411 		}
412 	} while (1);
413 
414 	queue_op(OP_READDIR_END, NULL);
415 
416 	return -nsi_errno_from_mid(err);
417 }
418 
419 static int fuse_fs_access_mkdir(const char *path, mode_t mode)
420 {
421 	NSI_ARG_UNUSED(mode);
422 
423 	int err = queue_op(OP_MKDIR, (void *)path);
424 
425 	return -nsi_errno_from_mid(err);
426 }
427 
428 static int fuse_fs_access_create(const char *path, mode_t mode, struct fuse_file_info *fi)
429 {
430 	int err;
431 	struct op_args_create args;
432 
433 	NSI_ARG_UNUSED(mode);
434 
435 	if (is_mount_point(path)) {
436 		return -ENOENT;
437 	}
438 
439 	args.path = path;
440 	args.fh_p = &fi->fh;
441 
442 	err = queue_op(OP_CREATE, (void *)&args);
443 
444 	return -nsi_errno_from_mid(err);
445 }
446 
447 static int fuse_fs_access_open(const char *path, struct fuse_file_info *fi)
448 {
449 	int err = fuse_fs_access_create(path, 0, fi);
450 
451 	return -nsi_errno_from_mid(err);
452 }
453 
454 static int fuse_fs_access_release(const char *path, struct fuse_file_info *fi)
455 {
456 	NSI_ARG_UNUSED(path);
457 
458 	if (fi->fh == INVALID_FILE_HANDLE) {
459 		return -EINVAL;
460 	}
461 
462 	(void)queue_op(OP_RELEASE, (void *)&fi->fh);
463 
464 	return 0;
465 }
466 
467 static int fuse_fs_access_read(const char *path, char *buf, size_t size, off_t off,
468 			       struct fuse_file_info *fi)
469 {
470 	int err;
471 	struct op_args_readwrite args;
472 
473 	NSI_ARG_UNUSED(path);
474 
475 	if (fi->fh == INVALID_FILE_HANDLE) {
476 		return -EINVAL;
477 	}
478 
479 	args.fh = fi->fh;
480 	args.buf = buf;
481 	args.size = size;
482 	args.off = off;
483 
484 	err = queue_op(OP_READ, (void *)&args);
485 
486 	return -nsi_errno_from_mid(err);
487 }
488 
489 static int fuse_fs_access_write(const char *path, const char *buf, size_t size, off_t off,
490 				struct fuse_file_info *fi)
491 {
492 	int err;
493 	struct op_args_readwrite args;
494 
495 	NSI_ARG_UNUSED(path);
496 
497 	if (fi->fh == INVALID_FILE_HANDLE) {
498 		return -EINVAL;
499 	}
500 
501 	args.fh = fi->fh;
502 	args.buf = (char *)buf;
503 	args.size = size;
504 	args.off = off;
505 
506 	err = queue_op(OP_WRITE, (void *)&args);
507 
508 	return -nsi_errno_from_mid(err);
509 }
510 
511 #if !defined(CONFIG_FUSE_LIBRARY_V3)
512 static int fuse_fs_access_ftruncate(const char *path, off_t size, struct fuse_file_info *fi)
513 {
514 	struct op_args_ftruncate args;
515 	int err;
516 
517 	NSI_ARG_UNUSED(path);
518 
519 	if (fi->fh == INVALID_FILE_HANDLE) {
520 		return -EINVAL;
521 	}
522 
523 	args.fh = fi->fh;
524 	args.size = size;
525 
526 	err = queue_op(OP_FTRUNCATE, (void *)&args);
527 
528 	return -nsi_errno_from_mid(err);
529 }
530 #endif
531 
532 #if defined(CONFIG_FUSE_LIBRARY_V3)
533 static int fuse_fs_access_truncate(const char *path, off_t size, struct fuse_file_info *fi)
534 {
535 	NSI_ARG_UNUSED(fi);
536 #else
537 static int fuse_fs_access_truncate(const char *path, off_t size)
538 {
539 #endif
540 	struct op_args_truncate args;
541 	int err;
542 
543 	args.path = path;
544 	args.size = size;
545 
546 	err = queue_op(OP_TRUNCATE, (void *)&args);
547 
548 	return -nsi_errno_from_mid(err);
549 }
550 
551 static int fuse_fs_access_rmdir(const char *path)
552 {
553 	int err = queue_op(OP_RMDIR, (void *)path);
554 
555 	return -nsi_errno_from_mid(err);
556 }
557 
558 static int fuse_fs_access_unlink(const char *path)
559 {
560 	int err = queue_op(OP_UNLINK, (void *)path);
561 
562 	return -nsi_errno_from_mid(err);
563 }
564 
565 static int fuse_fs_access_statfs(const char *path, struct statvfs *buf)
566 {
567 	NSI_ARG_UNUSED(path);
568 	NSI_ARG_UNUSED(buf);
569 	return 0;
570 }
571 
572 #if defined(CONFIG_FUSE_LIBRARY_V3)
573 static int fuse_fs_access_utimens(const char *path, const struct timespec tv[2],
574 				  struct fuse_file_info *fi)
575 {
576 	NSI_ARG_UNUSED(fi);
577 #else
578 static int fuse_fs_access_utimens(const char *path, const struct timespec tv[2])
579 {
580 #endif
581 	/* dummy */
582 	NSI_ARG_UNUSED(path);
583 	NSI_ARG_UNUSED(tv);
584 	return 0;
585 }
586 
587 #if defined(CONFIG_FUSE_LIBRARY_V3)
588 static void *fuse_fs_access_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
589 {
590 	NSI_ARG_UNUSED(conn);
591 	NSI_ARG_UNUSED(cfg);
592 
593 	sem_post(&fuse_started);
594 	return NULL;
595 }
596 #endif
597 
598 static struct fuse_operations fuse_fs_access_oper = {
599 	.getattr = fuse_fs_access_getattr,
600 	.readlink = NULL,
601 	.mknod = NULL,
602 	.mkdir = fuse_fs_access_mkdir,
603 	.unlink = fuse_fs_access_unlink,
604 	.rmdir = fuse_fs_access_rmdir,
605 	.symlink = NULL,
606 	.rename = NULL,
607 	.link = NULL,
608 	.chmod = NULL,
609 	.chown = NULL,
610 	.truncate = fuse_fs_access_truncate,
611 	.open = fuse_fs_access_open,
612 	.read = fuse_fs_access_read,
613 	.write = fuse_fs_access_write,
614 	.statfs = fuse_fs_access_statfs,
615 	.flush = NULL,
616 	.release = fuse_fs_access_release,
617 	.fsync = NULL,
618 	.setxattr = NULL,
619 	.getxattr = NULL,
620 	.listxattr = NULL,
621 	.removexattr = NULL,
622 	.opendir = NULL,
623 	.readdir = fuse_fs_access_readdir,
624 	.releasedir = NULL,
625 	.fsyncdir = NULL,
626 #if defined(CONFIG_FUSE_LIBRARY_V3)
627 	.init = fuse_fs_access_init,
628 #endif
629 	.destroy = NULL,
630 	.access = NULL,
631 	.create = fuse_fs_access_create,
632 #if !defined(CONFIG_FUSE_LIBRARY_V3)
633 	.ftruncate = fuse_fs_access_ftruncate,
634 #endif
635 	.lock = NULL,
636 	.utimens = fuse_fs_access_utimens,
637 	.bmap = NULL,
638 #if !defined(CONFIG_FUSE_LIBRARY_V3)
639 	.flag_nullpath_ok = 0,
640 	.flag_nopath = 0,
641 	.flag_utime_omit_ok = 0,
642 	.flag_reserved = 0,
643 #endif
644 	.ioctl = NULL,
645 	.poll = NULL,
646 	.write_buf = NULL,
647 	.read_buf = NULL,
648 	.flock = NULL,
649 	.fallocate = NULL,
650 };
651 
652 static void *ffsa_main(void *fuse_mountpoint)
653 {
654 	char *argv[] = {
655 		"",
656 		"-f",
657 		"-s",
658 		(char *)fuse_mountpoint
659 	};
660 	int argc = NSI_ARRAY_SIZE(argv);
661 
662 	nsi_print_trace("FUSE mounting flash in host %s/\n", (char *)fuse_mountpoint);
663 
664 	fuse_main(argc, argv, &fuse_fs_access_oper, NULL);
665 
666 	pthread_exit(0);
667 	return NULL;
668 }
669 
670 void ffsa_init_bottom(const char *fuse_mountpoint, struct ffa_op_callbacks *op_cbs)
671 {
672 	struct stat st;
673 	int err;
674 
675 	op_callbacks = op_cbs;
676 
677 	if (stat(fuse_mountpoint, &st) < 0) {
678 		if (mkdir(fuse_mountpoint, 0700) < 0) {
679 			nsi_print_error_and_exit("Failed to create directory for flash mount point "
680 						 "(%s): %s\n",
681 						 fuse_mountpoint, strerror(errno));
682 		}
683 	} else if (!S_ISDIR(st.st_mode)) {
684 		nsi_print_error_and_exit("%s is not a directory\n", fuse_mountpoint);
685 	}
686 
687 #if defined(CONFIG_FUSE_LIBRARY_V3)
688 	/* Fuse3's fuse_daemonize() changes the cwd to "/" which is a quite undesirable
689 	 * side-effect, so we will revert back to where we were once it has initialized
690 	 */
691 	char *cwd = getcwd(NULL, 0);
692 
693 	err = sem_init(&fuse_started, 0, 0);
694 	if (err) {
695 		nsi_print_error_and_exit("Failed to initialize semaphore\n");
696 	}
697 #endif
698 
699 	err = sem_init(&op_queue.op_done, 0, 0);
700 	if (err) {
701 		nsi_print_error_and_exit("Failed to initialize semaphore\n");
702 	}
703 
704 	err = pthread_create(&fuse_thread, NULL, ffsa_main, (void *)fuse_mountpoint);
705 	if (err < 0) {
706 		nsi_print_error_and_exit("Failed to create thread for ffsa_main()\n");
707 	}
708 
709 #if defined(CONFIG_FUSE_LIBRARY_V3)
710 	if (cwd != NULL) {
711 		sem_wait(&fuse_started);
712 		if (chdir(cwd)) {
713 			nsi_print_error_and_exit("Failed to change directory back to %s "
714 						 "after starting FUSE\n");
715 		}
716 		free(cwd);
717 	}
718 #endif
719 }
720 
721 void ffsa_cleanup_bottom(const char *fuse_mountpoint)
722 {
723 	char *full_cmd;
724 	static const char cmd[] = "fusermount -uz ";
725 
726 	full_cmd = malloc(strlen(cmd) + strlen(fuse_mountpoint) + 1);
727 
728 	sprintf(full_cmd, "%s%s", cmd, fuse_mountpoint);
729 	if (system(full_cmd) < -1) {
730 		nsi_print_trace("Failed to unmount fuse mount point\n");
731 	}
732 	free(full_cmd);
733 
734 	pthread_join(fuse_thread, NULL);
735 }
736