linux-ext4.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHSET 1/6] fuse2fs: even more bug fixes
@ 2025-05-21 22:34 Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 01/29] libext2fs: fix unix io manager invalidation Darrick J. Wong
                   ` (29 more replies)
  0 siblings, 30 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:34 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

Hi all,

This series fixes even more bugs in fuse2fs.

If you're going to start using this code, I strongly recommend pulling
from my git trees, which are linked below.

Comments and questions are, as always, welcome.

e2fsprogs git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/e2fsprogs.git/log/?h=fuse2fs-fixes
---
Commits in this patchset:
 * libext2fs: fix unix io manager invalidation
 * libext2fs: fix livelock in the unix io manager
 * fuse2fs: clean up error messages
 * fuse2fs: fix cache size parsing
 * fuse2fs: compact all the boolean flags in struct fuse2fs
 * fuse2fs: support XATTR_CREATE/REPLACE in setxattr
 * fuse2fs: fix error return handling in op_truncate
 * fuse2fs: flip parameter order in __translate_error
 * fuse2fs: fix CLI argument parsing leaks
 * fuse2fs: allow some control over acls
 * fuse2fs: enable processing of acls in the kernel
 * fuse2fs: make removexattr work correctly
 * fuse2fs: implement O_TRUNC correctly
 * fuse2fs: rearrange check_inum_access parameters a bit
 * fuse2fs: make filesystem corruption a hard error
 * fuse2fs: make internal state corruption a hard error
 * fuse2fs: make bad magic numbers report a corruption error too
 * fuse2fs: return EPERM for write access to EXT2_IMMUTABLE_FL files
 * fuse2fs: check the immutable flag in more places
 * fuse2fs: implement O_APPEND correctly
 * fuse2fs: decode fuse_main error codes
 * fuse2fs: fix fallocate zero range
 * fuse2fs: check for supported xattr name prefixes
 * fuse2fs: fix return value handling
 * fuse2fs: fix removing ea inodes when freeing a file
 * fuse2fs: fix post-EOF preallocation clearing on truncation
 * fuse2fs: also ignore the nodelalloc mount option
 * fuse2fs: propagate default ACLs to new children
 * fuse2fs: fix group membership checking in op_chmod
---
 lib/ext2fs/ext2fs.h          |    1 
 lib/ext2fs/ext2fsP.h         |    3 
 debian/libext2fs2t64.symbols |    1 
 lib/ext2fs/ext_attr.c        |   19 +
 lib/ext2fs/unix_io.c         |   53 ++-
 misc/fuse2fs.c               |  769 +++++++++++++++++++++++++++++++++++-------
 6 files changed, 705 insertions(+), 141 deletions(-)


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 01/29] libext2fs: fix unix io manager invalidation
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
@ 2025-05-21 22:35 ` Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 02/29] libext2fs: fix livelock in the unix io manager Darrick J. Wong
                   ` (28 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

flush_cached_blocks does not invalidate clean blocks from the block
cache.  From reading all the call sites, it looks like they all actually
want the cache to be empty on successful return, so adjust the
implementation to do this.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/unix_io.c |   30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)


diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index dbe748d9c43583..b98c44a84bb0af 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -670,20 +670,24 @@ static errcode_t flush_cached_blocks(io_channel channel,
 	if ((flags & FLUSH_NOLOCK) == 0)
 		mutex_lock(data, CACHE_MTX);
 	for (i=0, cache = data->cache; i < data->cache_size; i++, cache++) {
-		if (!cache->in_use || !cache->dirty)
+		if (!cache->in_use)
 			continue;
-		retval = raw_write_blk(channel, data,
-				       cache->block, 1, cache->buf,
-				       RAW_WRITE_NO_HANDLER);
-		if (retval) {
-			cache->write_err = 1;
-			errors_found = 1;
-			retval2 = retval;
-		} else {
-			cache->dirty = 0;
-			cache->write_err = 0;
-			if (flags & FLUSH_INVALIDATE)
-				cache->in_use = 0;
+		if (cache->dirty) {
+			retval = raw_write_blk(channel, data,
+					       cache->block, 1, cache->buf,
+					       RAW_WRITE_NO_HANDLER);
+			if (retval) {
+				cache->write_err = 1;
+				errors_found = 1;
+				retval2 = retval;
+			} else {
+				cache->dirty = 0;
+				cache->write_err = 0;
+				if (flags & FLUSH_INVALIDATE)
+					cache->in_use = 0;
+			}
+		} else if (flags & FLUSH_INVALIDATE) {
+			cache->in_use = 0;
 		}
 	}
 	if ((flags & FLUSH_NOLOCK) == 0)


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 02/29] libext2fs: fix livelock in the unix io manager
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 01/29] libext2fs: fix unix io manager invalidation Darrick J. Wong
@ 2025-05-21 22:35 ` Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 03/29] fuse2fs: clean up error messages Darrick J. Wong
                   ` (27 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

generic/441 found a livelock in the unix IO manager.  Let's say that
write_primary_superblock decides to call io_channel_set_blksize in the
process of writing the primary super.

unix_set_blksize then takes the cache and bounce mutexes, and calls
flush_cached_blocks.  If there are dirty blocks in the cache, they will
be written with raw_write_blk.  Unfortunately, that function tries to
take the bounce mutex, which we already hold.  At that point, we
livelock fuse2fs.

Cc: <linux-ext4@vger.kernel.org> # v1.46.0
Fixes: f20627cc639ab6 ("libext2fs: add threading support to the I/O manager abstraction")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/unix_io.c |   25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)


diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index b98c44a84bb0af..be70fee38890c8 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -344,7 +344,8 @@ static errcode_t raw_read_blk(io_channel channel,
 	return retval;
 }
 
-#define RAW_WRITE_NO_HANDLER	1
+#define RAW_WRITE_NO_HANDLER	(1U << 0)
+#define RAW_WRITE_NOLOCK	(1U << 1)
 
 static errcode_t raw_write_blk(io_channel channel,
 			       struct unix_private_data *data,
@@ -404,13 +405,15 @@ static errcode_t raw_write_blk(io_channel channel,
 	    (IS_ALIGNED(buf, channel->align) &&
 	     IS_ALIGNED(location, channel->align) &&
 	     IS_ALIGNED(size, channel->align))) {
-		mutex_lock(data, BOUNCE_MTX);
+		if (!(flags & RAW_WRITE_NOLOCK))
+			mutex_lock(data, BOUNCE_MTX);
 		if (ext2fs_llseek(data->dev, location, SEEK_SET) < 0) {
 			retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 			goto error_unlock;
 		}
 		actual = write(data->dev, buf, size);
-		mutex_unlock(data, BOUNCE_MTX);
+		if (!(flags & RAW_WRITE_NOLOCK))
+			mutex_unlock(data, BOUNCE_MTX);
 		if (actual < 0) {
 			retval = errno;
 			goto error_out;
@@ -445,7 +448,8 @@ static errcode_t raw_write_blk(io_channel channel,
 	while (size > 0) {
 		int actual_w;
 
-		mutex_lock(data, BOUNCE_MTX);
+		if (!(flags & RAW_WRITE_NOLOCK))
+			mutex_lock(data, BOUNCE_MTX);
 		if (size < align_size || offset) {
 			if (ext2fs_llseek(data->dev, aligned_blk * align_size,
 					  SEEK_SET) < 0) {
@@ -474,7 +478,8 @@ static errcode_t raw_write_blk(io_channel channel,
 			goto error_unlock;
 		}
 		actual_w = write(data->dev, data->bounce, align_size);
-		mutex_unlock(data, BOUNCE_MTX);
+		if (!(flags & RAW_WRITE_NOLOCK))
+			mutex_unlock(data, BOUNCE_MTX);
 		if (actual_w < 0) {
 			retval = errno;
 			goto error_out;
@@ -490,7 +495,8 @@ static errcode_t raw_write_blk(io_channel channel,
 	return 0;
 
 error_unlock:
-	mutex_unlock(data, BOUNCE_MTX);
+	if (!(flags & RAW_WRITE_NOLOCK))
+		mutex_unlock(data, BOUNCE_MTX);
 error_out:
 	if (((flags & RAW_WRITE_NO_HANDLER) == 0) && channel->write_error)
 		retval = (channel->write_error)(channel, block, count, buf,
@@ -673,9 +679,14 @@ static errcode_t flush_cached_blocks(io_channel channel,
 		if (!cache->in_use)
 			continue;
 		if (cache->dirty) {
+			int raw_flags = RAW_WRITE_NO_HANDLER;
+
+			if (flags & FLUSH_NOLOCK)
+				raw_flags |= RAW_WRITE_NOLOCK;
+
 			retval = raw_write_blk(channel, data,
 					       cache->block, 1, cache->buf,
-					       RAW_WRITE_NO_HANDLER);
+					       raw_flags);
 			if (retval) {
 				cache->write_err = 1;
 				errors_found = 1;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 03/29] fuse2fs: clean up error messages
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 01/29] libext2fs: fix unix io manager invalidation Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 02/29] libext2fs: fix livelock in the unix io manager Darrick J. Wong
@ 2025-05-21 22:35 ` Darrick J. Wong
  2025-05-21 22:35 ` [PATCH 04/29] fuse2fs: fix cache size parsing Darrick J. Wong
                   ` (26 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Instead of horridly line-wrapping multi-line messages that are printed
during mounting, let's just expand them to be one source code line per
printed line.  This will make it a lot easier for someone who sees the
these errors to grep the source code to find out where they came from.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 58c80c5c9a1ea2..8d52e00e3ece48 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4023,8 +4023,7 @@ int main(int argc, char *argv[])
 	if (ext2fs_has_feature_journal_needs_recovery(global_fs->super)) {
 		if (fctx.norecovery) {
 			log_printf(&fctx, "%s\n",
-				   _("Mounting read-only without "
-				     "recovering journal."));
+ _("Mounting read-only without recovering journal."));
 			fctx.ro = 1;
 			global_fs->flags &= ~EXT2_FLAG_RW;
 		} else {
@@ -4043,13 +4042,10 @@ int main(int argc, char *argv[])
 
 	if (global_fs->flags & EXT2_FLAG_RW) {
 		if (ext2fs_has_feature_journal(global_fs->super))
-			log_printf(&fctx, "%s\n",
-				   _("Warning: fuse2fs does not support "
-				     "using the\n"
-				     "journal.  There may be file system "
-				     "corruption or data loss if\n"
-				     "the file system is not gracefully "
-				     "unmounted.\n"));
+			log_printf(&fctx, "%s",
+ _("Warning: fuse2fs does not support using the journal.\n"
+   "There may be file system corruption or data loss if\n"
+   "the file system is not gracefully unmounted.\n"));
 		err = ext2fs_read_inode_bitmap(global_fs);
 		if (err) {
 			translate_error(global_fs, 0, err);


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 04/29] fuse2fs: fix cache size parsing
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (2 preceding siblings ...)
  2025-05-21 22:35 ` [PATCH 03/29] fuse2fs: clean up error messages Darrick J. Wong
@ 2025-05-21 22:35 ` Darrick J. Wong
  2025-05-21 22:36 ` [PATCH 05/29] fuse2fs: compact all the boolean flags in struct fuse2fs Darrick J. Wong
                   ` (25 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Fix the cache size parsing of "cache_size=%s" -- the "%" is at position
11, not 12.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 8d52e00e3ece48..3e78b6b13fa7bb 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -3809,7 +3809,7 @@ static int fuse2fs_opt_proc(void *data, const char *arg,
 		}
 		return 1;
 	case FUSE2FS_CACHE_SIZE:
-		ff->cache_size = parse_num_blocks2(arg + 12, -1);
+		ff->cache_size = parse_num_blocks2(arg + 11, -1);
 		if (ff->cache_size < 1 || ff->cache_size > INT32_MAX) {
 			fprintf(stderr, "%s: %s\n", arg,
  _("cache size must be between 1 block and 2GB."));


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 05/29] fuse2fs: compact all the boolean flags in struct fuse2fs
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (3 preceding siblings ...)
  2025-05-21 22:35 ` [PATCH 04/29] fuse2fs: fix cache size parsing Darrick J. Wong
@ 2025-05-21 22:36 ` Darrick J. Wong
  2025-05-21 22:36 ` [PATCH 06/29] fuse2fs: support XATTR_CREATE/REPLACE in setxattr Darrick J. Wong
                   ` (24 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Compact all the booleans into u8 fields.  I'd go further and turn them
into bitfields but that breaks the fuse argument parsing macros, which
compute the offset of the structure fields, and gcc won't let us do that
to bit fields.  Still, 136 -> 112 bytes isn't bad.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 3e78b6b13fa7bb..40bb223d50c4fe 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -150,16 +150,16 @@ struct fuse2fs {
 	pthread_mutex_t bfl;
 	char *device;
 	char *shortdev;
-	int ro;
-	int debug;
-	int no_default_opts;
-	int panic_on_error;
-	int minixdf;
-	int fakeroot;
-	int alloc_all_blocks;
-	int norecovery;
-	int kernel;
-	int directio;
+	uint8_t ro;
+	uint8_t debug;
+	uint8_t no_default_opts;
+	uint8_t panic_on_error;
+	uint8_t minixdf;
+	uint8_t fakeroot;
+	uint8_t alloc_all_blocks;
+	uint8_t norecovery;
+	uint8_t kernel;
+	uint8_t directio;
 	unsigned long offset;
 	unsigned int next_generation;
 	unsigned long long cache_size;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 06/29] fuse2fs: support XATTR_CREATE/REPLACE in setxattr
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (4 preceding siblings ...)
  2025-05-21 22:36 ` [PATCH 05/29] fuse2fs: compact all the boolean flags in struct fuse2fs Darrick J. Wong
@ 2025-05-21 22:36 ` Darrick J. Wong
  2025-05-21 22:36 ` [PATCH 07/29] fuse2fs: fix error return handling in op_truncate Darrick J. Wong
                   ` (23 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Fix the setxattr implementation to support the create and replace flags
instead of performing an upsert regardless of inputs.

Cc: <linux-ext4@vger.kernel.org> # v1.43
Fixes: 81cbf1ef4f5dab ("misc: add fuse2fs, a FUSE server for e2fsprogs")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 40bb223d50c4fe..33f72cf08f7b3a 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2507,7 +2507,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
 
 static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 		       const char *key, const char *value,
-		       size_t len, int flags EXT2FS_ATTR((unused)))
+		       size_t len, int flags)
 {
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
@@ -2517,6 +2517,9 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 	errcode_t err;
 	int ret = 0;
 
+	if (flags & ~(XATTR_CREATE | XATTR_REPLACE))
+		return -EOPNOTSUPP;
+
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
@@ -2551,6 +2554,31 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 		goto out2;
 	}
 
+	if (flags & (XATTR_CREATE | XATTR_REPLACE)) {
+		void *buf;
+		size_t buflen;
+
+		err = ext2fs_xattr_get(h, key, &buf, &buflen);
+		switch (err) {
+		case EXT2_ET_EA_KEY_NOT_FOUND:
+			if (flags & XATTR_REPLACE) {
+				ret = -ENODATA;
+				goto out2;
+			}
+			break;
+		case 0:
+			ext2fs_free_mem(&buf);
+			if (flags & XATTR_CREATE) {
+				ret = -EEXIST;
+				goto out2;
+			}
+			break;
+		default:
+			ret = translate_error(fs, ino, err);
+			goto out2;
+		}
+	}
+
 	err = ext2fs_xattr_set(h, key, value, len);
 	if (err) {
 		ret = translate_error(fs, ino, err);


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 07/29] fuse2fs: fix error return handling in op_truncate
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (5 preceding siblings ...)
  2025-05-21 22:36 ` [PATCH 06/29] fuse2fs: support XATTR_CREATE/REPLACE in setxattr Darrick J. Wong
@ 2025-05-21 22:36 ` Darrick J. Wong
  2025-05-21 22:37 ` [PATCH 08/29] fuse2fs: flip parameter order in __translate_error Darrick J. Wong
                   ` (22 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Fix a couple of bugs with the errcode/ret handling in op_truncate.
First, we need to return ESTALE for a zero inumber because there is no
inode zero in an ext* filesystem.  Second, we need to return negative
errno for failures to libfuse, not raw errcode_t.

Cc: <linux-ext4@vger.kernel.org> # v1.43
Fixes: 81cbf1ef4f5dab ("misc: add fuse2fs, a FUSE server for e2fsprogs")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 33f72cf08f7b3a..74f1ca81aebc61 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1963,10 +1963,14 @@ static int op_truncate(const char *path, off_t len
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
+	if (err) {
 		ret = translate_error(fs, 0, err);
 		goto out;
 	}
+	if (!ino) {
+		ret = -ESTALE;
+		goto out;
+	}
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, len);
 
 	ret = check_inum_access(fs, ino, W_OK);
@@ -1998,7 +2002,7 @@ static int op_truncate(const char *path, off_t len
 
 out:
 	pthread_mutex_unlock(&ff->bfl);
-	return err;
+	return ret;
 }
 
 #ifdef __linux__


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 08/29] fuse2fs: flip parameter order in __translate_error
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (6 preceding siblings ...)
  2025-05-21 22:36 ` [PATCH 07/29] fuse2fs: fix error return handling in op_truncate Darrick J. Wong
@ 2025-05-21 22:37 ` Darrick J. Wong
  2025-05-21 22:37 ` [PATCH 09/29] fuse2fs: fix CLI argument parsing leaks Darrick J. Wong
                   ` (21 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Flip the parameter order in __translate_error so that it matches
translate_error.  I wasted too much time debugging a memory corruption
that happened because I converted translate_error to __translate_error
when developing the next patch and the compiler didn't warn me about
mismatched types.

Cc: <linux-ext4@vger.kernel.org> # v1.43
Fixes: 81cbf1ef4f5dab ("misc: add fuse2fs, a FUSE server for e2fsprogs")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 74f1ca81aebc61..e60065402a0a43 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -173,9 +173,9 @@ struct fuse2fs {
 	return translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC); \
 } while (0)
 
-static int __translate_error(ext2_filsys fs, errcode_t err, ext2_ino_t ino,
+static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 			     const char *file, int line);
-#define translate_error(fs, ino, err) __translate_error((fs), (err), (ino), \
+#define translate_error(fs, ino, err) __translate_error((fs), (ino), (err), \
 			__FILE__, __LINE__)
 
 /* for macosx */
@@ -4164,7 +4164,7 @@ int main(int argc, char *argv[])
 	return ret;
 }
 
-static int __translate_error(ext2_filsys fs, errcode_t err, ext2_ino_t ino,
+static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 			     const char *file, int line)
 {
 	struct timespec now;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 09/29] fuse2fs: fix CLI argument parsing leaks
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (7 preceding siblings ...)
  2025-05-21 22:37 ` [PATCH 08/29] fuse2fs: flip parameter order in __translate_error Darrick J. Wong
@ 2025-05-21 22:37 ` Darrick J. Wong
  2025-05-21 22:37 ` [PATCH 10/29] fuse2fs: allow some control over acls Darrick J. Wong
                   ` (20 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Fix some memory leaks in the argument parser.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    3 +++
 1 file changed, 3 insertions(+)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index e60065402a0a43..40105368775a93 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4161,6 +4161,9 @@ int main(int argc, char *argv[])
 			com_err(argv[0], err, "while closing fs");
 		global_fs = NULL;
 	}
+	if (fctx.device)
+		free(fctx.device);
+	fuse_opt_free_args(&args);
 	return ret;
 }
 


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 10/29] fuse2fs: allow some control over acls
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (8 preceding siblings ...)
  2025-05-21 22:37 ` [PATCH 09/29] fuse2fs: fix CLI argument parsing leaks Darrick J. Wong
@ 2025-05-21 22:37 ` Darrick J. Wong
  2025-05-21 22:37 ` [PATCH 11/29] fuse2fs: enable processing of acls in the kernel Darrick J. Wong
                   ` (19 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Allow some control over whether or not ACLs get used, though for kernel
mode it will always be enabled.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 40105368775a93..a0e5d601e55877 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -160,6 +160,7 @@ struct fuse2fs {
 	uint8_t norecovery;
 	uint8_t kernel;
 	uint8_t directio;
+	uint8_t acl;
 	unsigned long offset;
 	unsigned int next_generation;
 	unsigned long long cache_size;
@@ -648,6 +649,10 @@ static void *op_init(struct fuse_conn_info *conn
 #ifdef FUSE_CAP_IOCTL_DIR
 	conn->want |= FUSE_CAP_IOCTL_DIR;
 #endif
+#ifdef FUSE_CAP_POSIX_ACL
+	if (ff->acl)
+		conn->want |= FUSE_CAP_POSIX_ACL;
+#endif
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	conn->time_gran = 1;
 	cfg->use_ino = 1;
@@ -3813,8 +3818,9 @@ static struct fuse_opt fuse2fs_opts[] = {
 	FUSE2FS_OPT("offset=%lu",	offset,			0),
 	FUSE2FS_OPT("kernel",		kernel,			1),
 	FUSE2FS_OPT("directio",		directio,		1),
+	FUSE2FS_OPT("acl",		acl,			1),
+	FUSE2FS_OPT("noacl",		acl,			0),
 
-	FUSE_OPT_KEY("acl",		FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("user_xattr",	FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("noblock_validity", FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("cache_size=%s",	FUSE2FS_CACHE_SIZE),


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 11/29] fuse2fs: enable processing of acls in the kernel
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (9 preceding siblings ...)
  2025-05-21 22:37 ` [PATCH 10/29] fuse2fs: allow some control over acls Darrick J. Wong
@ 2025-05-21 22:37 ` Darrick J. Wong
  2025-05-21 22:38 ` [PATCH 12/29] fuse2fs: make removexattr work correctly Darrick J. Wong
                   ` (18 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Let the kernel process ACLs.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index a0e5d601e55877..ce5314fa439090 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4141,9 +4141,15 @@ int main(int argc, char *argv[])
 #endif
 	}
 
-	if (fctx.kernel)
+	if (fctx.kernel) {
+		/*
+		 * ACLs are always enforced when kernel mode is enabled, to
+		 * match the kernel ext4 driver which always enables ACLs.
+		 */
+		fctx.acl = 1;
 		fuse_opt_insert_arg(&args, 1,
  "-oallow_other,default_permissions,suid,dev");
+	}
 
 	if (fctx.debug) {
 		int	i;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 12/29] fuse2fs: make removexattr work correctly
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (10 preceding siblings ...)
  2025-05-21 22:37 ` [PATCH 11/29] fuse2fs: enable processing of acls in the kernel Darrick J. Wong
@ 2025-05-21 22:38 ` Darrick J. Wong
  2025-05-21 22:38 ` [PATCH 13/29] fuse2fs: implement O_TRUNC correctly Darrick J. Wong
                   ` (17 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

removexattr is supposed to return ENODATA if the xattr name does not
exist, so we need to check for it explicitly.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index ce5314fa439090..299e62d3935886 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2611,6 +2611,8 @@ static int op_removexattr(const char *path, const char *key)
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
+	void *buf;
+	size_t buflen;
 	ext2_ino_t ino;
 	errcode_t err;
 	int ret = 0;
@@ -2651,6 +2653,27 @@ static int op_removexattr(const char *path, const char *key)
 		goto out2;
 	}
 
+	err = ext2fs_xattr_get(h, key, &buf, &buflen);
+	switch (err) {
+	case EXT2_ET_EA_KEY_NOT_FOUND:
+		/*
+		 * ACLs are special snowflakes that require a 0 return when
+		 * the ACL never existed in the first place.
+		 */
+		if (!strncmp(XATTR_SECURITY_PREFIX, key,
+			     XATTR_SECURITY_PREFIX_LEN))
+			ret = 0;
+		else
+			ret = -ENODATA;
+		goto out2;
+	case 0:
+		ext2fs_free_mem(&buf);
+		break;
+	default:
+		ret = translate_error(fs, ino, err);
+		goto out2;
+	}
+
 	err = ext2fs_xattr_remove(h, key);
 	if (err) {
 		ret = translate_error(fs, ino, err);


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 13/29] fuse2fs: implement O_TRUNC correctly
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (11 preceding siblings ...)
  2025-05-21 22:38 ` [PATCH 12/29] fuse2fs: make removexattr work correctly Darrick J. Wong
@ 2025-05-21 22:38 ` Darrick J. Wong
  2025-05-21 22:38 ` [PATCH 14/29] fuse2fs: rearrange check_inum_access parameters a bit Darrick J. Wong
                   ` (16 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Actually truncate files on open with O_TRUNC.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   56 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 33 insertions(+), 23 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 299e62d3935886..9e7d8b8fe5118d 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1950,6 +1950,29 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 	return ret;
 }
 
+static int truncate_helper(ext2_filsys fs, ext2_ino_t ino, off_t new_size)
+{
+	ext2_file_t file;
+	errcode_t err;
+	int ret = 0;
+
+	err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	err = ext2fs_file_set_size2(file, new_size);
+	if (err)
+		ret = translate_error(fs, ino, err);
+
+	err = ext2fs_file_close(file);
+	if (ret)
+		return ret;
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return update_mtime(fs, ino, NULL);
+}
+
 static int op_truncate(const char *path, off_t len
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, struct fuse_file_info *fi EXT2FS_ATTR((unused))
@@ -1959,9 +1982,8 @@ static int op_truncate(const char *path, off_t len
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
 	ext2_filsys fs;
-	errcode_t err;
 	ext2_ino_t ino;
-	ext2_file_t file;
+	errcode_t err;
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -1982,28 +2004,9 @@ static int op_truncate(const char *path, off_t len
 	if (ret)
 		goto out;
 
-	err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
-
-	err = ext2fs_file_set_size2(file, len);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out2;
-	}
-
-out2:
-	err = ext2fs_file_close(file);
+	ret = truncate_helper(fs, ino, len);
 	if (ret)
 		goto out;
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
-
-	ret = update_mtime(fs, ino, NULL);
 
 out:
 	pthread_mutex_unlock(&ff->bfl);
@@ -2039,7 +2042,7 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 	struct fuse2fs_file_handle *file;
 	int check = 0, ret = 0;
 
-	dbg_printf(ff, "%s: path=%s\n", __func__, path);
+	dbg_printf(ff, "%s: path=%s oflags=0o%o\n", __func__, path, fp->flags);
 	err = ext2fs_get_mem(sizeof(*file), &file);
 	if (err)
 		return translate_error(fs, 0, err);
@@ -2090,6 +2093,13 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 		} else
 			goto out;
 	}
+
+	if (fp->flags & O_TRUNC) {
+		ret = truncate_helper(fs, file->ino, 0);
+		if (ret)
+			goto out;
+	}
+
 	fp->fh = (uintptr_t)file;
 
 out:


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 14/29] fuse2fs: rearrange check_inum_access parameters a bit
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (12 preceding siblings ...)
  2025-05-21 22:38 ` [PATCH 13/29] fuse2fs: implement O_TRUNC correctly Darrick J. Wong
@ 2025-05-21 22:38 ` Darrick J. Wong
  2025-05-21 22:38 ` [PATCH 15/29] fuse2fs: make filesystem corruption a hard error Darrick J. Wong
                   ` (15 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Pass the struct fuse2fs pointer to check_inum_access so that we can do
some more rearranging in the next patches.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 9e7d8b8fe5118d..eb1ac818359c19 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -507,10 +507,10 @@ static inline int want_check_owner(struct fuse2fs *ff,
 	return !is_superuser(ff, ctxt);
 }
 
-static int check_inum_access(ext2_filsys fs, ext2_ino_t ino, mode_t mask)
+static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	ext2_filsys fs = ff->fs;
 	struct ext2_inode inode;
 	mode_t perms;
 	errcode_t err;
@@ -871,7 +871,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, parent, W_OK);
+	ret = check_inum_access(ff, parent, W_OK);
 	if (ret)
 		goto out2;
 
@@ -1002,7 +1002,7 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, parent, W_OK);
+	ret = check_inum_access(ff, parent, W_OK);
 	if (ret)
 		goto out2;
 
@@ -1123,7 +1123,7 @@ static int unlink_file_by_name(struct fuse2fs *ff, const char *path)
 		base_name = filename;
 	}
 
-	ret = check_inum_access(fs, dir, W_OK);
+	ret = check_inum_access(ff, dir, W_OK);
 	if (ret) {
 		free(filename);
 		return ret;
@@ -1387,7 +1387,7 @@ static int op_symlink(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, parent, W_OK);
+	ret = check_inum_access(ff, parent, W_OK);
 	if (ret)
 		goto out2;
 
@@ -1560,7 +1560,7 @@ static int op_rename(const char *from, const char *to
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, from_dir_ino, W_OK);
+	ret = check_inum_access(ff, from_dir_ino, W_OK);
 	if (ret)
 		goto out2;
 
@@ -1585,7 +1585,7 @@ static int op_rename(const char *from, const char *to
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, to_dir_ino, W_OK);
+	ret = check_inum_access(ff, to_dir_ino, W_OK);
 	if (ret)
 		goto out2;
 
@@ -1752,7 +1752,7 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, parent, W_OK);
+	ret = check_inum_access(ff, parent, W_OK);
 	if (ret)
 		goto out2;
 
@@ -2000,7 +2000,7 @@ static int op_truncate(const char *path, off_t len
 	}
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, len);
 
-	ret = check_inum_access(fs, ino, W_OK);
+	ret = check_inum_access(ff, ino, W_OK);
 	if (ret)
 		goto out;
 
@@ -2075,7 +2075,7 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 	}
 	dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
 
-	ret = check_inum_access(fs, file->ino, check);
+	ret = check_inum_access(ff, file->ino, check);
 	if (ret) {
 		/*
 		 * In a regular (Linux) fs driver, the kernel will open
@@ -2087,7 +2087,7 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 		 * also employ undocumented hacks (see above).
 		 */
 		if (check == R_OK) {
-			ret = check_inum_access(fs, file->ino, X_OK);
+			ret = check_inum_access(ff, file->ino, X_OK);
 			if (ret)
 				goto out;
 		} else
@@ -2384,7 +2384,7 @@ static int op_getxattr(const char *path, const char *key, char *value,
 	}
 	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
 
-	ret = check_inum_access(fs, ino, R_OK);
+	ret = check_inum_access(ff, ino, R_OK);
 	if (ret)
 		goto out;
 
@@ -2474,7 +2474,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
 	}
 	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
 
-	ret = check_inum_access(fs, ino, R_OK);
+	ret = check_inum_access(ff, ino, R_OK);
 	if (ret)
 		goto out;
 
@@ -2554,7 +2554,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 	}
 	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
 
