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