From fc0fca231b8cb607c5af1755f38ce0e1d6535ab0 Mon Sep 17 00:00:00 2001 From: Morning <391091+moonsoup@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:43:43 -0500 Subject: [PATCH 1/2] Fix macOS build: adapt getxattr to the Darwin 5-arg signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS FUSE implementations (macFUSE, FUSE-T) declare the getxattr operation with an extra trailing `uint32_t position` argument — the Darwin resource-fork xattr convention. Assigning the shared 4-arg op_getxattr to .getxattr then fails to compile with an incompatible-function-pointer-type error (ref upstream issue #143). Add an __APPLE__-only shim that accepts and ignores `position` and defers to the existing op_getxattr. No behavior change on Linux/BSD; ext2 has no resource-fork concept, so dropping `position` is correct. The #ifdef keeps the non-Darwin build using the 4-arg op_getxattr unchanged. Tested: builds and mounts read-only on macOS 26.5.1 (Apple Silicon, Darwin 25.5.0), macFUSE 5.2, Apple clang; read-only verification passes on ext2/ext3/ext4 throwaway images. Single-environment test only; broader platform testing not yet done. Co-Authored-By: Claude Opus 4.8 --- fuse-ext2/fuse-ext2.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/fuse-ext2/fuse-ext2.c b/fuse-ext2/fuse-ext2.c index 249a389c..6f2351de 100644 --- a/fuse-ext2/fuse-ext2.c +++ b/fuse-ext2/fuse-ext2.c @@ -277,6 +277,19 @@ static char * parse_mount_options (const char *orig_opts, struct extfs_data *opt goto exit; } +#ifdef __APPLE__ +/* macOS FUSE (macFUSE / FUSE-T) declares getxattr with an extra trailing + * `position` argument, for the Darwin resource-fork xattr convention. ext2 has + * no such concept, so this shim drops `position` and defers to the shared + * Linux-style op_getxattr. Without it the build fails with an incompatible + * function-pointer-type error assigning op_getxattr to .getxattr. */ +static int op_getxattr_darwin(const char *path, const char *name, char *value, + size_t size, uint32_t position) { + (void) position; + return op_getxattr(path, name, value, size); +} +#endif + static const struct fuse_operations ext2fs_ops = { .getattr = op_getattr, .readlink = op_readlink, @@ -298,7 +311,11 @@ static const struct fuse_operations ext2fs_ops = { .release = op_release, .fsync = op_fsync, .setxattr = NULL, +#ifdef __APPLE__ + .getxattr = op_getxattr_darwin, +#else .getxattr = op_getxattr, +#endif .listxattr = NULL, .removexattr = NULL, .opendir = op_open, From 13be00e901e93730e05f608fe2dc98e67ff41f6e Mon Sep 17 00:00:00 2001 From: Morning <391091+moonsoup@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:46:19 -0500 Subject: [PATCH 2/2] Add opt-in `no_default_permissions` mount option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By default fuse-ext2 mounts with the kernel's `default_permissions`, which enforces the on-disk mode/owner bits. When reading a filesystem recovered from another machine, files are often owned by a uid that does not exist on the host, so the mounting user is denied access to their own recovered data and must fall back to mounting everything as root. Add an opt-in `-o no_default_permissions` that omits `default_permissions`, letting the (root-privileged) fuse-ext2 daemon serve those files to the mounting user regardless of the foreign on-disk uid. Off by default — the kernel still enforces permissions unless explicitly requested. To allow this, `default_permissions` is now appended in parse_mount_options() rather than hardcoded in def_opts, so it can be conditionally suppressed. Tested on macOS 26.5.1 / macFUSE 5.2: a mode-0750 file owned by a foreign uid (1000) is unreadable by the local user under a default mount but readable with -o no_default_permissions; read-only verification still passes on ext2/ext3/ext4. Single-environment test only. Co-Authored-By: Claude Opus 4.8 --- fuse-ext2/fuse-ext2.c | 23 ++++++++++++++++++++--- fuse-ext2/fuse-ext2.h | 1 + 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/fuse-ext2/fuse-ext2.c b/fuse-ext2/fuse-ext2.c index 6f2351de..9619c45a 100644 --- a/fuse-ext2/fuse-ext2.c +++ b/fuse-ext2/fuse-ext2.c @@ -22,11 +22,17 @@ static const char *HOME = "http://github.com/alperakcan/fuse-ext2/"; +/* `default_permissions` is intentionally NOT in def_opts here; it is appended + * separately in parse_mount_options() UNLESS the `no_default_permissions` mount + * option is given. That option asks the kernel to skip its own mode-bit checks so + * a file owned by a uid that doesn't exist on this system (e.g. data recovered + * from another machine) can still be read by the mounting user. Useful for + * read-only data recovery; opt-in, off by default. */ #if __FreeBSD__ == 10 -static char def_opts[] = "allow_other,default_permissions,local,"; +static char def_opts[] = "allow_other,local,"; static char def_opts_rd[] = "noappledouble,"; #else -static char def_opts[] = "allow_other,default_permissions,"; +static char def_opts[] = "allow_other,"; static char def_opts_rd[] = ""; #endif @@ -39,7 +45,9 @@ static const char *usage_msg = "\n" "Usage: %s [-o option[,...]]\n" "\n" -"Options: ro, force, allow_other\n" +"Options: ro, force, allow_other, no_default_permissions\n" +" no_default_permissions: skip kernel mode-bit checks so files owned by\n" +" a uid absent on this system (e.g. recovered data) stay readable.\n" " Please see details in the manual.\n" "\n" "Example: fuse-ext2 /dev/sda1 /mnt/sda1\n" @@ -222,6 +230,12 @@ static char * parse_mount_options (const char *orig_opts, struct extfs_data *opt goto err_exit; } opts->silent = 1; + } else if (!strcmp(opt, "no_default_permissions")) { /* skip kernel mode-bit checks */ + if (val) { + debugf_main("'no_default_permissions' option should not have value"); + goto err_exit; + } + opts->no_default_permissions = 1; } else if (!strcmp(opt, "force")) { /* enable read/write */ if (val) { debugf_main("'force option should no have value"); @@ -247,6 +261,9 @@ static char * parse_mount_options (const char *orig_opts, struct extfs_data *opt } strcat(ret, def_opts); + if (!opts->no_default_permissions) { + strcat(ret, "default_permissions,"); + } if (opts->readonly == 1) { strcat(ret, def_opts_rd); strcat(ret, "ro,"); diff --git a/fuse-ext2/fuse-ext2.h b/fuse-ext2/fuse-ext2.h index 07fd1db6..9481faba 100644 --- a/fuse-ext2/fuse-ext2.h +++ b/fuse-ext2/fuse-ext2.h @@ -62,6 +62,7 @@ struct extfs_data { unsigned char silent; unsigned char force; unsigned char readonly; + unsigned char no_default_permissions; time_t last_flush; char *mnt_point; char *options;