-	ret = check_inum_access(fs, ino, W_OK);
+	ret = check_inum_access(ff, ino, W_OK);
 	if (ret == -EACCES) {
 		ret = -EPERM;
 		goto out;
@@ -2647,7 +2647,7 @@ static int op_removexattr(const char *path, const char *key)
 	}
 	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
 
-	ret = check_inum_access(fs, ino, W_OK);
+	ret = check_inum_access(ff, ino, W_OK);
 	if (ret)
 		goto out;
 
@@ -2820,7 +2820,7 @@ static int op_access(const char *path, int mask)
 		goto out;
 	}
 
-	ret = check_inum_access(fs, ino, mask);
+	ret = check_inum_access(ff, ino, mask);
 	if (ret)
 		goto out;
 
@@ -2872,7 +2872,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 	}
 
-	ret = check_inum_access(fs, parent, W_OK);
+	ret = check_inum_access(ff, parent, W_OK);
 	if (ret)
 		goto out2;
 
@@ -3059,7 +3059,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 			(long long int)ctv[0].tv_sec, ctv[0].tv_nsec,
 			(long long int)ctv[1].tv_sec, ctv[1].tv_nsec);
 
-	ret = check_inum_access(fs, ino, W_OK);
+	ret = check_inum_access(ff, ino, W_OK);
 	if (ret)
 		goto out;
 


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 15/29] fuse2fs: make filesystem corruption a hard error
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (13 preceding siblings ...)
  2025-05-21 22:38 ` [PATCH 14/29] fuse2fs: rearrange check_inum_access parameters a bit Darrick J. Wong
@ 2025-05-21 22:38 ` Darrick J. Wong
  2025-05-21 22:39 ` [PATCH 16/29] fuse2fs: make internal state " Darrick J. Wong
                   ` (14 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Change __translate_error so that all the EXT2_*_CORRUPTED codes generate
a hard error returning EUCLEAN which is the same errno that the kernel
uses.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   13 +++++++++++++
 1 file changed, 13 insertions(+)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index eb1ac818359c19..f0ac89db4f6c37 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4275,6 +4275,19 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 	case EXT2_ET_UNIMPLEMENTED:
 		ret = -EOPNOTSUPP;
 		break;
+	case EXT2_ET_DIR_CORRUPTED:
+	case EXT2_ET_CORRUPT_SUPERBLOCK:
+	case EXT2_ET_RESIZE_INODE_CORRUPT:
+	case EXT2_ET_TDB_ERR_CORRUPT:
+	case EXT2_ET_UNDO_FILE_CORRUPT:
+	case EXT2_ET_FILESYSTEM_CORRUPTED:
+	case EXT2_ET_CORRUPT_JOURNAL_SB:
+	case EXT2_ET_INODE_CORRUPTED:
+	case EXT2_ET_EA_INODE_CORRUPTED:
+		/* same errno that linux uses */
+		is_err = 1;
+		ret = -EUCLEAN;
+		break;
 	default:
 		is_err = 1;
 		ret = -EIO;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 16/29] fuse2fs: make internal state corruption a hard error
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (14 preceding siblings ...)
  2025-05-21 22:38 ` [PATCH 15/29] fuse2fs: make filesystem corruption a hard error Darrick J. Wong
@ 2025-05-21 22:39 ` Darrick J. Wong
  2025-05-21 22:39 ` [PATCH 17/29] fuse2fs: make bad magic numbers report a corruption error too Darrick J. Wong
                   ` (13 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

If the in-memory contents are wrong, then we want to report a corruption
error and shut down the filesystem.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index f0ac89db4f6c37..6b5b19062b4ca1 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -167,11 +167,11 @@ struct fuse2fs {
 };
 
 #define FUSE2FS_CHECK_MAGIC(fs, ptr, num) do {if ((ptr)->magic != (num)) \
-	return translate_error((fs), 0, EXT2_ET_MAGIC_EXT2_FILE); \
+	return translate_error((fs), 0, EXT2_ET_FILESYSTEM_CORRUPTED); \
 } while (0)
 
 #define FUSE2FS_CHECK_CONTEXT(ptr) do {if ((ptr)->magic != FUSE2FS_MAGIC) \
