Skip to content

Commit 0cfb9cd

Browse files
authored
Merge pull request #35 from sysprog21/fuse
Add guest-internal FUSE transport and minimal VFS
2 parents 3f19ece + aa449a7 commit 0cfb9cd

25 files changed

Lines changed: 3480 additions & 37 deletions

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ SRCS := \
3636
syscall/translate.c \
3737
syscall/mem.c \
3838
syscall/path.c \
39+
syscall/fuse.c \
3940
syscall/sidecar.c \
4041
syscall/fs.c \
4142
syscall/fs-stat.c \
@@ -149,6 +150,11 @@ $(BUILD_DIR)/test-pthread: tests/test-pthread.c | $(BUILD_DIR)
149150
@echo " CROSS $< (with -lpthread)"
150151
$(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread
151152

153+
# test-fuse-basic runs a guest daemon thread and consumer in one process
154+
$(BUILD_DIR)/test-fuse-basic: tests/test-fuse-basic.c | $(BUILD_DIR)
155+
@echo " CROSS $< (with -lpthread)"
156+
$(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread
157+
152158
# test-sched-policy spawns a pthread to verify per-thread TID lookup
153159
$(BUILD_DIR)/test-sched-policy: tests/test-sched-policy.c | $(BUILD_DIR)
154160
@echo " CROSS $< (with -lpthread)"

docs/testing.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ What they do:
4848
downloaded into `build/busybox` on first run.
4949
- `make test-busybox`: just the BusyBox suite, useful when iterating on a
5050
single applet failure without rerunning the unit suite
51+
- `make test-fuse-alpine`: validate guest `/dev/fuse` + `mount("fuse")`
52+
against the Alpine musl sysroot fixture
5153
- `make test-gdbstub`: debugger integration checks against the built-in GDB stub
5254
- `make test-matrix`: broader `elfuse` and QEMU cross-check
5355
- `make lint`: static analysis through `clang-tidy`
@@ -61,7 +63,7 @@ make elfuse
6163
make check
6264
```
6365

64-
For changes that touch procfs, path handling, networking, dynamic linking, or
66+
For changes that touch procfs, path handling, `/dev`, FUSE, networking, dynamic linking, or
6567
guest process semantics, run the matrix as well:
6668

6769
```sh

mk/tests.mk

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
test-full test-multi-vcpu test-rwx test-sysroot-rename \
99
test-case-collision test-case-collision-fallback test-sysroot-create-paths \
1010
test-proctitle-low-stack \
11-
test-sysroot-procfs-exec test-timeout-disable \
11+
test-sysroot-procfs-exec test-timeout-disable test-fuse-alpine \
1212
test-sysroot-nofollow test-sysroot-chdir perf
1313

1414
## Build and run the assembly hello world test
@@ -29,6 +29,8 @@ check: $(ELFUSE_BIN) $(TEST_DEPS) check-syscall-coverage
2929
@$(MAKE) --no-print-directory test-busybox
3030
@printf "\n$(BLUE)━━━ sysroot procfs exec validation ━━━$(RESET)\n"
3131
@$(MAKE) --no-print-directory test-sysroot-procfs-exec
32+
@printf "\n$(BLUE)━━━ Alpine sysroot FUSE validation ━━━$(RESET)\n"
33+
@$(MAKE) --no-print-directory test-fuse-alpine
3234
@printf "\n$(BLUE)━━━ timeout=0 validation ━━━$(RESET)\n"
3335
@$(MAKE) --no-print-directory test-timeout-disable
3436

@@ -297,6 +299,14 @@ test-dynamic: $(ELFUSE_BIN)
297299
@printf "$(BLUE)▸ Running$(RESET) dynamic hello-dynamic (--sysroot)\n"
298300
$(ELFUSE_BIN) --sysroot $(SYSROOT_DIR) $(GUEST_DYNAMIC_TESTS)/bin/hello-dynamic
299301

302+
## Run guest FUSE validation against the Alpine musl sysroot
303+
test-fuse-alpine: $(ELFUSE_BIN) $(BUILD_DIR)/test-fuse-basic
304+
@if [ -z "$(SYSROOT_DIR)" ] || [ ! -d "$(SYSROOT_DIR)" ]; then \
305+
printf "$(YELLOW)SKIP$(RESET) Alpine sysroot not found. Set SYSROOT_DIR=/path/to/sysroot or run tests/fetch-fixtures.sh.\n"; \
306+
exit 0; \
307+
fi
308+
@bash tests/test-fuse-alpine.sh $(ELFUSE_BIN) $(SYSROOT_DIR) $(BUILD_DIR)/test-fuse-basic
309+
300310
## Run dynamically-linked coreutils tests (--sysroot)
301311
test-dynamic-coreutils: $(ELFUSE_BIN)
302312
@if [ -z "$(SYSROOT_DIR)" ] || [ ! -d "$(SYSROOT_DIR)" ]; then \

src/core/guest.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,9 @@ static inline void guest_pt_gen_bump(guest_t *g)
314314
* a synchronous IPI into a sibling vCPU thread, so the window remains.
315315
* The guest is responsible for serializing concurrent PT mutations
316316
* against concurrent accesses (futex / pthread_mutex), which is the same
317-
* contract real Linux requires of well-behaved multi-threaded code. See
318-
* TODO.md "Bounded retry on stale TLB data abort" (P3 hardening) for the
319-
* tracked follow-up if a workload ever surfaces an actual reproducer.
317+
* contract real Linux requires of well-behaved multi-threaded code. A
318+
* bounded-retry on stale-TLB data aborts is a known hardening direction
319+
* if a workload ever surfaces an actual reproducer.
320320
*/
321321
extern _Thread_local tlbi_request_t cpu_tlbi_req;
322322

src/runtime/procemu.c

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
#include "syscall/abi.h"
5151
#include "syscall/fd.h"
52+
#include "syscall/fuse.h"
5253
#include "syscall/internal.h"
5354
#include "syscall/proc.h"
5455
#include "syscall/sys.h"
@@ -2000,6 +2001,7 @@ int proc_intercept_open(const guest_t *g,
20002001
"\tproc\n"
20012002
"\tsysfs\n"
20022003
"\tdevtmpfs\n"
2004+
"\tfuse\n"
20032005
"\text4\n"
20042006
"\tvfat\n");
20052007
}
@@ -2009,23 +2011,35 @@ int proc_intercept_open(const guest_t *g,
20092011
* - type source super_options
20102012
*/
20112013
if (!strcmp(path, "/proc/self/mountinfo")) {
2012-
return proc_emit_literal(
2014+
char buf[8192];
2015+
size_t off = (size_t) snprintf(
2016+
buf, sizeof(buf),
20132017
"1 0 0:1 / / rw,relatime - ext4 /dev/root rw\n"
20142018
"2 1 0:2 / /proc rw,nosuid,nodev,noexec - proc proc rw\n"
20152019
"3 1 0:3 / /tmp rw,nosuid,nodev - tmpfs tmpfs rw\n"
20162020
"4 1 0:4 / /dev rw,nosuid - devtmpfs devtmpfs rw\n"
20172021
"5 4 0:5 / /dev/shm rw,nosuid,nodev - tmpfs tmpfs rw\n");
2022+
if (off >= sizeof(buf) ||
2023+
fuse_append_mountinfo(buf, sizeof(buf), &off) < 0)
2024+
return -1;
2025+
return proc_synthetic_fd(buf, off);
20182026
}
20192027

20202028
/* /proc/mounts, /etc/mtab -> synthetic mount table */
20212029
if (!strcmp(path, "/proc/mounts") || !strcmp(path, "/proc/self/mounts") ||
20222030
!strcmp(path, "/etc/mtab")) {
2023-
return proc_emit_literal(
2024-
"/ / ext4 rw,relatime 0 0\n"
2025-
"proc /proc proc rw,nosuid,nodev,noexec 0 0\n"
2026-
"tmpfs /tmp tmpfs rw,nosuid,nodev 0 0\n"
2027-
"devtmpfs /dev devtmpfs rw,nosuid 0 0\n"
2028-
"tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0\n");
2031+
char buf[8192];
2032+
size_t off =
2033+
(size_t) snprintf(buf, sizeof(buf),
2034+
"/ / ext4 rw,relatime 0 0\n"
2035+
"proc /proc proc rw,nosuid,nodev,noexec 0 0\n"
2036+
"tmpfs /tmp tmpfs rw,nosuid,nodev 0 0\n"
2037+
"devtmpfs /dev devtmpfs rw,nosuid 0 0\n"
2038+
"tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0\n");
2039+
if (off >= sizeof(buf) ||
2040+
fuse_append_mounts(buf, sizeof(buf), &off) < 0)
2041+
return -1;
2042+
return proc_synthetic_fd(buf, off);
20292043
}
20302044

20312045
/* OOM nodes share one stored adjustment.
@@ -2114,12 +2128,15 @@ int proc_intercept_open(const guest_t *g,
21142128
}
21152129
}
21162130

2131+
int mnt_id = 0;
2132+
if (fuse_fd_mnt_id(n, &mnt_id) < 0)
2133+
mnt_id = 0;
21172134
return proc_emit_fmt(
21182135
"pos:\t%lld\n"
21192136
"flags:\t0%o\n"
2120-
"mnt_id:\t0\n"
2137+
"mnt_id:\t%d\n"
21212138
"%s",
2122-
(long long) pos, snap.linux_flags, extra);
2139+
(long long) pos, snap.linux_flags, mnt_id, extra);
21232140
}
21242141

21252142
/* /proc/self/fdinfo -> directory listing. Each open gets its own scratch
@@ -2367,6 +2384,9 @@ int proc_intercept_stat(const char *path, struct stat *st)
23672384
* irrelevant here; callers need stat to succeed before opening the
23682385
* synthetic file.
23692386
*/
2387+
if (!strcmp(path, "/dev/fuse"))
2388+
return fuse_proc_stat(st);
2389+
23702390
/* /dev/shm is a directory */
23712391
if (!strcmp(path, "/dev/shm") || !strcmp(path, "/dev/shm/")) {
23722392
stat_fill_proc_dir(st, 01777, 2,

src/syscall/abi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#define SYS_symlinkat 36
3737
#define SYS_linkat 37
3838
#define SYS_renameat 38
39+
#define SYS_mount 40
3940
#define SYS_truncate 45
4041
#define SYS_statfs 43
4142
#define SYS_fstatfs 44
@@ -278,12 +279,14 @@ typedef struct {
278279
#define LINUX_EBUSY 16
279280
#define LINUX_EEXIST 17
280281
#define LINUX_EXDEV 18
282+
#define LINUX_ENODEV 19
281283
#define LINUX_ENOTDIR 20
282284
#define LINUX_EINVAL 22
283285
#define LINUX_EMFILE 24
284286
#define LINUX_ENOTTY 25
285287
#define LINUX_EFBIG 27
286288
#define LINUX_ENOSPC 28
289+
#define LINUX_ESPIPE 29
287290
#define LINUX_ERANGE 34
288291
#define LINUX_EDEADLK 35
289292
#define LINUX_ENAMETOOLONG 36
@@ -629,6 +632,9 @@ typedef struct {
629632
#define FD_PATH 11
630633
#define FD_NETLINK 12
631634
#define FD_PIDFD 13
635+
#define FD_FUSE_DEV 14
636+
#define FD_FUSE_FILE 15
637+
#define FD_FUSE_DIR 16
632638
#define FD_VIRTUAL_PATH_MAX 64
633639

634640
/* File sealing flags (F_SEAL_*) for memfd_create. Tracked per-FD. */

src/syscall/dispatch.tbl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ SYS_symlinkat sc_symlinkat 1
6060
SYS_linkat sc_linkat 1
6161
SYS_renameat sc_renameat 1
6262
SYS_renameat2 sc_renameat2 1
63+
SYS_mount sc_mount 1
6364
SYS_readlinkat sc_readlinkat 1
6465
SYS_newfstatat sc_newfstatat 1
6566
SYS_fstat sc_fstat 0

src/syscall/exec.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
#include "syscall/abi.h"
3333
#include "syscall/exec.h"
34+
#include "syscall/fuse.h"
3435
#include "syscall/internal.h"
3536
#include "syscall/path.h"
3637
#include "syscall/proc.h"
@@ -118,6 +119,9 @@ int64_t sys_execve(hv_vcpu_t vcpu,
118119

119120
char path_host_buf[LINUX_PATH_MAX];
120121
const char *path_host = path;
122+
bool path_host_temp = false;
123+
char interp_host_buf[LINUX_PATH_MAX];
124+
bool interp_host_temp = false;
121125

122126
#define MAX_ARGS 256
123127
#define MAX_ENVS 4096
@@ -172,7 +176,15 @@ int64_t sys_execve(hv_vcpu_t vcpu,
172176
err = linux_errno();
173177
goto fail;
174178
}
175-
str_copy_trunc(path_host_buf, tx.host_path, sizeof(path_host_buf));
179+
if (tx.fuse_path) {
180+
err = fuse_materialize_path(tx.intercept_path, path_host_buf,
181+
sizeof(path_host_buf));
182+
if (err < 0)
183+
goto fail;
184+
path_host_temp = true;
185+
} else {
186+
str_copy_trunc(path_host_buf, tx.host_path, sizeof(path_host_buf));
187+
}
176188
path_host = path_host_buf;
177189
}
178190
if (!path_host) {
@@ -304,9 +316,23 @@ int64_t sys_execve(hv_vcpu_t vcpu,
304316
err = linux_errno();
305317
goto fail;
306318
}
307-
str_copy_trunc(path_host_buf, interp_tx.host_path,
308-
sizeof(path_host_buf));
309-
path_host = path_host_buf;
319+
if (path_host_temp) {
320+
unlink(path_host_buf);
321+
path_host_temp = false;
322+
}
323+
if (interp_tx.fuse_path) {
324+
err =
325+
fuse_materialize_path(interp_tx.intercept_path, interp_host_buf,
326+
sizeof(interp_host_buf));
327+
if (err < 0)
328+
goto fail;
329+
interp_host_temp = true;
330+
path_host = interp_host_buf;
331+
} else {
332+
str_copy_trunc(path_host_buf, interp_tx.host_path,
333+
sizeof(path_host_buf));
334+
path_host = path_host_buf;
335+
}
310336

311337
if (elf_load(path_host, &elf_info) < 0) {
312338
err = -LINUX_ENOENT;
@@ -383,6 +409,10 @@ int64_t sys_execve(hv_vcpu_t vcpu,
383409
*/
384410
if (0) {
385411
fail:
412+
if (path_host_temp)
413+
unlink(path_host_buf);
414+
if (interp_host_temp)
415+
unlink(interp_host_buf);
386416
free(argv_buf);
387417
free(envp_buf);
388418
return err;
@@ -693,8 +723,7 @@ int64_t sys_execve(hv_vcpu_t vcpu,
693723
elf_info.segments[i].gpa + elf_info.segments[i].memsz +
694724
elf_load_base,
695725
elf_pf_to_prot(elf_info.segments[i].flags),
696-
LINUX_MAP_PRIVATE, elf_info.segments[i].offset,
697-
path_host);
726+
LINUX_MAP_PRIVATE, elf_info.segments[i].offset, path);
698727
}
699728
/* interp_resolved was computed before guest_reset so no filesystem lookup
700729
* is needed after the point of no return.
@@ -803,6 +832,10 @@ int64_t sys_execve(hv_vcpu_t vcpu,
803832
log_debug("execve: loaded %s, entry=0x%llx sp=0x%llx", path_host,
804833
(unsigned long long) entry_ipa, (unsigned long long) sp_ipa);
805834

835+
if (path_host_temp)
836+
unlink(path_host_buf);
837+
if (interp_host_temp)
838+
unlink(interp_host_buf);
806839
free(argv_buf);
807840
free(envp_buf);
808841

src/syscall/fs-stat.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "runtime/procemu.h"
1515

1616
#include "syscall/abi.h"
17+
#include "syscall/fuse.h"
1718
#include "syscall/fs.h"
1819
#include "syscall/internal.h"
1920
#include "syscall/path.h"
@@ -182,13 +183,27 @@ static int64_t stat_at_path(guest_t *g,
182183
sizeof(path), &pathp) < 0)
183184
return -LINUX_EFAULT;
184185

186+
if (pathp[0] == '/' && fuse_path_matches_mount(pathp)) {
187+
int frc = fuse_stat_path(pathp, mac_st, flags);
188+
if (frc < 0)
189+
return frc;
190+
return 0;
191+
}
192+
185193
path_translation_t tx;
186194
if (path_translate_at(dirfd, pathp,
187195
(flags & LINUX_AT_SYMLINK_NOFOLLOW) ? PATH_TR_NOFOLLOW
188196
: PATH_TR_NONE,
189197
&tx) < 0)
190198
return linux_errno();
191199

200+
if (tx.fuse_path) {
201+
int frc = fuse_stat_path(tx.intercept_path, mac_st, flags);
202+
if (frc < 0)
203+
return frc;
204+
return 0;
205+
}
206+
192207
if (tx.proc_resolved == 0 && dirfd == LINUX_AT_FDCWD && pathp[0] != '/' &&
193208
pathp[0] != '\0' && !proc_get_sysroot()) {
194209
int mac_flags = translate_at_flags(flags);
@@ -244,13 +259,21 @@ static int64_t stat_at_path(guest_t *g,
244259

245260
int64_t sys_fstat(guest_t *g, int fd, uint64_t stat_gva)
246261
{
262+
struct stat mac_st;
263+
int frc = fuse_fstat_fd(fd, &mac_st);
264+
if (frc == 0) {
265+
if (write_linux_stat(g, stat_gva, &mac_st) < 0)
266+
return -LINUX_EFAULT;
267+
return 0;
268+
}
269+
if (frc != -LINUX_EBADF)
270+
return frc;
271+
247272
host_fd_ref_t host_ref;
248273
if (host_fd_ref_open(fd, &host_ref) < 0) {
249274
log_debug("fstat(%d): invalid guest fd", fd);
250275
return -LINUX_EBADF;
251276
}
252-
253-
struct stat mac_st;
254277
if (fstat(host_ref.fd, &mac_st) < 0) {
255278
log_debug("fstat(%d->%d): host fstat failed errno=%d", fd, host_ref.fd,
256279
errno);
@@ -297,6 +320,8 @@ int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva)
297320
path_translation_t tx;
298321
if (path_translate_at(LINUX_AT_FDCWD, path, PATH_TR_NONE, &tx) < 0)
299322
return linux_errno();
323+
if (tx.fuse_path)
324+
return -LINUX_ENOSYS;
300325

301326
struct statfs mac_st;
302327
if (statfs(tx.host_path, &mac_st) < 0)

0 commit comments

Comments
 (0)