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