-	return translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC); \
+	return translate_error(global_fs, 0, EXT2_ET_FILESYSTEM_CORRUPTED); \
 } while (0)
 
 static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 17/29] fuse2fs: make bad magic numbers report a corruption error too
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (15 preceding siblings ...)
  2025-05-21 22:39 ` [PATCH 16/29] fuse2fs: make internal state " Darrick J. Wong
@ 2025-05-21 22:39 ` Darrick J. Wong
  2025-05-21 22:39 ` [PATCH 18/29] fuse2fs: return EPERM for write access to EXT2_IMMUTABLE_FL files Darrick J. Wong
                   ` (12 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Report bad magic numbers as corruption errors too.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 6b5b19062b4ca1..e73730cfe27130 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4275,6 +4275,37 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 	case EXT2_ET_UNIMPLEMENTED:
 		ret = -EOPNOTSUPP;
 		break;
+	case EXT2_ET_MAGIC_EXT2FS_FILSYS:
+	case EXT2_ET_MAGIC_BADBLOCKS_LIST:
+	case EXT2_ET_MAGIC_BADBLOCKS_ITERATE:
+	case EXT2_ET_MAGIC_INODE_SCAN:
+	case EXT2_ET_MAGIC_IO_CHANNEL:
+	case EXT2_ET_MAGIC_UNIX_IO_CHANNEL:
+	case EXT2_ET_MAGIC_IO_MANAGER:
+	case EXT2_ET_MAGIC_BLOCK_BITMAP:
+	case EXT2_ET_MAGIC_INODE_BITMAP:
+	case EXT2_ET_MAGIC_GENERIC_BITMAP:
+	case EXT2_ET_MAGIC_TEST_IO_CHANNEL:
+	case EXT2_ET_MAGIC_DBLIST:
+	case EXT2_ET_MAGIC_ICOUNT:
+	case EXT2_ET_MAGIC_PQ_IO_CHANNEL:
+	case EXT2_ET_MAGIC_E2IMAGE:
+	case EXT2_ET_MAGIC_INODE_IO_CHANNEL:
+	case EXT2_ET_MAGIC_EXTENT_HANDLE:
+	case EXT2_ET_BAD_MAGIC:
+	case EXT2_ET_MAGIC_EXTENT_PATH:
+	case EXT2_ET_MAGIC_GENERIC_BITMAP64:
+	case EXT2_ET_MAGIC_BLOCK_BITMAP64:
+	case EXT2_ET_MAGIC_INODE_BITMAP64:
+	case EXT2_ET_MAGIC_RESERVED_13:
+	case EXT2_ET_MAGIC_RESERVED_14:
+	case EXT2_ET_MAGIC_RESERVED_15:
+	case EXT2_ET_MAGIC_RESERVED_16:
+	case EXT2_ET_MAGIC_RESERVED_17:
+	case EXT2_ET_MAGIC_RESERVED_18:
+	case EXT2_ET_MAGIC_RESERVED_19:
+	case EXT2_ET_MMP_MAGIC_INVALID:
+	case EXT2_ET_MAGIC_EA_HANDLE:
 	case EXT2_ET_DIR_CORRUPTED:
 	case EXT2_ET_CORRUPT_SUPERBLOCK:
 	case EXT2_ET_RESIZE_INODE_CORRUPT:


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 18/29] fuse2fs: return EPERM for write access to EXT2_IMMUTABLE_FL files
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (16 preceding siblings ...)
  2025-05-21 22:39 ` [PATCH 17/29] fuse2fs: make bad magic numbers report a corruption error too Darrick J. Wong
@ 2025-05-21 22:39 ` Darrick J. Wong
  2025-05-21 22:39 ` [PATCH 19/29] fuse2fs: check the immutable flag in more places Darrick J. Wong
                   ` (11 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

The kernel drivers return EPERM for attempts to write to immutable
files, so switch the error code.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index e73730cfe27130..06d59a3e824e09 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -524,11 +524,14 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 		return translate_error(fs, ino, err);
 	perms = inode.i_mode & 0777;
 
-	dbg_printf(ff, "access ino=%d mask=e%s%s%s perms=0%o fuid=%d fgid=%d "
-		   "uid=%d gid=%d\n", ino,
-		   (mask & R_OK ? "r" : ""), (mask & W_OK ? "w" : ""),
-		   (mask & X_OK ? "x" : ""), perms, inode_uid(inode),
-		   inode_gid(inode), ctxt->uid, ctxt->gid);
+	dbg_printf(ff, "access ino=%d mask=e%s%s%s perms=0%o iflags=0x%x "
+		   "fuid=%d fgid=%d uid=%d gid=%d\n", ino,
+		   (mask & R_OK ? "r" : ""),
+		   (mask & W_OK ? "w" : ""),
+		   (mask & X_OK ? "x" : ""),
+		   perms, inode.i_flags,
+		   inode_uid(inode), inode_gid(inode),
+		   ctxt->uid, ctxt->gid);
 
 	/* existence check */
 	if (mask == 0)
@@ -537,7 +540,7 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 	/* is immutable? */
 	if ((mask & W_OK) &&
 	    (inode.i_flags & EXT2_IMMUTABLE_FL))
-		return -EACCES;
+		return -EPERM;
 
 	/* If kernel is responsible for mode and acl checks, we're done. */
 	if (ff->kernel)


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 19/29] fuse2fs: check the immutable flag in more places
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (17 preceding siblings ...)
  2025-05-21 22:39 ` [PATCH 18/29] fuse2fs: return EPERM for write access to EXT2_IMMUTABLE_FL files Darrick J. Wong
@ 2025-05-21 22:39 ` Darrick J. Wong
  2025-05-21 22:40 ` [PATCH 20/29] fuse2fs: implement O_APPEND correctly Darrick J. Wong
                   ` (10 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

We need to check the immutable flag in a few more places that try to
modify files.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   73 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 68 insertions(+), 5 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 06d59a3e824e09..8567d2a8801bb6 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -507,6 +507,30 @@ static inline int want_check_owner(struct fuse2fs *ff,
 	return !is_superuser(ff, ctxt);
 }
 
+static int check_iflags_access(struct fuse2fs *ff, ext2_ino_t ino,
+			       const struct ext2_inode *inode, int mask)
+{
+	ext2_filsys fs = ff->fs;
+
+	/* no writing to read-only or broken fs */
+	if ((mask & W_OK) && !fs_writeable(fs))
+		return -EROFS;
+
+	dbg_printf(ff, "access ino=%d mask=e%s%s%s iflags=0x%x\n",
+		   ino,
+		   (mask & R_OK ? "r" : ""),
+		   (mask & W_OK ? "w" : ""),
+		   (mask & X_OK ? "x" : ""),
+		   inode->i_flags);
+
+	/* is immutable? */
+	if ((mask & W_OK) &&
+	    (inode->i_flags & EXT2_IMMUTABLE_FL))
+		return -EPERM;
+
+	return 0;
+}
+
 static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 {
 	struct fuse_context *ctxt = fuse_get_context();
@@ -514,6 +538,7 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 	struct ext2_inode inode;
 	mode_t perms;
 	errcode_t err;
+	int ret;
 
 	/* no writing to read-only or broken fs */
 	if ((mask & W_OK) && !fs_writeable(fs))
@@ -537,10 +562,9 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 	if (mask == 0)
 		return 0;
 
-	/* is immutable? */
-	if ((mask & W_OK) &&
-	    (inode.i_flags & EXT2_IMMUTABLE_FL))
-		return -EPERM;
+	ret = check_iflags_access(ff, ino, &inode, mask);
+	if (ret)
+		return ret;
 
 	/* If kernel is responsible for mode and acl checks, we're done. */
 	if (ff->kernel)
@@ -1218,6 +1242,10 @@ static int __op_unlink(struct fuse2fs *ff, const char *path)
 		goto out;
 	}
 
+	ret = check_inum_access(ff, ino, W_OK);
+	if (ret)
+		goto out;
+
 	ret = unlink_file_by_name(ff, path);
 	if (ret)
 		goto out;
@@ -1286,6 +1314,10 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 	}
 	dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
 
+	ret = check_inum_access(ff, child, W_OK);
+	if (ret)
+		goto out;
+
 	rds.parent = 0;
 	rds.empty = 1;
 
@@ -1295,6 +1327,16 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 		goto out;
 	}
 
+	/* the kernel checks parent permissions before emptiness */
+	if (rds.parent == 0) {
+		ret = translate_error(fs, child, EXT2_ET_FILESYSTEM_CORRUPTED);
+		goto out;
+	}
+
+	ret = check_inum_access(ff, rds.parent, W_OK);
+	if (ret)
+		goto out;
+
 	if (rds.empty == 0) {
 		ret = -ENOTEMPTY;
 		goto out;
@@ -1530,6 +1572,16 @@ static int op_rename(const char *from, const char *to
 		goto out;
 	}
 
+	ret = check_inum_access(ff, from_ino, W_OK);
+	if (ret)
+		goto out;
+
+	if (to_ino) {
+		ret = check_inum_access(ff, to_ino, W_OK);
+		if (ret)
+			goto out;
+	}
+
 	temp_to = strdup(to);
 	if (!temp_to) {
 		ret = -ENOMEM;
@@ -1759,7 +1811,6 @@ static int op_link(const char *src, const char *dest)
 	if (ret)
 		goto out2;
 
-
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, src, &ino);
 	if (err || ino == 0) {
 		ret = translate_error(fs, 0, err);
@@ -1774,6 +1825,10 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 	}
 
+	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	if (ret)
+		goto out2;
+
 	inode.i_links_count++;
 	ret = update_ctime(fs, ino, &inode);
 	if (ret)
@@ -1848,6 +1903,10 @@ static int op_chmod(const char *path, mode_t mode
 		goto out;
 	}
 
+	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	if (ret)
+		goto out;
+
 	if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
 		ret = -EPERM;
 		goto out;
@@ -1912,6 +1971,10 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 		goto out;
 	}
 
+	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	if (ret)
+		goto out;
+
 	/* FUSE seems to feed us ~0 to mean "don't change" */
 	if (owner != (uid_t) ~0) {
 		/* Only root gets to change UID. */


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 20/29] fuse2fs: implement O_APPEND correctly
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (18 preceding siblings ...)
  2025-05-21 22:39 ` [PATCH 19/29] fuse2fs: check the immutable flag in more places Darrick J. Wong
@ 2025-05-21 22:40 ` Darrick J. Wong
  2025-05-21 22:40 ` [PATCH 21/29] fuse2fs: decode fuse_main error codes Darrick J. Wong
                   ` (9 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Try to implement append-only files correctly.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   50 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 39 insertions(+), 11 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 8567d2a8801bb6..52c24715fbc109 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -41,6 +41,7 @@
 #include <inttypes.h>
 #include "ext2fs/ext2fs.h"
 #include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fsP.h"
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 # define FUSE_PLATFORM_OPTS	""
 #else
@@ -507,20 +508,26 @@ static inline int want_check_owner(struct fuse2fs *ff,
 	return !is_superuser(ff, ctxt);
 }
 
+/* Test for append permission */
+#define A_OK	16
+
 static int check_iflags_access(struct fuse2fs *ff, ext2_ino_t ino,
 			       const struct ext2_inode *inode, int mask)
 {
 	ext2_filsys fs = ff->fs;
 
-	/* no writing to read-only or broken fs */
-	if ((mask & W_OK) && !fs_writeable(fs))
+	EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
+
+	/* no writing or metadata changes to read-only or broken fs */
+	if ((mask & (W_OK | A_OK)) && !fs_writeable(fs))
 		return -EROFS;
 
-	dbg_printf(ff, "access ino=%d mask=e%s%s%s iflags=0x%x\n",
+	dbg_printf(ff, "access ino=%d mask=e%s%s%s%s iflags=0x%x\n",
 		   ino,
 		   (mask & R_OK ? "r" : ""),
 		   (mask & W_OK ? "w" : ""),
 		   (mask & X_OK ? "x" : ""),
+		   (mask & A_OK ? "a" : ""),
 		   inode->i_flags);
 
 	/* is immutable? */
@@ -528,6 +535,10 @@ static int check_iflags_access(struct fuse2fs *ff, ext2_ino_t ino,
 	    (inode->i_flags & EXT2_IMMUTABLE_FL))
 		return -EPERM;
 
+	/* is append-only? */
+	if ((inode->i_flags & EXT2_APPEND_FL) && (mask & W_OK) && !(mask & A_OK))
+		return -EPERM;
+
 	return 0;
 }
 
@@ -541,7 +552,7 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 	int ret;
 
 	/* no writing to read-only or broken fs */
-	if ((mask & W_OK) && !fs_writeable(fs))
+	if ((mask & (W_OK | A_OK)) && !fs_writeable(fs))
 		return -EROFS;
 
 	err = ext2fs_read_inode(fs, ino, &inode);
@@ -549,11 +560,12 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 		return translate_error(fs, ino, err);
 	perms = inode.i_mode & 0777;
 
-	dbg_printf(ff, "access ino=%d mask=e%s%s%s perms=0%o iflags=0x%x "
+	dbg_printf(ff, "access ino=%d mask=e%s%s%s%s perms=0%o iflags=0x%x "
 		   "fuid=%d fgid=%d uid=%d gid=%d\n", ino,
 		   (mask & R_OK ? "r" : ""),
 		   (mask & W_OK ? "w" : ""),
 		   (mask & X_OK ? "x" : ""),
+		   (mask & A_OK ? "a" : ""),
 		   perms, inode.i_flags,
 		   inode_uid(inode), inode_gid(inode),
 		   ctxt->uid, ctxt->gid);
@@ -898,7 +910,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, W_OK);
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -1029,7 +1041,7 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, W_OK);
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -1432,7 +1444,7 @@ static int op_symlink(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, W_OK);
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -1807,7 +1819,7 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, W_OK);
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -2128,6 +2140,15 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 		file->open_flags |= EXT2_FILE_WRITE;
 		break;
 	}
+	if (fp->flags & O_APPEND) {
+		/* the kernel doesn't allow truncation of an append-only file */
+		if (fp->flags & O_TRUNC) {
+			ret = -EPERM;
+			goto out;
+		}
+
+		check |= A_OK;
+	}
 
 	detect_linux_executable_open(fp->flags, &check, &file->open_flags);
 
@@ -2938,7 +2959,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, W_OK);
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -3110,6 +3131,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 	errcode_t err;
 	ext2_ino_t ino;
 	struct ext2_inode_large inode;
+	int access = W_OK;
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -3125,7 +3147,13 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 			(long long int)ctv[0].tv_sec, ctv[0].tv_nsec,
 			(long long int)ctv[1].tv_sec, ctv[1].tv_nsec);
 
-	ret = check_inum_access(ff, ino, W_OK);
+	/*
+	 * ext4 allows timestamp updates of append-only files but only if we're
+	 * setting to current time
+	 */
+	if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
+		access |= A_OK;
+	ret = check_inum_access(ff, ino, access);
 	if (ret)
 		goto out;
 


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 21/29] fuse2fs: decode fuse_main error codes
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (19 preceding siblings ...)
  2025-05-21 22:40 ` [PATCH 20/29] fuse2fs: implement O_APPEND correctly Darrick J. Wong
@ 2025-05-21 22:40 ` Darrick J. Wong
  2025-05-21 22:40 ` [PATCH 22/29] fuse2fs: fix fallocate zero range Darrick J. Wong
                   ` (8 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Translate the fuse_main return values into actual mount(8) style error
codes instead of returning 0 all the time, and print something to the
original stderr if something went wrong so that the user will know what
to do next.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   37 +++++++++++++++++++++++++++++++++++--
 1 file changed, 35 insertions(+), 2 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 52c24715fbc109..4d4eaedfc33e1f 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4072,6 +4072,7 @@ int main(int argc, char *argv[])
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
 	struct fuse2fs fctx;
 	errcode_t err;
+	FILE *orig_stderr = stderr;
 	char *logfile;
 	char extra_args[BUFSIZ];
 	int ret = 0;
@@ -4289,11 +4290,43 @@ int main(int argc, char *argv[])
 	}
 
 	pthread_mutex_init(&fctx.bfl, NULL);
-	fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+	ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
 	pthread_mutex_destroy(&fctx.bfl);
 
-	ret = 0;
+	switch(ret) {
+	case 0:
+		/* success */
+		ret = 0;
+		break;
+	case 1:
+	case 2:
+		/* invalid option or no mountpoint */
+		ret = 1;
+		break;
+	case 3:
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+		/* setup or mounting failed */
+		ret = 32;
+		break;
+	default:
+		/* fuse started up enough to call op_init */
+		ret = 0;
+		break;
+	}
 out:
+	if (ret & 1) {
+		fprintf(orig_stderr, "%s\n",
+ _("Mount failed due to unrecognized options.  Check dmesg(1) for details."));
+		fflush(orig_stderr);
+	}
+	if (ret & 32) {
+		fprintf(orig_stderr, "%s\n",
+ _("Mount failed while opening filesystem.  Check dmesg(1) for details."));
+		fflush(orig_stderr);
+	}
 	if (global_fs) {
 		err = ext2fs_close(global_fs);
 		if (err)


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 22/29] fuse2fs: fix fallocate zero range
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (20 preceding siblings ...)
  2025-05-21 22:40 ` [PATCH 21/29] fuse2fs: decode fuse_main error codes Darrick J. Wong
@ 2025-05-21 22:40 ` Darrick J. Wong
  2025-05-21 22:40 ` [PATCH 23/29] fuse2fs: check for supported xattr name prefixes Darrick J. Wong
                   ` (7 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Allow this flag when we're checking flags in op_fallocate.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 4d4eaedfc33e1f..74bbe661a417e5 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -3820,7 +3820,7 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 	int ret;
 
 	/* Catch unknown flags */
-	if (mode & ~(FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
+	if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
 		return -EOPNOTSUPP;
 
 	pthread_mutex_lock(&ff->bfl);


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 23/29] fuse2fs: check for supported xattr name prefixes
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (21 preceding siblings ...)
  2025-05-21 22:40 ` [PATCH 22/29] fuse2fs: fix fallocate zero range Darrick J. Wong
@ 2025-05-21 22:40 ` Darrick J. Wong
  2025-05-21 22:41 ` [PATCH 24/29] fuse2fs: fix return value handling Darrick J. Wong
                   ` (6 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

Ignore any xattr calls for name prefixes that the kernel doesn't also
support.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/ext2fsP.h |    3 +++
 misc/fuse2fs.c       |   37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)


diff --git a/lib/ext2fs/ext2fsP.h b/lib/ext2fs/ext2fsP.h
index d1f2105e9813ca..428081c9e2ff38 100644
--- a/lib/ext2fs/ext2fsP.h
+++ b/lib/ext2fs/ext2fsP.h
@@ -214,4 +214,7 @@ typedef void (*ext2_exit_fn)(void *);
 errcode_t ext2fs_add_exit_fn(ext2_exit_fn fn, void *data);
 errcode_t ext2fs_remove_exit_fn(ext2_exit_fn fn, void *data);
 
+#define ARRAY_SIZE(array)			\
+        (sizeof(array) / sizeof(array[0]))
+
 #define EXT2FS_BUILD_BUG_ON(cond) ((void)sizeof(char[1 - 2*!!(cond)]))
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 74bbe661a417e5..28b77d367cf705 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2443,6 +2443,27 @@ static int op_statfs(const char *path EXT2FS_ATTR((unused)),
 	return 0;
 }
 
+static const char *valid_xattr_prefixes[] = {
+	"user.",
+	"trusted.",
+	"security.",
+	"gnu.",
+	"system.",
+};
+
+static int validate_xattr_name(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(valid_xattr_prefixes); i++) {
+		if (!strncmp(name, valid_xattr_prefixes[i],
+					strlen(valid_xattr_prefixes[i])))
+			return 1;
+	}
+
+	return 0;
+}
+
 static int op_getxattr(const char *path, const char *key, char *value,
 		       size_t len)
 {
@@ -2456,6 +2477,9 @@ static int op_getxattr(const char *path, const char *key, char *value,
 	errcode_t err;
 	int ret = 0;
 
+	if (!validate_xattr_name(key))
+		return -ENODATA;
+
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
@@ -2626,6 +2650,9 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 	if (flags & ~(XATTR_CREATE | XATTR_REPLACE))
 		return -EOPNOTSUPP;
 
+	if (!validate_xattr_name(key))
+		return -EINVAL;
+
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
@@ -2714,6 +2741,16 @@ static int op_removexattr(const char *path, const char *key)
 	errcode_t err;
 	int ret = 0;
 
+	/*
+	 * Once in a while libfuse gives us a no-name xattr to delete as part
+	 * of clearing ACLs.  Just pretend we cleared them.
+	 */
+	if (key[0] == 0)
+		return 0;
+
+	if (!validate_xattr_name(key))
+		return -ENODATA;
+
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 24/29] fuse2fs: fix return value handling
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (22 preceding siblings ...)
  2025-05-21 22:40 ` [PATCH 23/29] fuse2fs: check for supported xattr name prefixes Darrick J. Wong
@ 2025-05-21 22:41 ` Darrick J. Wong
  2025-05-21 22:41 ` [PATCH 25/29] fuse2fs: fix removing ea inodes when freeing a file Darrick J. Wong
                   ` (5 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

For the xattr functions, don't obliterate the return value of the file
system operation with an error code coming from ext2fs_xattrs_close
failing.  Granted, it doesn't ever fail (right now!) so this is mostly
just preening.

Also fix the obsolete op_truncate not to squash error returns.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 28b77d367cf705..a630d93f5f48a8 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2529,7 +2529,7 @@ static int op_getxattr(const char *path, const char *key, char *value,
 	ext2fs_free_mem(&ptr);
 out2:
 	err = ext2fs_xattrs_close(&h);
-	if (err)
+	if (err && !ret)
 		ret = translate_error(fs, ino, err);
 out:
 	pthread_mutex_unlock(&ff->bfl);
@@ -2627,7 +2627,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
 	ret = bufsz;
 out2:
 	err = ext2fs_xattrs_close(&h);
-	if (err)
+	if (err && !ret)
 		ret = translate_error(fs, ino, err);
 out:
 	pthread_mutex_unlock(&ff->bfl);
@@ -2817,7 +2817,7 @@ static int op_removexattr(const char *path, const char *key)
 	ret = update_ctime(fs, ino, NULL);
 out2:
 	err = ext2fs_xattrs_close(&h);
-	if (err)
+	if (err && !ret)
 		ret = translate_error(fs, ino, err);
 out:
 	pthread_mutex_unlock(&ff->bfl);
@@ -3129,7 +3129,7 @@ static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 
 out:
 	pthread_mutex_unlock(&ff->bfl);
-	return 0;
+	return ret;
 }
 
 static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 25/29] fuse2fs: fix removing ea inodes when freeing a file
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (23 preceding siblings ...)
  2025-05-21 22:41 ` [PATCH 24/29] fuse2fs: fix return value handling Darrick J. Wong
@ 2025-05-21 22:41 ` Darrick J. Wong
  2025-05-21 22:41 ` [PATCH 26/29] fuse2fs: fix post-EOF preallocation clearing on truncation Darrick J. Wong
                   ` (4 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

If the filesystem has ea_inode set, then each file that has xattrs might
have stored an xattr value in a separate inode.  These inodes also need
to be freed, so create a library function to do that, and call it from
the fuse2fs unlink method.  Seen by ext4/026.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/ext2fs.h          |    1 +
 debian/libext2fs2t64.symbols |    1 +
 lib/ext2fs/ext_attr.c        |   19 +++++++++++++++++++
 misc/fuse2fs.c               |   40 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 61 insertions(+)


diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index 1527719554f001..2661e10f57c047 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -1406,6 +1406,7 @@ errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *handle,
 			   size_t value_len);
 errcode_t ext2fs_xattr_remove(struct ext2_xattr_handle *handle,
 			      const char *key);
+errcode_t ext2fs_xattr_remove_all(struct ext2_xattr_handle *handle);
 errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
 			     struct ext2_xattr_handle **handle);
 errcode_t ext2fs_xattrs_close(struct ext2_xattr_handle **handle);
diff --git a/debian/libext2fs2t64.symbols b/debian/libext2fs2t64.symbols
index 35af8880dd4843..fc1e16ff1e086c 100644
--- a/debian/libext2fs2t64.symbols
+++ b/debian/libext2fs2t64.symbols
@@ -670,6 +670,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_xattr_get@Base 1.43
  ext2fs_xattr_inode_max_size@Base 1.43
  ext2fs_xattr_remove@Base 1.43
+ ext2fs_xattr_remove_all@Base 1.47.3
  ext2fs_xattr_set@Base 1.43
  ext2fs_xattrs_close@Base 1.43
  ext2fs_xattrs_count@Base 1.43
diff --git a/lib/ext2fs/ext_attr.c b/lib/ext2fs/ext_attr.c
index 1b5f90d33f3cd4..7723d0f918d6a8 100644
--- a/lib/ext2fs/ext_attr.c
+++ b/lib/ext2fs/ext_attr.c
@@ -1355,6 +1355,7 @@ static errcode_t xattr_inode_dec_ref(ext2_filsys fs, ext2_ino_t ino)
 			goto out;
 	}
 
+	inode.i_flags &= ~EXT4_EA_INODE_FL;
 	ext2fs_inode_alloc_stats2(fs, ino, -1 /* inuse */, 0 /* is_dir */);
 
 write_out:
@@ -1725,6 +1726,24 @@ errcode_t ext2fs_xattr_remove(struct ext2_xattr_handle *handle,
 	return 0;
 }
 
+errcode_t ext2fs_xattr_remove_all(struct ext2_xattr_handle *handle)
+{
+	struct ext2_xattr *x;
+	struct ext2_xattr *end = handle->attrs + handle->count;
+
+	EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE);
+	for (x = handle->attrs; x < end; x++) {
+		ext2fs_free_mem(&x->name);
+		ext2fs_free_mem(&x->value);
+		if (x->ea_ino)
+			xattr_inode_dec_ref(handle->fs, x->ea_ino);
+	}
+
+	handle->ibody_count = 0;
+	handle->count = 0;
+	return ext2fs_xattrs_write(handle);
+}
+
 errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
 			     struct ext2_xattr_handle **handle)
 {
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index a630d93f5f48a8..46ae73bd4e25bb 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1178,6 +1178,40 @@ static int unlink_file_by_name(struct fuse2fs *ff, const char *path)
 	return update_mtime(fs, dir, NULL);
 }
 
+static errcode_t remove_ea_inodes(struct fuse2fs *ff, ext2_ino_t ino,
+				  struct ext2_inode_large *inode)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_xattr_handle *h;
+	errcode_t err;
+
+	/*
+	 * The xattr handle maintains its own private copy of the inode, so
+	 * write ours to disk so that we can read it.
+	 */
+	err = fuse2fs_write_inode(fs, ino, inode);
+	if (err)
+		return err;
+
+	err = ext2fs_xattrs_open(fs, ino, &h);
+	if (err)
+		return err;
+
+	err = ext2fs_xattrs_read(h);
+	if (err)
+		goto out_close;
+
+	err = ext2fs_xattr_remove_all(h);
+	if (err)
+		goto out_close;
+
+out_close:
+	ext2fs_xattrs_close(&h);
+
+	/* Now read the inode back in. */
+	return fuse2fs_read_inode(fs, ino, inode);
+}
+
 static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
 {
 	ext2_filsys fs = ff->fs;
@@ -1213,6 +1247,12 @@ static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
 	if (inode.i_links_count)
 		goto write_out;
 
+	if (ext2fs_has_feature_ea_inode(fs->super)) {
+		err = remove_ea_inodes(ff, ino, &inode);
+		if (err)
+			goto write_out;
+	}
+
 	/* Nobody holds this file; free its blocks! */
 	err = ext2fs_free_ext_attr(fs, ino, &inode);
 	if (err)


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 26/29] fuse2fs: fix post-EOF preallocation clearing on truncation
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (24 preceding siblings ...)
  2025-05-21 22:41 ` [PATCH 25/29] fuse2fs: fix removing ea inodes when freeing a file Darrick J. Wong
@ 2025-05-21 22:41 ` Darrick J. Wong
  2025-05-21 22:41 ` [PATCH 27/29] fuse2fs: also ignore the nodelalloc mount option Darrick J. Wong
                   ` (3 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

generic/092 shows that truncating a file to its current size does not
clean out post-eof preallocations like the kernel does.  Adopt the
kernel's behavior for consistency.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   52 ++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 48 insertions(+), 4 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 46ae73bd4e25bb..626e3a42181148 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2068,9 +2068,30 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 	return ret;
 }
 
-static int truncate_helper(ext2_filsys fs, ext2_ino_t ino, off_t new_size)
+static int punch_posteof(struct fuse2fs *ff, ext2_ino_t ino, off_t new_size)
 {
+	ext2_filsys fs = ff->fs;
+	struct ext2_inode_large inode;
+	blk64_t truncate_block = (new_size + fs->blocksize - 1) / fs->blocksize;
+	errcode_t err;
+
+	err = fuse2fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), 0, truncate_block,
+			   ~0ULL);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int truncate_helper(struct fuse2fs *ff, ext2_ino_t ino, off_t new_size)
+{
+	ext2_filsys fs = ff->fs;
 	ext2_file_t file;
+	__u64 old_isize;
 	errcode_t err;
 	int ret = 0;
 
@@ -2078,17 +2099,40 @@ static int truncate_helper(ext2_filsys fs, ext2_ino_t ino, off_t new_size)
 	if (err)
 		return translate_error(fs, ino, err);
 
+	err = ext2fs_file_get_lsize(file, &old_isize);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out_close;
+	}
+
+	dbg_printf(ff, "%s: ino=%u isize=0x%llx new_size=0x%llx\n", __func__,
+		   ino,
+		   (unsigned long long)old_isize,
+		   (unsigned long long)new_size);
+
 	err = ext2fs_file_set_size2(file, new_size);
 	if (err)
 		ret = translate_error(fs, ino, err);
 
+out_close:
 	err = ext2fs_file_close(file);
 	if (ret)
 		return ret;
 	if (err)
 		return translate_error(fs, ino, err);
 
-	return update_mtime(fs, ino, NULL);
+	ret = update_mtime(fs, ino, NULL);
+	if (ret)
+		return ret;
+
+	/*
+	 * Truncating to the current size is usually understood to mean that
+	 * we should clear out post-EOF preallocations.
+	 */
+	if (new_size == old_isize)
+		return punch_posteof(ff, ino, new_size);
+
+	return 0;
 }
 
 static int op_truncate(const char *path, off_t len
@@ -2122,7 +2166,7 @@ static int op_truncate(const char *path, off_t len
 	if (ret)
 		goto out;
 
-	ret = truncate_helper(fs, ino, len);
+	ret = truncate_helper(ff, ino, len);
 	if (ret)
 		goto out;
 
@@ -2222,7 +2266,7 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 	}
 
 	if (fp->flags & O_TRUNC) {
-		ret = truncate_helper(fs, file->ino, 0);
+		ret = truncate_helper(ff, file->ino, 0);
 		if (ret)
 			goto out;
 	}


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 27/29] fuse2fs: also ignore the nodelalloc mount option
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (25 preceding siblings ...)
  2025-05-21 22:41 ` [PATCH 26/29] fuse2fs: fix post-EOF preallocation clearing on truncation Darrick J. Wong
@ 2025-05-21 22:41 ` Darrick J. Wong
  2025-05-21 22:42 ` [PATCH 28/29] fuse2fs: propagate default ACLs to new children Darrick J. Wong
                   ` (2 subsequent siblings)
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

We don't support delalloc, so accept the nodelalloc option even if we do
nothing about it.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    1 +
 1 file changed, 1 insertion(+)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 626e3a42181148..71e81992cc1819 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4071,6 +4071,7 @@ static struct fuse_opt fuse2fs_opts[] = {
 
 	FUSE_OPT_KEY("user_xattr",	FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("noblock_validity", FUSE2FS_IGNORED),
+	FUSE_OPT_KEY("nodelalloc",	FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("cache_size=%s",	FUSE2FS_CACHE_SIZE),
 
 	FUSE_OPT_KEY("-V",             FUSE2FS_VERSION),


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 28/29] fuse2fs: propagate default ACLs to new children
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (26 preceding siblings ...)
  2025-05-21 22:41 ` [PATCH 27/29] fuse2fs: also ignore the nodelalloc mount option Darrick J. Wong
@ 2025-05-21 22:42 ` Darrick J. Wong
  2025-05-21 22:42 ` [PATCH 29/29] fuse2fs: fix group membership checking in op_chmod Darrick J. Wong
  2025-05-23 14:03 ` [PATCHSET 1/6] fuse2fs: even more bug fixes Theodore Ts'o
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:42 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

generic/319 points out that we don't propagate the default ACL from a
directory into new children.  Do that, since that's expected behavior.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |  124 +++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 103 insertions(+), 21 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 71e81992cc1819..a9f753c775db09 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -866,6 +866,96 @@ static int op_readlink(const char *path, char *buf, size_t len)
 	return ret;
 }
 
+static int __getxattr(struct fuse2fs *ff, ext2_ino_t ino, const char *name,
+		      void **value, size_t *value_len)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_xattr_handle *h;
+	errcode_t err;
+	int ret = 0;
+
+	err = ext2fs_xattrs_open(fs, ino, &h);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	err = ext2fs_xattrs_read(h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out_close;
+	}
+
+	err = ext2fs_xattr_get(h, name, value, value_len);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out_close;
+	}
+
+out_close:
+	err = ext2fs_xattrs_close(&h);
+	if (err && !ret)
+		ret = translate_error(fs, ino, err);
+	return ret;
+}
+
+static int __setxattr(struct fuse2fs *ff, ext2_ino_t ino, const char *name,
+		      void *value, size_t valuelen)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_xattr_handle *h;
+	errcode_t err;
+	int ret = 0;
+
+	err = ext2fs_xattrs_open(fs, ino, &h);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	err = ext2fs_xattrs_read(h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out_close;
+	}
+
+	err = ext2fs_xattr_set(h, name, value, valuelen);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out_close;
+	}
+
+out_close:
+	err = ext2fs_xattrs_close(&h);
+	if (err && !ret)
+		ret = translate_error(fs, ino, err);
+	return ret;
+}
+
+static int propagate_default_acls(struct fuse2fs *ff, ext2_ino_t parent,
+				  ext2_ino_t child)
+{
+	void *def;
+	size_t deflen;
+	int ret;
+
+	if (!ff->acl)
+		return 0;
+
+	ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
+			 &deflen);
+	switch (ret) {
+	case -ENODATA:
+	case -ENOENT:
+		/* no default acl */
+		return 0;
+	case 0:
+		break;
+	default:
+		return ret;
+	}
+
+	ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
+	ext2fs_free_mem(&def);
+	return ret;
+}
+
 static int op_mknod(const char *path, mode_t mode, dev_t dev)
 {
 	struct fuse_context *ctxt = fuse_get_context();
@@ -989,6 +1079,9 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 
 	ext2fs_inode_alloc_stats2(fs, child, 1, 0);
 
+	ret = propagate_default_acls(ff, parent, child);
+	if (ret)
+		goto out2;
 out2:
 	pthread_mutex_unlock(&ff->bfl);
 out:
@@ -1130,6 +1223,10 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out3;
 	}
 
+	ret = propagate_default_acls(ff, parent, child);
+	if (ret)
+		goto out3;
+
 out3:
 	ext2fs_free_mem(&block);
 out2:
@@ -2554,7 +2651,6 @@ static int op_getxattr(const char *path, const char *key, char *value,
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
 	ext2_filsys fs;
-	struct ext2_xattr_handle *h;
 	void *ptr;
 	size_t plen;
 	ext2_ino_t ino;
@@ -2583,23 +2679,9 @@ static int op_getxattr(const char *path, const char *key, char *value,
 	if (ret)
 		goto out;
 
-	err = ext2fs_xattrs_open(fs, ino, &h);
-	if (err) {
-		ret = translate_error(fs, ino, err);
+	ret = __getxattr(ff, ino, key, &ptr, &plen);
+	if (ret)
 		goto out;
-	}
-
-	err = ext2fs_xattrs_read(h);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out2;
-	}
-
-	err = ext2fs_xattr_get(h, key, &ptr, &plen);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out2;
-	}
 
 	if (!len) {
 		ret = plen;
@@ -2611,10 +2693,6 @@ static int op_getxattr(const char *path, const char *key, char *value,
 	}
 
 	ext2fs_free_mem(&ptr);
-out2:
-	err = ext2fs_xattrs_close(&h);
-	if (err && !ret)
-		ret = translate_error(fs, ino, err);
 out:
 	pthread_mutex_unlock(&ff->bfl);
 
@@ -3153,6 +3231,10 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 
 	ext2fs_inode_alloc_stats2(fs, child, 1, 0);
 
+	ret = propagate_default_acls(ff, parent, child);
+	if (ret)
+		goto out2;
+
 	ret = __op_open(ff, path, fp);
 	if (ret)
 		goto out2;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 29/29] fuse2fs: fix group membership checking in op_chmod
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (27 preceding siblings ...)
  2025-05-21 22:42 ` [PATCH 28/29] fuse2fs: propagate default ACLs to new children Darrick J. Wong
@ 2025-05-21 22:42 ` Darrick J. Wong
  2025-05-23 14:03 ` [PATCHSET 1/6] fuse2fs: even more bug fixes Theodore Ts'o
  29 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-21 22:42 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

From: Darrick J. Wong <djwong@kernel.org>

In the decade or so since I touched fuse2fs, libfuse3 has grown the
ability to read the group ids of a process making a chmod request.  So
now we actually /can/ determine if a file's gid is a in the group list
of the process that initiated a fuse request.  Let's implement that too.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |   78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 76 insertions(+), 2 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index a9f753c775db09..7ec9c6d861fd80 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2020,6 +2020,70 @@ static int op_link(const char *src, const char *dest)
 	return ret;
 }
 
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+/* Obtain group ids of the process that sent us a command(?) */
+static int get_req_groups(struct fuse2fs *ff, gid_t **gids, size_t *nr_gids)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	gid_t *array;
+	int nr = 32;	/* nobody has more than 32 groups right? */
+	int ret;
+
+	do {
+		err = ext2fs_get_array(nr, sizeof(gid_t), &array);
+		if (err)
+			return translate_error(fs, 0, err);
+
+		ret = fuse_getgroups(nr, array);
+		if (ret < 0)
+			return ret;
+
+		if (ret <= nr) {
+			*gids = array;
+			*nr_gids = ret;
+			return 0;
+		}
+
+		ext2fs_free_mem(&array);
+		nr = ret;
+	} while (0);
+
+	/* shut up gcc */
+	return -ENOMEM;
+}
+
+/*
+ * Is this file's group id in the set of groups associated with the process
+ * that initiated the fuse request?  Returns 1 for yes, 0 for no, or a negative
+ * errno.
+ */
+static int in_file_group(struct fuse_context *ctxt,
+			 const struct ext2_inode_large *inode)
+{
+	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	gid_t *gids = NULL;
+	size_t i, nr_gids = 0;
+	gid_t gid = inode_gid(*inode);
+	int ret;
+
+	ret = get_req_groups(ff, &gids, &nr_gids);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < nr_gids; i++)
+		if (gids[i] == gid)
+			return 1;
+	return 0;
+}
+#else
+static int in_file_group(struct fuse_context *ctxt,
+			 const struct ext2_inode_large *inode)
+{
+	return ctxt->gid == inode_gid(*inode);
+}
+#endif
+
 static int op_chmod(const char *path, mode_t mode
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, struct fuse_file_info *fi EXT2FS_ATTR((unused))
@@ -2066,11 +2130,21 @@ static int op_chmod(const char *path, mode_t mode
 	 * of the user's groups, but FUSE only tells us about the primary
 	 * group.
 	 */
-	if (!is_superuser(ff, ctxt) && ctxt->gid != inode_gid(inode))
-		mode &= ~S_ISGID;
+	if (!is_superuser(ff, ctxt)) {
+		ret = in_file_group(ctxt, &inode);
+		if (ret < 0)
+			goto out;
+
+		if (!ret)
+			mode &= ~S_ISGID;
+	}
 
 	inode.i_mode &= ~0xFFF;
 	inode.i_mode |= mode & 0xFFF;
+
+	dbg_printf(ff, "%s: path=%s new_mode=0%o ino=%d\n", __func__,
+		   path, inode.i_mode, ino);
+
 	ret = update_ctime(fs, ino, &inode);
 	if (ret)
 		goto out;


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [PATCHSET 1/6] fuse2fs: even more bug fixes
  2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
                   ` (28 preceding siblings ...)
  2025-05-21 22:42 ` [PATCH 29/29] fuse2fs: fix group membership checking in op_chmod Darrick J. Wong
@ 2025-05-23 14:03 ` Theodore Ts'o
  2025-05-29  1:37   ` Darrick J. Wong
  29 siblings, 1 reply; 32+ messages in thread
From: Theodore Ts'o @ 2025-05-23 14:03 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: linux-ext4

On Wed, May 21, 2025 at 03:34:36PM -0700, Darrick J. Wong wrote:
> Hi all,
> 
> This series fixes even more bugs in fuse2fs.

Thanks, applied.  I found that it didn't compile, because it used
fuse2fs_{read,write}_inode() which doesn't get defined until PATCHSET
2/6.  So I've also applied the second patchset, and then split the
patch "fuse2fs: simplify reading and writing inodes" into two commits,
and applied the first half before "fuse2fs: fix removing ea inodes when
freeing a file" to keep the fuse2fs changes bisectable.

Also, I cleaned up some patches to keep fuse2fs portable enough to
work with MacOS.  I've lightly tested fuse2fs with MacFuse (installed
via MacPorts) on macOS Sequoia and it works without having to pay $$$
to Apple.  You do need to enable the MacFuse system extension (which
is signed by Benjamin Fleischer, so I guess Fleischer is the one who
paid $3000 to Apple) and reboot, but it does work.

I'll reach out to someone in FreeBSD land to test whether fuse2fs
works there, since I'd like to maintain cross-OS portability as much
as possible.  It does mean that I'm a bit concerned about the fuseblk
patchset because I'm pretty sure that's Linux-specific, correct?

Cheers,

						- Ted

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCHSET 1/6] fuse2fs: even more bug fixes
  2025-05-23 14:03 ` [PATCHSET 1/6] fuse2fs: even more bug fixes Theodore Ts'o
@ 2025-05-29  1:37   ` Darrick J. Wong
  0 siblings, 0 replies; 32+ messages in thread
From: Darrick J. Wong @ 2025-05-29  1:37 UTC (permalink / raw)
  To: Theodore Ts'o; +Cc: linux-ext4

On Fri, May 23, 2025 at 10:03:44AM -0400, Theodore Ts'o wrote:
> On Wed, May 21, 2025 at 03:34:36PM -0700, Darrick J. Wong wrote:
> > Hi all,
> > 
> > This series fixes even more bugs in fuse2fs.
> 
> Thanks, applied.  I found that it didn't compile, because it used
> fuse2fs_{read,write}_inode() which doesn't get defined until PATCHSET
> 2/6.  So I've also applied the second patchset, and then split the
> patch "fuse2fs: simplify reading and writing inodes" into two commits,
> and applied the first half before "fuse2fs: fix removing ea inodes when
> freeing a file" to keep the fuse2fs changes bisectable.
> 
> Also, I cleaned up some patches to keep fuse2fs portable enough to
> work with MacOS.  I've lightly tested fuse2fs with MacFuse (installed
> via MacPorts) on macOS Sequoia and it works without having to pay $$$
> to Apple.  You do need to enable the MacFuse system extension (which
> is signed by Benjamin Fleischer, so I guess Fleischer is the one who
> paid $3000 to Apple) and reboot, but it does work.
> 
> I'll reach out to someone in FreeBSD land to test whether fuse2fs
> works there, since I'd like to maintain cross-OS portability as much
> as possible.

Hrmm.  A question I've been pondering for a couple of weeks is whether
or not fuse2fs-iomap should actually care about cross-OS portability.
The further I wander into the two layers of libfuse, the more I realize
that the low level fuse library is for fuse servers that want to tie
themselves to Linux (e.g. you can implement statx and copy_file_range)
and the high level library, which seems to be maintaining compatibility
with the osx/bsd variants.

At this point I don't know if one can actually /create/ a hybrid fuse
server that talks to both layers (layering violation!!) since the upper
level hides the guts of the low level library.  If you've looked at the
fuse2fs iomap code, you'll notice that I've passed the Linux fuse
driver's nodeid (aka the iget number) all the way up to fuse2fs along
with the ext2 ino number which is basically a u64 cookie that gets
passed along with the nodeid.

If the Linux fuse driver maintainers accept the fuse-iomap code then
maybe it would make sense to fork fuse2fs: one for general compatibility
across filesystems, and second one optimized for Linux.

> It does mean that I'm a bit concerned about the fuseblk
> patchset because I'm pretty sure that's Linux-specific, correct?

That's a good question.  libfuse says that fuse_operations::bmap only
makes sense for filesystems mounted with the "blkdev" option (which is
what turns on fuseblk mode) and doesn't #ifdef it away on any platforms.
libfuse itself seems to be built on freebsd, but then ...

Looking through what I think is the mount option parsing code in
fuse_vfsop_mount, I don't think they support a blkdev= option?
https://raw.githubusercontent.com/freebsd/freebsd-src/refs/heads/main/sys/fs/fuse/fuse_vfsops.c

So maybe it's not supported on !linux.  Unfortunately, it's not easy to
tell from the one other fuseblk server I know of (ntfs-3g) if they even
try to use fuseblk mode on freebsd because of course they vendored
libfuse into their own source code(!)

Will have a closer look tomorrow.  At worst we can always wrap it in
more #ifdef __LINUX__ magic.

--D

> Cheers,
> 
> 						- Ted
> 

^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2025-05-29  1:37 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-21 22:34 [PATCHSET 1/6] fuse2fs: even more bug fixes Darrick J. Wong
2025-05-21 22:35 ` [PATCH 01/29] libext2fs: fix unix io manager invalidation Darrick J. Wong
2025-05-21 22:35 ` [PATCH 02/29] libext2fs: fix livelock in the unix io manager Darrick J. Wong
2025-05-21 22:35 ` [PATCH 03/29] fuse2fs: clean up error messages Darrick J. Wong
2025-05-21 22:35 ` [PATCH 04/29] fuse2fs: fix cache size parsing Darrick J. Wong
2025-05-21 22:36 ` [PATCH 05/29] fuse2fs: compact all the boolean flags in struct fuse2fs Darrick J. Wong
2025-05-21 22:36 ` [PATCH 06/29] fuse2fs: support XATTR_CREATE/REPLACE in setxattr Darrick J. Wong
2025-05-21 22:36 ` [PATCH 07/29] fuse2fs: fix error return handling in op_truncate Darrick J. Wong
2025-05-21 22:37 ` [PATCH 08/29] fuse2fs: flip parameter order in __translate_error Darrick J. Wong
2025-05-21 22:37 ` [PATCH 09/29] fuse2fs: fix CLI argument parsing leaks Darrick J. Wong
2025-05-21 22:37 ` [PATCH 10/29] fuse2fs: allow some control over acls Darrick J. Wong
2025-05-21 22:37 ` [PATCH 11/29] fuse2fs: enable processing of acls in the kernel Darrick J. Wong
2025-05-21 22:38 ` [PATCH 12/29] fuse2fs: make removexattr work correctly Darrick J. Wong
2025-05-21 22:38 ` [PATCH 13/29] fuse2fs: implement O_TRUNC correctly Darrick J. Wong
2025-05-21 22:38 ` [PATCH 14/29] fuse2fs: rearrange check_inum_access parameters a bit Darrick J. Wong
2025-05-21 22:38 ` [PATCH 15/29] fuse2fs: make filesystem corruption a hard error Darrick J. Wong
2025-05-21 22:39 ` [PATCH 16/29] fuse2fs: make internal state " Darrick J. Wong
2025-05-21 22:39 ` [PATCH 17/29] fuse2fs: make bad magic numbers report a corruption error too Darrick J. Wong
2025-05-21 22:39 ` [PATCH 18/29] fuse2fs: return EPERM for write access to EXT2_IMMUTABLE_FL files Darrick J. Wong
2025-05-21 22:39 ` [PATCH 19/29] fuse2fs: check the immutable flag in more places Darrick J. Wong
2025-05-21 22:40 ` [PATCH 20/29] fuse2fs: implement O_APPEND correctly Darrick J. Wong
2025-05-21 22:40 ` [PATCH 21/29] fuse2fs: decode fuse_main error codes Darrick J. Wong
2025-05-21 22:40 ` [PATCH 22/29] fuse2fs: fix fallocate zero range Darrick J. Wong
2025-05-21 22:40 ` [PATCH 23/29] fuse2fs: check for supported xattr name prefixes Darrick J. Wong
2025-05-21 22:41 ` [PATCH 24/29] fuse2fs: fix return value handling Darrick J. Wong
2025-05-21 22:41 ` [PATCH 25/29] fuse2fs: fix removing ea inodes when freeing a file Darrick J. Wong
2025-05-21 22:41 ` [PATCH 26/29] fuse2fs: fix post-EOF preallocation clearing on truncation Darrick J. Wong
2025-05-21 22:41 ` [PATCH 27/29] fuse2fs: also ignore the nodelalloc mount option Darrick J. Wong
2025-05-21 22:42 ` [PATCH 28/29] fuse2fs: propagate default ACLs to new children Darrick J. Wong
2025-05-21 22:42 ` [PATCH 29/29] fuse2fs: fix group membership checking in op_chmod Darrick J. Wong
2025-05-23 14:03 ` [PATCHSET 1/6] fuse2fs: even more bug fixes Theodore Ts'o
2025-05-29  1:37   ` Darrick J. Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).