linux-ext4.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHBOMB 1.48] fuse2fs: new features, new server
@ 2025-11-06 22:14 Darrick J. Wong
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
                   ` (8 more replies)
  0 siblings, 9 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:14 UTC (permalink / raw)
  To: Theodore Ts'o; +Cc: linux-ext4

Hi Ted,

As promised, this large patchbomb contains all the improvements that I'd
like to make to fuse2fs before integrating iomap.  Major new features
include:

 - Fix locking of the block device (or image file) to prevent multiple
   fuse2fs instances.
 - Initializing the htree index in a growing directory so that directory
   lookups aren't horrifyingly slow.
 - Implementing MMP for cluster support.
 - Use file handles to reduce directory path lookups.
 - Implement directory seeking, readdir plus (the fuse version), and
   dirsync.
 - Fix differences in nlink overflow handling vs the kernel.
 - Cache symlinks when possible.
 - Refactor startup and shutdown to reduce main() complexity.
 - Add tracing for file operations.
 - Add the shutdown ioctl for better recovery testing.
 - Drop fuse 2.xx support.

At the end of this series, I create a new fuse ext* server (fuse4fs)
which uses the lowlevel FUSE API instead of the high level one.  The
major advantage of using the lowlevel API is that all file operations
are performed in terms of inodes instead of paths.  As a result, fuse4fs
has MUCH less overhead than fuse2fs because we avoid the overhead of
having libfuse translate inode numbers to paths only to have fuse2fs
translate paths back into inode numbers.

Obviously, this stuff should go into e2fsprogs 1.48, not a 1.47.x stable
release.

--D

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

* [PATCHSET 1/9] fuse2fs: fix locking problems
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
@ 2025-11-06 22:27 ` Darrick J. Wong
  2025-11-06 22:30   ` [PATCH 1/4] libext2fs: add POSIX advisory locking to the unix IO manager Darrick J. Wong
                     ` (3 more replies)
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                   ` (7 subsequent siblings)
  8 siblings, 4 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:27 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

Hi all,

Teach fuse2fs to flock the file(s) underlying the filesystem if the fs
is stored in a regular file, so that we don't have to create and
maintain separate lockfiles.

For block devices, fix a weird race between mount and unmount that is
causing testing failures by waiting a small amount of time to grab a
lock.

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-locking
---
Commits in this patchset:
 * libext2fs: add POSIX advisory locking to the unix IO manager
 * fuse2fs: try to lock filesystem image files before using them
 * fuse2fs: quiet down write-protect warning
 * fuse2fs: try to grab block device O_EXCL repeatedly
---
 lib/ext2fs/ext2_io.h         |   12 +++-
 debian/libext2fs2t64.symbols |    2 +
 lib/ext2fs/io_manager.c      |   16 +++++
 lib/ext2fs/unix_io.c         |   71 +++++++++++++++++++++
 misc/fuse2fs.c               |  144 +++++++++++++++++++++++++++++++++++++++---
 5 files changed, 232 insertions(+), 13 deletions(-)


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

* [PATCHSET 2/9] fuse2fs: add some easy new features
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
@ 2025-11-06 22:28 ` Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 01/19] libext2fs: initialize htree when expanding directory Darrick J. Wong
                     ` (18 more replies)
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                   ` (6 subsequent siblings)
  8 siblings, 19 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:28 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

Hi all,

As of 2025, libfuse is a lot more capable than it was in 2013.
Implement some new features such as readdirplus and directory seeking
for better directory performance, and reduce the amount of filesystem
flushing so that it only happens when userspace explicitly asks for it.
Now we also can add htree indices to large directories, support MMP in
fuse2fs, and link count overflows are handled correctly.

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-new-features
---
Commits in this patchset:
 * libext2fs: initialize htree when expanding directory
 * libext2fs: create link count adjustment helpers for dir_nlink
 * libext2fs: fix ext2fs_mmp_update
 * libext2fs: refactor aligned MMP buffer allocation
 * libext2fs: always use ext2fs_mmp_get_mem to allocate fs->mmp_buf
 * fuse2fs: check root directory while mounting
 * fuse2fs: read bitmaps asynchronously during initialization
 * fuse2fs: use file handles when possible
 * fuse2fs: implement dir seeking
 * fuse2fs: implement readdirplus
 * fuse2fs: implement dirsync mode
 * fuse2fs: only flush O_SYNC files on close
 * fuse2fs: improve want_extra_isize handling
 * fuse2fs: cache symlink targets in the kernel
 * fuse2fs: constrain worker thread count
 * fuse2fs: improve error handling behaviors
 * fuse2fs: fix link count overflows on dir_nlink filesystems
 * libsupport: add background thread manager
 * fuse2fs: implement MMP updates
---
 lib/ext2fs/ext2fs.h          |    6 
 lib/support/bthread.h        |   27 ++
 debian/libext2fs2t64.symbols |    6 
 e2fsck/unix.c                |    2 
 lib/ext2fs/dupfs.c           |    7 
 lib/ext2fs/link.c            |  290 ++++++++++++++++++++
 lib/ext2fs/mkdir.c           |    2 
 lib/ext2fs/mmp.c             |   24 +-
 lib/support/Makefile.in      |    6 
 lib/support/bthread.c        |  201 ++++++++++++++
 misc/dumpe2fs.c              |    2 
 misc/fuse2fs.1.in            |    9 +
 misc/fuse2fs.c               |  617 ++++++++++++++++++++++++++++++++++++------
 13 files changed, 1094 insertions(+), 105 deletions(-)
 create mode 100644 lib/support/bthread.h
 create mode 100644 lib/support/bthread.c


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

* [PATCHSET 3/9] fuse2fs: clean up operation startup
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
@ 2025-11-06 22:28 ` Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 1/9] fuse2fs: rework FUSE2FS_CHECK_CONTEXT not to rely on global_fs Darrick J. Wong
                     ` (8 more replies)
  2025-11-06 22:28 ` [PATCHSET 4/9] fuse2fs: refactor unmount code Darrick J. Wong
                   ` (5 subsequent siblings)
  8 siblings, 9 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:28 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

Hi all,

Reduce the amount of boilerplate in fuse2fs by creating helper functions
to start and finish a file operation instead of open-coding the logic
all over the place.  This also fixes a couple of theoretical races.

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-refactor-operation-startup
---
Commits in this patchset:
 * fuse2fs: rework FUSE2FS_CHECK_CONTEXT not to rely on global_fs
 * fuse2fs: rework checking file handles
 * fuse2fs: rework fallocate file handle extraction
 * fuse2fs: consolidate file handle checking in op_ioctl
 * fuse2fs: move fs assignment closer to locking the bfl
 * fuse2fs: clean up operation startup
 * fuse2fs: clean up operation completion
 * fuse2fs: clean up more boilerplate
 * fuse2fs: collect runtime of various operations
---
 configure       |   37 ++++
 configure.ac    |   19 ++
 lib/config.h.in |    3 
 misc/fuse2fs.c  |  475 ++++++++++++++++++++++++++++---------------------------
 4 files changed, 303 insertions(+), 231 deletions(-)


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

* [PATCHSET 4/9] fuse2fs: refactor unmount code
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
                   ` (2 preceding siblings ...)
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
@ 2025-11-06 22:28 ` Darrick J. Wong
  2025-11-06 22:38   ` [PATCH 1/3] fuse2fs: get rid of the global_fs variable Darrick J. Wong
                     ` (2 more replies)
  2025-11-06 22:28 ` [PATCHSET 5/9] fuse2fs: refactor mount code Darrick J. Wong
                   ` (4 subsequent siblings)
  8 siblings, 3 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:28 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

Hi all,

In this series, we refactor the code around ext2fs_close to get ready
for iomap mode.  The significant part of this series is moving the
unmount code to op_destroy, because we want to release the block device
as a part of the umount(2) process to maintain expected behavior of the
in-kernel local filesystem drivers.

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-refactor-unmounting
---
Commits in this patchset:
 * fuse2fs: get rid of the global_fs variable
 * fuse2fs: hoist lockfile code
 * fuse2fs: hoist unmount code from main
---
 misc/fuse2fs.c |  195 ++++++++++++++++++++++++++++++--------------------------
 1 file changed, 106 insertions(+), 89 deletions(-)


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

* [PATCHSET 5/9] fuse2fs: refactor mount code
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
                   ` (3 preceding siblings ...)
  2025-11-06 22:28 ` [PATCHSET 4/9] fuse2fs: refactor unmount code Darrick J. Wong
@ 2025-11-06 22:28 ` Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 1/3] fuse2fs: split filesystem mounting into helper functions Darrick J. Wong
                     ` (2 more replies)
  2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
                   ` (3 subsequent siblings)
  8 siblings, 3 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:28 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

Hi all,

Here, we hoist the mounting code out of main() into a pile of separate
helper functions to reduce the complexity of understanding the mount
code.  This isn't strictly required for iomap, but it makes main() a lot
easier to understand.

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-refactor-mounting
---
Commits in this patchset:
 * fuse2fs: split filesystem mounting into helper functions
 * fuse2fs: register as an IO flusher thread
 * fuse2fs: adjust OOM killer score if possible
---
 configure       |   37 +++
 configure.ac    |   19 ++
 lib/config.h.in |    3 
 misc/fuse2fs.c  |  634 +++++++++++++++++++++++++++++++------------------------
 4 files changed, 413 insertions(+), 280 deletions(-)


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

* [PATCHSET 6/9] fuse2fs: improve operation tracing
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
                   ` (4 preceding siblings ...)
  2025-11-06 22:28 ` [PATCHSET 5/9] fuse2fs: refactor mount code Darrick J. Wong
@ 2025-11-06 22:29 ` Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 1/4] fuse2fs: hook library error message printing Darrick J. Wong
                     ` (3 more replies)
  2025-11-06 22:29 ` [PATCHSET 7/9] fuse2fs: better tracking of writable state Darrick J. Wong
                   ` (2 subsequent siblings)
  8 siblings, 4 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:29 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

Hi all,

This series improves the ability for developers to trace the activities
of fuse2fs by adding more debugging printfs and tracing abilities of
fuse2fs.  It also registers a com_err handler for libext2fs so we can
capture errors coming out of there, and changes filesystem error
reporting to tell us the function name instead of just fuse2fs.c.

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-tracing
---
Commits in this patchset:
 * fuse2fs: hook library error message printing
 * fuse2fs: print the function name in error messages, not the file name
 * fuse2fs: improve tracing for file range operations
 * fuse2fs: record thread id in debug trace data
---
 misc/fuse2fs.c |   93 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 69 insertions(+), 24 deletions(-)


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

* [PATCHSET 7/9] fuse2fs: better tracking of writable state
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
                   ` (5 preceding siblings ...)
  2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
@ 2025-11-06 22:29 ` Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 1/3] fuse2fs: pass a struct fuse2fs to fs_writeable Darrick J. Wong
                     ` (2 more replies)
  2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
  8 siblings, 3 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:29 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

Hi all,

There are multiple mutability variables in play in fuse2fs -- first,
EXT2_FLAG_RW tracks whether or not we can write anything to the
filesystem.  However, there's a second state, which is whether or not
we actually want to write to the filesystem, regardless of the library
state.  This can happen if we open libext2fs for writing, but then
discover something about the filesystem that makes us not want to write
to it after all.

Split out this second variable into an explicit variable 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-writability
---
Commits in this patchset:
 * fuse2fs: pass a struct fuse2fs to fs_writeable
 * fuse2fs: track our own writable state
 * fuse2fs: enable the shutdown ioctl
---
 misc/fuse2fs.c |  102 ++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 73 insertions(+), 29 deletions(-)


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

* [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
                   ` (6 preceding siblings ...)
  2025-11-06 22:29 ` [PATCHSET 7/9] fuse2fs: better tracking of writable state Darrick J. Wong
@ 2025-11-06 22:29 ` Darrick J. Wong
  2025-11-06 22:42   ` [PATCH 1/4] fuse2fs: bump library version Darrick J. Wong
                     ` (3 more replies)
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
  8 siblings, 4 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:29 UTC (permalink / raw)
  To: tytso; +Cc: amir73il, linux-ext4

Hi all,

In preparation to start hacking on fuse2fs and iomap, upgrade fuse2fs
library support to 3.17, which is the latest upstream release as of this
writing.  Drop support for libfuse2, which is now very obsolete.

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-library-upgrade
---
Commits in this patchset:
 * fuse2fs: bump library version
 * fuse2fs: wrap the fuse_set_feature_flag helper for older libfuse
 * fuse2fs: disable nfs exports
 * fuse2fs: drop fuse 2.x support code
---
 configure      |  318 +++++---------------------------------------------------
 configure.ac   |   85 +++++----------
 misc/fuse2fs.c |  252 ++++++++++----------------------------------
 3 files changed, 115 insertions(+), 540 deletions(-)


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

* [PATCHSET 9/9] fuse4fs: fork a low level fuse server
  2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
                   ` (7 preceding siblings ...)
  2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
@ 2025-11-06 22:30 ` Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure Darrick J. Wong
                     ` (22 more replies)
  8 siblings, 23 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:30 UTC (permalink / raw)
  To: tytso; +Cc: amir73il, linux-ext4

Hi all,

Whilst developing the fuse2fs+iomap prototype, I discovered a
fundamental design limitation of the upper-level libfuse API: hardlinks.
The upper level fuse library really wants to communicate with the fuse
server with file paths, instead of using inode numbers.  This works
great for filesystems that don't have inodes, create files dynamically
at runtime, or lack stable inode numbers.

Unfortunately, the libfuse path abstraction assigns a unique nodeid to
every child file in the entire filesystem, without regard to hard links.
In other words, a hardlinked regular file may have one ondisk inode
number but multiple kernel inodes.  For classic fuse2fs this isn't a
problem because all file access goes through the fuse server and the big
library lock protects us from corruption.

For fuse2fs + iomap this is a disaster because we rely on the kernel to
coordinate access to inodes.  For hardlinked files, we *require* that
there only be one in-kernel inode for each ondisk inode.

The path based mechanism is also very inefficient for fuse2fs.  Every
time a file is accessed, the upper level libfuse passes a new nodeid to
the kernel, and on every file access the kernel passes that same nodeid
back to libfuse.  libfuse then walks its internal directory entry cache
to construct a path string for that nodeid and hands it to fuse2fs.
fuse2fs then walks the ondisk directory structure to find the ext2 inode
number.  Every time.

Create a new fuse4fs server from fuse2fs that uses the lowlevel fuse
API.  This affords us direct control over nodeids and eliminates the
path wrangling.  Hardlinks can be supported when iomap is turned on,
and metadata-heavy workloads run twice as fast.

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=fuse4fs-fork
---
Commits in this patchset:
 * fuse2fs: separate libfuse3 and fuse2fs detection in configure
 * fuse2fs: start porting fuse2fs to lowlevel libfuse API
 * debian: create new package for fuse4fs
 * fuse4fs: namespace some helpers
 * fuse4fs: convert to low level API
 * libsupport: port the kernel list.h to libsupport
 * libsupport: add a cache
 * cache: disable debugging
 * cache: use modern list iterator macros
 * cache: embed struct cache in the owner
 * cache: pass cache pointer to callbacks
 * cache: pass a private data pointer through cache_walk
 * cache: add a helper to grab a new refcount for a cache_node
 * cache: return results of a cache flush
 * cache: add a "get only if incore" flag to cache_node_get
 * cache: support gradual expansion
 * cache: support updating maxcount and flags
 * cache: support channging flags
 * cache: implement automatic shrinking
 * fuse4fs: add cache to track open files
 * fuse4fs: use the orphaned inode list
 * fuse4fs: implement FUSE_TMPFILE
 * fuse4fs: create incore reverse orphan list
---
 lib/ext2fs/jfs_compat.h  |    2 
 lib/ext2fs/kernel-list.h |  111 -
 lib/support/cache.h      |  184 +
 lib/support/list.h       |  901 ++++++
 lib/support/xbitops.h    |  128 +
 Makefile.in              |    3 
 configure                |  414 +--
 configure.ac             |  156 +
 debian/control           |   12 
 debian/fuse4fs.install   |    2 
 debian/fuse4fs.links     |    3 
 debian/rules             |   11 
 debugfs/Makefile.in      |   12 
 e2fsck/Makefile.in       |   56 
 fuse4fs/Makefile.in      |  193 +
 fuse4fs/fuse4fs.1.in     |  118 +
 fuse4fs/fuse4fs.c        | 6430 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/config.h.in          |    3 
 lib/e2p/Makefile.in      |    4 
 lib/ext2fs/Makefile.in   |   14 
 lib/support/Makefile.in  |    8 
 lib/support/cache.c      |  882 ++++++
 misc/Makefile.in         |   18 
 misc/tune2fs.c           |    4 
 24 files changed, 9222 insertions(+), 447 deletions(-)
 delete mode 100644 lib/ext2fs/kernel-list.h
 create mode 100644 lib/support/cache.h
 create mode 100644 lib/support/list.h
 create mode 100644 lib/support/xbitops.h
 create mode 100644 debian/fuse4fs.install
 create mode 100644 debian/fuse4fs.links
 create mode 100644 fuse4fs/Makefile.in
 create mode 100644 fuse4fs/fuse4fs.1.in
 create mode 100644 fuse4fs/fuse4fs.c
 create mode 100644 lib/support/cache.c


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

* [PATCH 1/4] libext2fs: add POSIX advisory locking to the unix IO manager
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
@ 2025-11-06 22:30   ` Darrick J. Wong
  2025-11-06 22:30   ` [PATCH 2/4] fuse2fs: try to lock filesystem image files before using them Darrick J. Wong
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:30 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Add support for using flock() to protect the files opened by the Unix IO
manager so that other fuse2fs servers cannot stomp all over the
filesystem.  We may some day want to adopt this in e2fsck.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/ext2_io.h         |   12 +++++++
 debian/libext2fs2t64.symbols |    2 +
 lib/ext2fs/io_manager.c      |   16 +++++++++
 lib/ext2fs/unix_io.c         |   71 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 100 insertions(+), 1 deletion(-)


diff --git a/lib/ext2fs/ext2_io.h b/lib/ext2fs/ext2_io.h
index 39a4e8fcf6b515..61865d54d82490 100644
--- a/lib/ext2fs/ext2_io.h
+++ b/lib/ext2fs/ext2_io.h
@@ -102,7 +102,8 @@ struct struct_io_manager {
 				     unsigned long long count);
 	errcode_t (*zeroout)(io_channel channel, unsigned long long block,
 			     unsigned long long count);
-	long	reserved[14];
+	errcode_t (*flock)(io_channel channel, unsigned int flock_flags);
+	long	reserved[13];
 };
 
 #define IO_FLAG_RW		0x0001
@@ -112,6 +113,13 @@ struct struct_io_manager {
 #define IO_FLAG_THREADS		0x0010
 #define IO_FLAG_NOCACHE		0x0020
 
+/* Prevent other programs from reading or writing to underlying storage */
+#define IO_CHANNEL_FLOCK_EXCLUSIVE	0x1
+/* Prevent other programs from writing to underlying storage */
+#define IO_CHANNEL_FLOCK_SHARED		0x2
+/* Return EBUSY if the lock cannot be taken immediately */
+#define IO_CHANNEL_FLOCK_TRYLOCK	0x4
+
 /*
  * Convenience functions....
  */
@@ -145,6 +153,8 @@ extern errcode_t io_channel_alloc_buf(io_channel channel,
 extern errcode_t io_channel_cache_readahead(io_channel io,
 					    unsigned long long block,
 					    unsigned long long count);
+extern errcode_t io_channel_flock(io_channel io, unsigned int flock_flags);
+extern errcode_t io_channel_funlock(io_channel io);
 
 #ifdef _WIN32
 /* windows_io.c */
diff --git a/debian/libext2fs2t64.symbols b/debian/libext2fs2t64.symbols
index a3042c3292da93..b4d80161f1e1b4 100644
--- a/debian/libext2fs2t64.symbols
+++ b/debian/libext2fs2t64.symbols
@@ -693,6 +693,8 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  io_channel_alloc_buf@Base 1.42.3
  io_channel_cache_readahead@Base 1.43
  io_channel_discard@Base 1.42
+ io_channel_flock@Base 1.47.99
+ io_channel_funlock@Base 1.47.99
  io_channel_read_blk64@Base 1.41.1
  io_channel_set_options@Base 1.37
  io_channel_write_blk64@Base 1.41.1
diff --git a/lib/ext2fs/io_manager.c b/lib/ext2fs/io_manager.c
index dca6af09996b70..791ec7d14adbba 100644
--- a/lib/ext2fs/io_manager.c
+++ b/lib/ext2fs/io_manager.c
@@ -150,3 +150,19 @@ errcode_t io_channel_cache_readahead(io_channel io, unsigned long long block,
 
 	return io->manager->cache_readahead(io, block, count);
 }
+
+errcode_t io_channel_flock(io_channel io, unsigned int flock_flags)
+{
+	if (!io->manager->flock)
+		return EXT2_ET_OP_NOT_SUPPORTED;
+
+	return io->manager->flock(io, flock_flags);
+}
+
+errcode_t io_channel_funlock(io_channel io)
+{
+	if (!io->manager->flock)
+		return EXT2_ET_OP_NOT_SUPPORTED;
+
+	return io->manager->flock(io, 0);
+}
diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index 1456b4d4bbe212..abd33ba839f7e9 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -64,6 +64,9 @@
 #ifdef HAVE_PTHREAD
 #include <pthread.h>
 #endif
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
 
 #if defined(__linux__) && defined(_IO) && !defined(BLKROGET)
 #define BLKROGET   _IO(0x12, 94) /* Get read-only status (0 = read_write).  */
@@ -135,6 +138,7 @@ struct unix_private_data {
 	int	flags;
 	int	align;
 	int	access_time;
+	int	unix_flock_flags;
 	ext2_loff_t offset;
 	struct unix_cache *cache;
 	unsigned int cache_size;
@@ -875,6 +879,68 @@ int ext2fs_fstat(int fd, ext2fs_struct_stat *buf)
 #endif
 }
 
+#ifdef HAVE_SYS_FILE_H
+static errcode_t unix_funlock(io_channel channel)
+{
+	struct unix_private_data *data;
+	int ret;
+
+	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+	data = (struct unix_private_data *) channel->private_data;
+	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+
+	if (data->unix_flock_flags) {
+		ret = flock(data->dev, LOCK_UN);
+		if (ret)
+			return errno;
+
+		data->unix_flock_flags = 0;
+	}
+
+	return 0;
+}
+
+static errcode_t unix_flock(io_channel channel, unsigned int flock_flags)
+{
+	struct unix_private_data *data;
+	int unix_flock_flags = 0;
+	errcode_t ret;
+
+	EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+	data = (struct unix_private_data *) channel->private_data;
+	EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+
+	ret = unix_funlock(channel);
+	if (ret)
+		return ret;
+
+	if (flock_flags & IO_CHANNEL_FLOCK_EXCLUSIVE)
+		unix_flock_flags |= LOCK_EX;
+
+	if (flock_flags & IO_CHANNEL_FLOCK_SHARED)
+		unix_flock_flags |= LOCK_SH;
+
+	if (flock_flags & IO_CHANNEL_FLOCK_TRYLOCK)
+		unix_flock_flags |= LOCK_NB;
+
+	if (!unix_flock_flags)
+		return 0;
+
+	ret = flock(data->dev, unix_flock_flags);
+	if (ret < 0)
+		return errno;
+
+	data->unix_flock_flags = unix_flock_flags & ~LOCK_NB;
+	return 0;
+}
+#else
+#define unix_flock		NULL
+
+static errcode_t unix_funlock(io_channel channel)
+{
+	return 0;
+}
+#endif /* HAVE_SYS_FILE_H */
 
 static errcode_t unix_open_channel(const char *name, int fd,
 				   int flags, io_channel *channel,
@@ -1061,6 +1127,7 @@ static errcode_t unix_open_channel(const char *name, int fd,
 
 cleanup:
 	if (data) {
+		unix_funlock(io);
 		if (io->manager != unixfd_io_manager && data->dev >= 0)
 			close(data->dev);
 		if (data->cache) {
@@ -1147,6 +1214,8 @@ static errcode_t unix_close(io_channel channel)
 	retval = flush_cached_blocks(channel, data, 0);
 #endif
 
+	unix_funlock(channel);
+
 	if (channel->manager != unixfd_io_manager && close(data->dev) < 0)
 		retval = errno;
 	free_cache(data);
@@ -1683,6 +1752,7 @@ static struct struct_io_manager struct_unix_manager = {
 	.discard	= unix_discard,
 	.cache_readahead	= unix_cache_readahead,
 	.zeroout	= unix_zeroout,
+	.flock		= unix_flock,
 };
 
 io_manager unix_io_manager = &struct_unix_manager;
@@ -1704,6 +1774,7 @@ static struct struct_io_manager struct_unixfd_manager = {
 	.discard	= unix_discard,
 	.cache_readahead	= unix_cache_readahead,
 	.zeroout	= unix_zeroout,
+	.flock		= unix_flock,
 };
 
 io_manager unixfd_io_manager = &struct_unixfd_manager;


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

* [PATCH 2/4] fuse2fs: try to lock filesystem image files before using them
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
  2025-11-06 22:30   ` [PATCH 1/4] libext2fs: add POSIX advisory locking to the unix IO manager Darrick J. Wong
@ 2025-11-06 22:30   ` Darrick J. Wong
  2025-11-06 22:30   ` [PATCH 3/4] fuse2fs: quiet down write-protect warning Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 4/4] fuse2fs: try to grab block device O_EXCL repeatedly Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:30 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Originally, this patch added support for using flock() to protect the
files opened by the Unix IO manager so that systemd and udev could not
access the block device while e2fsprogs is doing something with the
device.  However, flocking block devices for the duration of a mount
creates stalling problems with udev, because it will not process a
uevent for a block device until it can flock(LOCK_SH) the block device.
As a result, udevsettle blocks for 2 minutes until it times out and
exits with failure.

Since libext2fs generally opens block devices (on Linux) with O_EXCL,
fuse2fs is guaranteed to be the only program accessing the filesystem if
it's on a block device.  However, O_EXCL doesn't do anything for
non-block devices, so we need a way to coordinate write access to
filesystem image files.  Use the locking code, but only if we have a
non-block device.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 83886faf2eada8..7b94f0df1688a1 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -297,6 +297,55 @@ static inline off_t FUSE2FS_FSB_TO_B(const struct fuse2fs *ff, blk64_t bno)
 	return bno << ff->blocklog;
 }
 
+static double gettime_monotonic(void)
+{
+#ifdef CLOCK_MONOTONIC
+	struct timespec ts;
+#endif
+	struct timeval tv;
+	static pthread_mutex_t fake_lock = PTHREAD_MUTEX_INITIALIZER;
+	static double fake_time = 0;
+	double dret;
+	int ret;
+
+#ifdef CLOCK_MONOTONIC
+	ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+	if (ret == 0)
+		return (double)ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+#endif
+	ret = gettimeofday(&tv, NULL);
+	if (ret == 0)
+		return (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+
+	/* If we have no clock sources at all, fake it */
+	pthread_mutex_lock(&fake_lock);
+	fake_time += 1.0;
+	dret = fake_time;
+	pthread_mutex_unlock(&fake_lock);
+
+	return dret;
+}
+
+static double init_deadline(double timeout)
+{
+	return gettime_monotonic() + timeout;
+}
+
+static int retry_before_deadline(double deadline)
+{
+	double now = gettime_monotonic();
+
+	if (now >= deadline)
+		return 0;
+
+	/* sleep for 0.1s before trying again */
+	usleep(100000);
+	return 1;
+}
+
+/* Wait this many seconds to acquire the filesystem device */
+#define FUSE2FS_OPEN_TIMEOUT	(15.0)
+
 #define EXT4_EPOCH_BITS 2
 #define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
 #define EXT4_NSEC_MASK  (~0UL << EXT4_EPOCH_BITS)
@@ -4684,6 +4733,7 @@ int main(int argc, char *argv[])
 	errcode_t err;
 	FILE *orig_stderr = stderr;
 	char extra_args[BUFSIZ];
+	double deadline;
 	int ret;
 	int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_EXCLUSIVE |
 		    EXT2_FLAG_RW;
@@ -4791,6 +4841,34 @@ int main(int argc, char *argv[])
 		err_printf(&fctx, "%s\n", _("Please run e2fsck -fy."));
 		goto out;
 	}
+
+	/*
+	 * If the filesystem is stored in a regular file, take an (advisory)
+	 * exclusive lock to prevent other instances of e2fsprogs from writing
+	 * to the filesystem image.  On Linux we don't want to do this for
+	 * block devices because udev will spin forever trying to settle a
+	 * uevent and cause weird userspace stalls, and block devices have
+	 * O_EXCL so we don't need this there.
+	 */
+	if (!(global_fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
+		unsigned int lock_flags = IO_CHANNEL_FLOCK_TRYLOCK;
+
+		if (global_fs->flags & IO_FLAG_RW)
+			lock_flags |= IO_CHANNEL_FLOCK_EXCLUSIVE;
+		else
+			lock_flags |= IO_CHANNEL_FLOCK_SHARED;
+
+		deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
+		do {
+			err = io_channel_flock(global_fs->io, lock_flags);
+		} while (err == EWOULDBLOCK && retry_before_deadline(deadline));
+		if (err) {
+			err_printf(&fctx, "%s: %s\n",
+ _("Could not lock filesystem image"), error_message(err));
+			goto out;
+		}
+	}
+
 	fctx.fs = global_fs;
 	global_fs->priv_data = &fctx;
 	fctx.blocklog = u_log2(fctx.fs->blocksize);


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

* [PATCH 3/4] fuse2fs: quiet down write-protect warning
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
  2025-11-06 22:30   ` [PATCH 1/4] libext2fs: add POSIX advisory locking to the unix IO manager Darrick J. Wong
  2025-11-06 22:30   ` [PATCH 2/4] fuse2fs: try to lock filesystem image files before using them Darrick J. Wong
@ 2025-11-06 22:30   ` Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 4/4] fuse2fs: try to grab block device O_EXCL repeatedly Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:30 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

No need to retry the mount with write protection if we already set ro
and cleared EXT2_FLAG_RO.

Fixes: e352b2ad174573 ("fuse2fs: mount norecovery if main block device is readonly")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 7b94f0df1688a1..bbd79d6c09f4bc 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4823,7 +4823,8 @@ int main(int argc, char *argv[])
 		flags |= EXT2_FLAG_DIRECT_IO;
 	err = ext2fs_open2(fctx.device, options, flags, 0, 0, unix_io_manager,
 			   &global_fs);
-	if (err == EPERM || err == EACCES) {
+	if ((err == EPERM || err == EACCES) &&
+	    (!fctx.ro || (flags & EXT2_FLAG_RW))) {
 		/*
 		 * Source device cannot be opened for write.  Under these
 		 * circumstances, mount(8) will try again with a ro mount,


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

* [PATCH 4/4] fuse2fs: try to grab block device O_EXCL repeatedly
  2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
                     ` (2 preceding siblings ...)
  2025-11-06 22:30   ` [PATCH 3/4] fuse2fs: quiet down write-protect warning Darrick J. Wong
@ 2025-11-06 22:31   ` Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:31 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

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

generic/646 keeps failing to mount in this fashion:

 run fstests generic/646 at 2025-10-20 17:10:26
 [U] FUSE2FS (sda4): mounted filesystem f8f21d10-2ec9-4aef-a509-b32659b4e6b0.
 fuse: EXPERIMENTAL iomap feature enabled.  Use at your own risk!
 [U] FUSE2FS (sda4): shut down requested.
 [U] FUSE2FS (sda4): unmounted filesystem f8f21d10-2ec9-4aef-a509-b32659b4e6b0.
 [U] FUSE2FS (sda4): mounted filesystem 9efc6297-74c0-448c-b253-cecffd947239.
 fuse: EXPERIMENTAL iomap feature enabled.  Use at your own risk!
 [U] FUSE2FS (sda4): shut down requested.
 [U] FUSE2FS (sda4): unmounted filesystem 9efc6297-74c0-448c-b253-cecffd947239.
 [U] FUSE2FS (sda4): mounted filesystem 9efc6297-74c0-448c-b253-cecffd947239.
 [U] FUSE2FS (sda4): Warning: Mounting unchecked fs, running e2fsck is recommended.
 [U] FUSE2FS (sda4): Device or resource busy.
 [U] FUSE2FS (sda4): Please run e2fsck -fy.
 [U] Mount failed while opening filesystem.  Check dmesg(1) for details.
 [U] FUSE2FS (sda4): unmounted filesystem 9efc6297-74c0-448c-b253-cecffd947239.

It turns out that one can mount a fuse filesystem and unmount it before
the kernel even has a chance to send FUSE_INIT to the fuse server.  If
this occurs, the unmount code will abort the FUSE_INIT request and tear
down the fs mount immediately.

Unfortunately for fstests, the fuse server may have already opened the
block device with O_EXCL and will keep running with the bdev open until
libfuse notices that the connection to the kernel died and tells the
fuse server to destroy itself.  That might not happen for a long time
after the unmount program exits, in which case a subsequent invocation
of the fuse server can race with the dying fuse server to open the block
device.  When this happens, the new invocation fails with "Device or
resource busy".

This is exactly what's happening in this test, which is only noticeable
because it cycles the scratch mount so quickly.

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 |   67 +++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 54 insertions(+), 13 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index bbd79d6c09f4bc..76872d793ea394 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4821,21 +4821,62 @@ int main(int argc, char *argv[])
 	sprintf(options, "offset=%lu", fctx.offset);
 	if (fctx.directio)
 		flags |= EXT2_FLAG_DIRECT_IO;
-	err = ext2fs_open2(fctx.device, options, flags, 0, 0, unix_io_manager,
-			   &global_fs);
-	if ((err == EPERM || err == EACCES) &&
-	    (!fctx.ro || (flags & EXT2_FLAG_RW))) {
-		/*
-		 * Source device cannot be opened for write.  Under these
-		 * circumstances, mount(8) will try again with a ro mount,
-		 * and the kernel will open the block device readonly.
-		 */
-		log_printf(&fctx, "%s\n",
- _("WARNING: source write-protected, mounted read-only."));
-		flags &= ~EXT2_FLAG_RW;
-		fctx.ro = 1;
+
+	/*
+	 * If the filesystem is stored on a block device, the _EXCLUSIVE flag
+	 * causes libext2fs to try to open the block device with O_EXCL.  If
+	 * the block device is already opened O_EXCL by something else, the
+	 * open call returns EBUSY.
+	 *
+	 * Unfortunately, there's a nasty race between fuse2fs going through
+	 * its startup sequence (open fs, parse superblock, daemonize, create
+	 * mount, respond to FUSE_INIT) in response to a mount(8) invocation
+	 * and another process that calls umount(2) on the same mount.
+	 *
+	 * If fuse2fs is being run as a mount(8) helper and has daemonized, the
+	 * original fuse2fs subprocess exits and so will mount(8).  This can
+	 * occur before the kernel issues a FUSE_INIT request to fuse2fs.  If
+	 * a process then umount(2)'s the mount, the kernel will abort the
+	 * fuse connection.  If the FUSE_INIT request hasn't been issued, now
+	 * it won't ever be issued.  The kernel tears down the mount and
+	 * returns from umount(2), but fuse2fs has no idea that any of this has
+	 * happened because it receives no requests.
+	 *
+	 * At this point, the original fuse2fs server holds the block device
+	 * open O_EXCL.  If mount(8) is invoked again on the same device, the
+	 * new fuse2fs server will try to open the block device O_EXCL and
+	 * fail.  A crappy solution here is to retry for 5 seconds, hoping that
+	 * the first fuse2fs server will wake up and exit.
+	 *
+	 * If the filesystem is in a regular file, O_EXCL (without O_CREAT) has
+	 * no defined behavior, but it never returns EBUSY.
+	 */
+	deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
+	do {
 		err = ext2fs_open2(fctx.device, options, flags, 0, 0,
 				   unix_io_manager, &global_fs);
+		if ((err == EPERM || err == EACCES) &&
+		    (!fctx.ro || (flags & EXT2_FLAG_RW))) {
+			/*
+			 * Source device cannot be opened for write.  Under
+			 * these circumstances, mount(8) will try again with a
+			 * ro mount, and the kernel will open the block device
+			 * readonly.
+			 */
+			log_printf(&fctx, "%s\n",
+ _("WARNING: source write-protected, mounted read-only."));
+			flags &= ~EXT2_FLAG_RW;
+			fctx.ro = 1;
+
+			/* Force the loop to run once more */
+			err = -1;
+		}
+	} while (err == -1 ||
+		 (err == EBUSY && retry_before_deadline(deadline)));
+	if (err == EBUSY) {
+		err_printf(&fctx, "%s: %s.\n",
+ _("Could not lock filesystem block device"), error_message(err));
+		goto out;
 	}
 	if (err) {
 		err_printf(&fctx, "%s.\n", error_message(err));


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

* [PATCH 01/19] libext2fs: initialize htree when expanding directory
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
@ 2025-11-06 22:31   ` Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 02/19] libext2fs: create link count adjustment helpers for dir_nlink Darrick J. Wong
                     ` (17 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:31 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Teach ext2fs_link to initialize the htree when we're expanding a
directory beyond the first block.  This brings fuse2fs' behavior in line
with the kernel and dramatically improves performance.  It also is the
start of a bunch of bug fixing for ext4/045.  This is a straight port of
fs/ext4/namei.c.

This patch is needed for the next patch, which fixes the dir_nlink
functionality, because apparently the two features are intertwined.

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


diff --git a/lib/ext2fs/link.c b/lib/ext2fs/link.c
index 1697da39f8855f..83d3ea7d00c3ed 100644
--- a/lib/ext2fs/link.c
+++ b/lib/ext2fs/link.c
@@ -599,6 +599,237 @@ static errcode_t dx_link(ext2_filsys fs, ext2_ino_t dir,
 	return retval;
 }
 
+struct fake_dirent
+{
+	__le32 inode;
+	__le16 rec_len;
+	__u8 name_len;
+	__u8 file_type;
+};
+
+/*
+ * dx_root_info is laid out so that if it should somehow get overlaid by a
+ * dirent the two low bits of the hash version will be zero.  Therefore, the
+ * hash version mod 4 should never be 0.  Sincerely, the paranoia department.
+ */
+
+struct dx_root
+{
+	struct fake_dirent dot;
+	char dot_name[4];
+	struct fake_dirent dotdot;
+	char dotdot_name[4];
+	struct ext2_dx_root_info info;
+	struct ext2_dx_entry entries[];
+};
+
+static int check_dx_root(ext2_filsys fs, ext2_ino_t ino, struct dx_root *root)
+{
+	struct fake_dirent *fde;
+	char *error_msg;
+	unsigned int rlen;
+	char *blockend = (char *)root + fs->blocksize;
+
+	fde = &root->dot;
+	if (fde->name_len != 1) {
+		error_msg = "invalid name_len for '.'";
+		goto corrupted;
+	}
+	if (strncmp(root->dot_name, ".", fde->name_len)) {
+		error_msg = "invalid name for '.'";
+		goto corrupted;
+	}
+
+	ext2fs_get_rec_len(fs, (struct ext2_dir_entry *)fde, &rlen);
+	if ((char *)fde + rlen >= blockend) {
+		error_msg = "invalid rec_len for '.'";
+		goto corrupted;
+	}
+
+	fde = &root->dotdot;
+	if (fde->name_len != 2) {
+		error_msg = "invalid name_len for '..'";
+		goto corrupted;
+	}
+	if (strncmp(root->dotdot_name, "..", fde->name_len)) {
+		error_msg = "invalid name for '..'";
+		goto corrupted;
+	}
+	ext2fs_get_rec_len(fs, (struct ext2_dir_entry *)fde, &rlen);
+	if ((char *)fde + rlen >= blockend) {
+		error_msg = "invalid rec_len for '..'";
+		goto corrupted;
+	}
+
+	return 1;
+
+corrupted:
+	fprintf(stderr, "Corrupt dir %u: %s, running e2fsck is recommended\n",
+			 ino, error_msg);
+	return 0;
+}
+
+static inline struct ext2_dir_entry *
+ext2fs_next_entry(ext2_filsys fs, struct ext2_dir_entry *p)
+{
+	unsigned int rlen;
+
+	ext2fs_get_rec_len(fs, p, &rlen);
+	return (struct ext2_dir_entry *)((char *)p + rlen);
+}
+
+static inline void dx_set_block(struct ext2_dx_entry *entry, unsigned value)
+{
+	entry->block = ext2fs_cpu_to_le32(value);
+}
+
+static inline void dx_set_count(struct ext2_dx_entry *entries, unsigned value)
+{
+	((struct ext2_dx_countlimit *) entries)->count = ext2fs_cpu_to_le16(value);
+}
+
+static inline void dx_set_limit(struct ext2_dx_entry *entries, unsigned value)
+{
+	((struct ext2_dx_countlimit *) entries)->limit = ext2fs_cpu_to_le16(value);
+}
+
+static inline unsigned dx_root_limit(ext2_filsys fs, unsigned infosize)
+{
+	unsigned int entry_space = fs->blocksize -
+			ext2fs_dir_rec_len(1, 0) -
+			ext2fs_dir_rec_len(2, 0) - infosize;
+
+	if (ext2fs_has_feature_metadata_csum(fs->super))
+		entry_space -= sizeof(struct ext2_dx_tail);
+	return entry_space / sizeof(struct ext2_dx_entry);
+}
+
+/*
+ * This converts a one block unindexed directory to a 3 block indexed
+ * directory, and adds the dentry to the indexed directory.  Returns 0 if the
+ * index was not created; EAGAIN if it was; or an errcode_t on error.
+ */
+static errcode_t try_make_indexed_dir(ext2_filsys fs, ext2_ino_t dir,
+				      const char *name, ext2_ino_t ino,
+				      int flags)
+{
+	struct ext2_inode inode;
+	char *buf0, *buf1, *top;
+	struct dx_root *root;
+	struct ext2_dx_entry *entries;
+	struct fake_dirent *fde;
+	struct ext2_dir_entry *de, *de2;
+	blk64_t pblk0, pblk1;
+	unsigned int len;
+	const unsigned int blocksize = fs->blocksize;
+	int csum_size = 0;
+	errcode_t retval;
+
+	retval = ext2fs_read_inode(fs, dir, &inode);
+	if (retval)
+		return retval;
+
+	if (inode.i_size > fs->blocksize || (inode.i_flags & EXT2_INDEX_FL))
+		return 0;
+
+	if (ext2fs_has_feature_metadata_csum(fs->super))
+		csum_size = sizeof(struct ext2_dir_entry_tail);
+
+	retval = ext2fs_get_mem(blocksize, &buf0);
+	if (retval)
+		return retval;
+
+	retval = ext2fs_get_mem(blocksize, &buf1);
+	if (retval)
+		goto out_buf0;
+
+	retval = ext2fs_bmap2(fs, dir, &inode, NULL, 0, 0, NULL, &pblk0);
+	if (retval)
+		goto out_buf1;
+
+	retval = ext2fs_read_dir_block4(fs, pblk0, buf0, 0, dir);
+	if (retval)
+		goto out_buf1;
+
+	root = (struct dx_root *)buf0;
+	if (!check_dx_root(fs, dir, root)) {
+		retval = EXT2_ET_DIR_CORRUPTED;
+		goto out_buf1;
+	}
+
+	/* The 0th block becomes the root, move the dirents out */
+	fde = &root->dotdot;
+	de = ext2fs_next_entry(fs, (struct ext2_dir_entry *)fde);
+	len = ((char *) root) + (blocksize - csum_size) - (char *) de;
+
+	/* Allocate new block for the 0th block's dirents */
+	retval = ext2fs_bmap2(fs, dir, &inode, NULL, BMAP_ALLOC | BMAP_ZERO, 1,
+			      NULL, &pblk1);
+	if (retval)
+		goto out_buf1;
+
+	memcpy(buf1, de, len);
+	memset(de, 0, len); /* wipe old data */
+
+	de = (struct ext2_dir_entry *)buf1;
+	top = buf1 + len;
+	while ((char *)(de2 = ext2fs_next_entry(fs, de)) < top) {
+#if 0
+		if (ext4_check_dir_entry(dir, NULL, de, bh2, buf1, len,
+					(char *)de - buf1)) {
+			retval = EXT2_ET_DIR_CORRUPTED;
+			goto out_buf1;
+		}
+#endif
+		de = de2;
+	}
+	retval = ext2fs_set_rec_len(fs,
+			buf1 + (blocksize - csum_size) - (char *) de, de);
+	if (retval)
+		goto out_buf1;
+
+	if (csum_size)
+		ext2fs_initialize_dirent_tail(fs,
+				EXT2_DIRENT_TAIL(buf1, fs->blocksize));
+
+	/* Initialize the root; the dot dirents already exist */
+	de = (struct ext2_dir_entry *) (&root->dotdot);
+	retval = ext2fs_set_rec_len(fs, blocksize - ext2fs_dir_rec_len(2, 0),
+				    de);
+	memset (&root->info, 0, sizeof(root->info));
+	root->info.info_length = sizeof(root->info);
+	if (ext4_hash_in_dirent(&inode))
+		root->info.hash_version = EXT2_HASH_SIPHASH;
+	else
+		root->info.hash_version = fs->super->s_def_hash_version;
+
+	entries = root->entries;
+	dx_set_block(entries, 1);
+	dx_set_count(entries, 1);
+	dx_set_limit(entries, dx_root_limit(fs, sizeof(root->info)));
+
+	retval = ext2fs_write_dir_block4(fs, pblk1, buf1, 0, dir);
+	if (retval)
+		goto out_buf1;
+
+	retval = ext2fs_write_dir_block4(fs, pblk0, buf0, 0, dir);
+	if (retval)
+		goto out_buf1;
+
+	inode.i_flags |= EXT2_INDEX_FL;
+	inode.i_size += fs->blocksize;
+	retval = ext2fs_write_inode(fs, dir, &inode);
+	if (retval)
+		goto out_buf1;
+
+	retval = EAGAIN;
+out_buf1:
+	ext2fs_free_mem(&buf1);
+out_buf0:
+	ext2fs_free_mem(&buf0);
+	return retval;
+}
+
 /*
  * Note: the low 3 bits of the flags field are used as the directory
  * entry filetype.
@@ -671,6 +902,16 @@ errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name,
 	if (!ls.done) {
 		if (!(flags & EXT2FS_LINK_EXPAND))
 			return EXT2_ET_DIR_NO_SPACE;
+
+		if (ext2fs_has_feature_dir_index(fs->super)) {
+			retval = try_make_indexed_dir(fs, dir, name, ino,
+						      flags);
+			if (retval == EAGAIN)
+				goto retry;
+			if (retval)
+				return retval;
+		}
+
 		retval = ext2fs_expand_dir(fs, dir);
 		if (retval)
 			return retval;


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

* [PATCH 02/19] libext2fs: create link count adjustment helpers for dir_nlink
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 01/19] libext2fs: initialize htree when expanding directory Darrick J. Wong
@ 2025-11-06 22:31   ` Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 03/19] libext2fs: fix ext2fs_mmp_update Darrick J. Wong
                     ` (16 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:31 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

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

Create some helpers to deal with link count adjustments for directories
on dir_nlink filesystems that become large enough to have an htree
index.  In other words, fix the problem that creating a new child
subdirectory can overflow the link count of the parent directory.

The unused library functions created by this patch will be used in the
next patch to fix problems with fuse2fs.

Cc: <linux-ext4@vger.kernel.org> # v1.40
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/ext2fs.h          |    5 ++++
 debian/libext2fs2t64.symbols |    5 ++++
 lib/ext2fs/link.c            |   49 ++++++++++++++++++++++++++++++++++++++++++
 lib/ext2fs/mkdir.c           |    2 +-
 4 files changed, 60 insertions(+), 1 deletion(-)


diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index bb2170b78d6308..dec48b80d341db 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -1840,6 +1840,11 @@ errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name,
 		      ext2_ino_t ino, int flags);
 errcode_t ext2fs_unlink(ext2_filsys fs, ext2_ino_t dir, const char *name,
 			ext2_ino_t ino, int flags);
+int ext2fs_dir_is_dx(ext2_filsys fs, const struct ext2_inode *inode);
+void ext2fs_inc_nlink(ext2_filsys fs, struct ext2_inode *inode);
+void ext2fs_dec_nlink(struct ext2_inode *inode);
+int ext2fs_dir_link_max(ext2_filsys fs, struct ext2_inode_large *inode);
+int ext2fs_dir_link_empty(struct ext2_inode *inode);
 
 /* symlink.c */
 errcode_t ext2fs_symlink(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t ino,
diff --git a/debian/libext2fs2t64.symbols b/debian/libext2fs2t64.symbols
index b4d80161f1e1b4..01f4f269a2660e 100644
--- a/debian/libext2fs2t64.symbols
+++ b/debian/libext2fs2t64.symbols
@@ -173,6 +173,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_dblist_iterate@Base 1.37
  ext2fs_dblist_sort2@Base 1.42
  ext2fs_dblist_sort@Base 1.37
+ ext2fs_dec_nlink@Base 1.47.4
  ext2fs_decode_extent@Base 1.46.0
  ext2fs_default_journal_size@Base 1.40
  ext2fs_default_orphan_file_blocks@Base 1.47.0
@@ -182,6 +183,9 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_dir_block_csum_verify@Base 1.43
  ext2fs_dir_iterate2@Base 1.37
  ext2fs_dir_iterate@Base 1.37
+ ext2fs_dir_is_dx@Base 1.47.4
+ ext2fs_dir_link_empty@Base 1.47.4
+ ext2fs_dir_link_max@Base 1.47.4
  ext2fs_dirent_csum_verify@Base 1.43
  ext2fs_dirent_file_type@Base 1.43
  ext2fs_dirent_has_tail@Base 1.43
@@ -376,6 +380,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_image_inode_write@Base 1.37
  ext2fs_image_super_read@Base 1.37
  ext2fs_image_super_write@Base 1.37
+ ext2fs_inc_nlink@Base 1.47.4
  ext2fs_init_csum_seed@Base 1.43
  ext2fs_init_dblist@Base 1.37
  ext2fs_initialize@Base 1.37
diff --git a/lib/ext2fs/link.c b/lib/ext2fs/link.c
index 83d3ea7d00c3ed..eccbf0d23b5d6c 100644
--- a/lib/ext2fs/link.c
+++ b/lib/ext2fs/link.c
@@ -919,3 +919,52 @@ errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name,
 	}
 	return 0;
 }
+
+/* Does this directory have an htree index? */
+int ext2fs_dir_is_dx(ext2_filsys fs, const struct ext2_inode *inode)
+{
+	return ext2fs_has_feature_dir_index(fs->super) &&
+		S_ISDIR(inode->i_mode) && (inode->i_flags & EXT2_INDEX_FL);
+}
+
+/*
+ * Set directory link count to 1 if nlinks > EXT2_LINK_MAX, or if nlinks == 2
+ * since this indicates that nlinks count was previously 1 to avoid overflowing
+ * the 16-bit i_links_count field on disk.  Directories with i_nlink == 1 mean
+ * that subdirectory link counts are not being maintained accurately.
+ *
+ * The caller has already checked for i_nlink overflow in case the DIR_LINK
+ * feature is not enabled and returned -EMLINK.  The is_dx() check is a proxy
+ * for checking S_ISDIR(inode) (since the INODE_INDEX feature will not be set
+ * on regular files) and to avoid creating huge/slow non-HTREE directories.
+ */
+void ext2fs_inc_nlink(ext2_filsys fs, struct ext2_inode *inode)
+{
+	inode->i_links_count++;
+
+	if (ext2fs_dir_is_dx(fs, inode) &&
+	    (inode->i_links_count > EXT2_LINK_MAX || inode->i_links_count == 2))
+		inode->i_links_count = 1;
+}
+
+/*
+ * If a directory had nlink == 1, then we should let it be 1. This indicates
+ * directory has >EXT2_LINK_MAX subdirs.
+ */
+void ext2fs_dec_nlink(struct ext2_inode *inode)
+{
+	if (!S_ISDIR(inode->i_mode) || inode->i_links_count > 2)
+		inode->i_links_count--;
+}
+
+int ext2fs_dir_link_max(ext2_filsys fs, struct ext2_inode_large *inode)
+{
+	return inode->i_links_count >= EXT2_LINK_MAX &&
+	       !(ext2fs_dir_is_dx(fs, EXT2_INODE(inode)) &&
+		 ext2fs_has_feature_dir_nlink(fs->super));
+}
+
+int ext2fs_dir_link_empty(struct ext2_inode *inode)
+{
+	return inode->i_links_count == 2 || inode->i_links_count == 1;
+}
diff --git a/lib/ext2fs/mkdir.c b/lib/ext2fs/mkdir.c
index 45f6e9e27164d2..c0a08c88560bd2 100644
--- a/lib/ext2fs/mkdir.c
+++ b/lib/ext2fs/mkdir.c
@@ -182,7 +182,7 @@ errcode_t ext2fs_mkdir2(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t ino,
 		retval = ext2fs_read_inode(fs, parent, &parent_inode);
 		if (retval)
 			goto cleanup;
-		parent_inode.i_links_count++;
+		ext2fs_inc_nlink(fs, &parent_inode);
 		retval = ext2fs_write_inode(fs, parent, &parent_inode);
 		if (retval)
 			goto cleanup;


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

* [PATCH 03/19] libext2fs: fix ext2fs_mmp_update
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 01/19] libext2fs: initialize htree when expanding directory Darrick J. Wong
  2025-11-06 22:31   ` [PATCH 02/19] libext2fs: create link count adjustment helpers for dir_nlink Darrick J. Wong
@ 2025-11-06 22:31   ` Darrick J. Wong
  2025-11-06 22:32   ` [PATCH 04/19] libext2fs: refactor aligned MMP buffer allocation Darrick J. Wong
                     ` (15 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:31 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

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

ext2fs_mmp_read has this undocumented behavior that it updates
fs->mmp_cmp and not fs->mmp_buf.  Therefore, ext2fs_mmp_update2 actually
updates a stale version of the MMP buffer and writes that out to disk.
Fortunately the only two fields that get updated regularly mmp_time and
mmp_seq so the behavior was never incorrect, but this confused me for a
while, so let's fix it.

Cc: <linux-ext4@vger.kernel.org> # v1.42
Fixes: 0f5eba7501f467 ("ext2fs: add multi-mount protection (INCOMPAT_MMP)")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/mmp.c |    6 ++++++
 1 file changed, 6 insertions(+)


diff --git a/lib/ext2fs/mmp.c b/lib/ext2fs/mmp.c
index eb9417020e6d3f..6337852c3f6700 100644
--- a/lib/ext2fs/mmp.c
+++ b/lib/ext2fs/mmp.c
@@ -469,6 +469,12 @@ errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately)
 	if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
 		return EXT2_ET_MMP_CHANGE_ABORT;
 
+	/*
+	 * Believe it or not, ext2fs_mmp_read actually overwrites fs->mmp_cmp
+	 * and leaves fs->mmp_buf untouched.  Hence we copy mmp_cmp into
+	 * mmp_buf, update mmp_buf, and write mmp_buf out to disk.
+	 */
+	memcpy(mmp, mmp_cmp, sizeof(*mmp_cmp));
 	mmp->mmp_time = tv.tv_sec;
 	mmp->mmp_seq = EXT4_MMP_SEQ_FSCK;
 	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);


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

* [PATCH 04/19] libext2fs: refactor aligned MMP buffer allocation
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (2 preceding siblings ...)
  2025-11-06 22:31   ` [PATCH 03/19] libext2fs: fix ext2fs_mmp_update Darrick J. Wong
@ 2025-11-06 22:32   ` Darrick J. Wong
  2025-11-06 22:32   ` [PATCH 05/19] libext2fs: always use ext2fs_mmp_get_mem to allocate fs->mmp_buf Darrick J. Wong
                     ` (14 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:32 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Hoist the code that allocates an MMP buffer into a separate helper.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/ext2fs.h          |    1 +
 debian/libext2fs2t64.symbols |    1 +
 lib/ext2fs/dupfs.c           |    5 +----
 lib/ext2fs/mmp.c             |   12 ++++++++----
 4 files changed, 11 insertions(+), 8 deletions(-)


diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index dec48b80d341db..c4fcb10bea0fb9 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -1861,6 +1861,7 @@ errcode_t ext2fs_mmp_update(ext2_filsys fs);
 errcode_t ext2fs_mmp_update2(ext2_filsys fs, int immediately);
 errcode_t ext2fs_mmp_stop(ext2_filsys fs);
 unsigned ext2fs_mmp_new_seq(void);
+errcode_t ext2fs_mmp_get_mem(ext2_filsys fs, void **ptr);
 
 /* read_bb.c */
 extern errcode_t ext2fs_read_bb_inode(ext2_filsys fs,
diff --git a/debian/libext2fs2t64.symbols b/debian/libext2fs2t64.symbols
index 01f4f269a2660e..affe4c27d4e791 100644
--- a/debian/libext2fs2t64.symbols
+++ b/debian/libext2fs2t64.symbols
@@ -448,6 +448,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_mmp_clear@Base 1.42
  ext2fs_mmp_csum_set@Base 1.43
  ext2fs_mmp_csum_verify@Base 1.43
+ ext2fs_mmp_get_mem@Base 1.47.4
  ext2fs_mmp_init@Base 1.42
  ext2fs_mmp_new_seq@Base 1.42
  ext2fs_mmp_read@Base 1.42
diff --git a/lib/ext2fs/dupfs.c b/lib/ext2fs/dupfs.c
index 02721e1a574a1f..0fd4e6c67afb5d 100644
--- a/lib/ext2fs/dupfs.c
+++ b/lib/ext2fs/dupfs.c
@@ -104,10 +104,7 @@ errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest)
 		}
 	}
 	if (src->mmp_cmp) {
-		int align = ext2fs_get_dio_alignment(src->mmp_fd);
-
-		retval = ext2fs_get_memalign(src->blocksize, align,
-					     &fs->mmp_cmp);
+		retval = ext2fs_mmp_get_mem(src, &fs->mmp_cmp);
 		if (retval)
 			goto errout;
 		memcpy(fs->mmp_cmp, src->mmp_cmp, src->blocksize);
diff --git a/lib/ext2fs/mmp.c b/lib/ext2fs/mmp.c
index 6337852c3f6700..41ef4e3e2aa6c5 100644
--- a/lib/ext2fs/mmp.c
+++ b/lib/ext2fs/mmp.c
@@ -41,6 +41,13 @@
 #endif
 #endif
 
+errcode_t ext2fs_mmp_get_mem(ext2_filsys fs, void **ptr)
+{
+	int align = ext2fs_get_dio_alignment(fs->mmp_fd);
+
+	return ext2fs_get_memalign(fs->blocksize, align, ptr);
+}
+
 errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
 {
 #ifdef CONFIG_MMP
@@ -78,10 +85,7 @@ errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
 	}
 
 	if (fs->mmp_cmp == NULL) {
-		int align = ext2fs_get_dio_alignment(fs->mmp_fd);
-
-		retval = ext2fs_get_memalign(fs->blocksize, align,
-					     &fs->mmp_cmp);
+		retval = ext2fs_mmp_get_mem(fs, &fs->mmp_cmp);
 		if (retval)
 			return retval;
 	}


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

* [PATCH 05/19] libext2fs: always use ext2fs_mmp_get_mem to allocate fs->mmp_buf
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (3 preceding siblings ...)
  2025-11-06 22:32   ` [PATCH 04/19] libext2fs: refactor aligned MMP buffer allocation Darrick J. Wong
@ 2025-11-06 22:32   ` Darrick J. Wong
  2025-11-06 22:32   ` [PATCH 06/19] fuse2fs: check root directory while mounting Darrick J. Wong
                     ` (13 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:32 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Always use our special allocator function to allocate the MMP buffer.
This will be useful in case we ever pass that buffer to ext2fs_mmp_write
on a filesystem that is opened with O_DIRECT.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 e2fsck/unix.c      |    2 +-
 lib/ext2fs/dupfs.c |    2 +-
 lib/ext2fs/mmp.c   |    6 +++---
 misc/dumpe2fs.c    |    2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)


diff --git a/e2fsck/unix.c b/e2fsck/unix.c
index 7768f0ed7c4e3e..335ca37763e65f 100644
--- a/e2fsck/unix.c
+++ b/e2fsck/unix.c
@@ -1230,7 +1230,7 @@ static errcode_t e2fsck_check_mmp(ext2_filsys fs, e2fsck_t ctx)
 
 	clear_problem_context(&pctx);
 	if (fs->mmp_buf == NULL) {
-		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
+		retval = ext2fs_mmp_get_mem(fs, &fs->mmp_buf);
 		if (retval)
 			goto check_error;
 	}
diff --git a/lib/ext2fs/dupfs.c b/lib/ext2fs/dupfs.c
index 0fd4e6c67afb5d..db3a812d8d1c0d 100644
--- a/lib/ext2fs/dupfs.c
+++ b/lib/ext2fs/dupfs.c
@@ -91,7 +91,7 @@ errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest)
 			goto errout;
 	}
 	if (src->mmp_buf) {
-		retval = ext2fs_get_mem(src->blocksize, &fs->mmp_buf);
+		retval = ext2fs_mmp_get_mem(src, &fs->mmp_buf);
 		if (retval)
 			goto errout;
 		memcpy(fs->mmp_buf, src->mmp_buf, src->blocksize);
diff --git a/lib/ext2fs/mmp.c b/lib/ext2fs/mmp.c
index 41ef4e3e2aa6c5..e2823732e2b6a2 100644
--- a/lib/ext2fs/mmp.c
+++ b/lib/ext2fs/mmp.c
@@ -204,7 +204,7 @@ static errcode_t ext2fs_mmp_reset(ext2_filsys fs)
 	errcode_t retval = 0;
 
 	if (fs->mmp_buf == NULL) {
-		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
+		retval = ext2fs_mmp_get_mem(fs, &fs->mmp_buf);
 		if (retval)
 			goto out;
 	}
@@ -268,7 +268,7 @@ errcode_t ext2fs_mmp_init(ext2_filsys fs)
 		return EXT2_ET_INVALID_ARGUMENT;
 
 	if (fs->mmp_buf == NULL) {
-		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
+		retval = ext2fs_mmp_get_mem(fs, &fs->mmp_buf);
 		if (retval)
 			goto out;
 	}
@@ -306,7 +306,7 @@ errcode_t ext2fs_mmp_start(ext2_filsys fs)
 	errcode_t retval = 0;
 
 	if (fs->mmp_buf == NULL) {
-		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
+		retval = ext2fs_mmp_get_mem(fs, &fs->mmp_buf);
 		if (retval)
 			goto mmp_error;
 	}
diff --git a/misc/dumpe2fs.c b/misc/dumpe2fs.c
index b56d15d98caa47..1754fd4a999b9f 100644
--- a/misc/dumpe2fs.c
+++ b/misc/dumpe2fs.c
@@ -472,7 +472,7 @@ static void print_mmp_block(ext2_filsys fs)
 	errcode_t retval;
 
 	if (fs->mmp_buf == NULL) {
-		retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
+		retval = ext2fs_mmp_get_mem(fs, &fs->mmp_buf);
 		if (retval) {
 			com_err(program_name, retval,
 				_("failed to alloc MMP buffer\n"));


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

* [PATCH 06/19] fuse2fs: check root directory while mounting
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (4 preceding siblings ...)
  2025-11-06 22:32   ` [PATCH 05/19] libext2fs: always use ext2fs_mmp_get_mem to allocate fs->mmp_buf Darrick J. Wong
@ 2025-11-06 22:32   ` Darrick J. Wong
  2025-11-06 22:32   ` [PATCH 07/19] fuse2fs: read bitmaps asynchronously during initialization Darrick J. Wong
                     ` (12 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:32 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

The kernel will fail a mount attempt if the root directory inode isn't
even minimally readable at mount time.  Do the same for fuse2fs so that
we can pass ext4/008.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 76872d793ea394..3e02eb13ec5488 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4726,6 +4726,19 @@ static unsigned long long default_cache_size(void)
 	return ret;
 }
 
+/* Make sure the root directory is readable. */
+static errcode_t fuse2fs_check_root_dir(ext2_filsys fs)
+{
+	struct ext2_inode_large inode;
+	errcode_t err;
+
+	err = fuse2fs_read_inode(fs, EXT2_ROOT_INO, &inode);
+	if (err)
+		return translate_error(fs, EXT2_ROOT_INO, err);
+
+	return 0;
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
@@ -4978,6 +4991,10 @@ int main(int argc, char *argv[])
 		}
 	}
 
+	ret = fuse2fs_check_root_dir(global_fs);
+	if (ret)
+		goto out;
+
 	if (global_fs->flags & EXT2_FLAG_RW) {
 		if (ext2fs_has_feature_journal(global_fs->super))
 			log_printf(&fctx, "%s",


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

* [PATCH 07/19] fuse2fs: read bitmaps asynchronously during initialization
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (5 preceding siblings ...)
  2025-11-06 22:32   ` [PATCH 06/19] fuse2fs: check root directory while mounting Darrick J. Wong
@ 2025-11-06 22:32   ` Darrick J. Wong
  2025-11-06 22:33   ` [PATCH 08/19] fuse2fs: use file handles when possible Darrick J. Wong
                     ` (11 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:32 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

The kernel reads the bitmaps asynchronously when the filesystem is
mounted.  Do this as well in fuse2fs to reduce mount times.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 3e02eb13ec5488..ebb0539abc2237 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -981,6 +981,21 @@ static int fuse2fs_setup_logging(struct fuse2fs *ff)
 	return 0;
 }
 
+static int fuse2fs_read_bitmaps(struct fuse2fs *ff)
+{
+	errcode_t err;
+
+	err = ext2fs_read_inode_bitmap(ff->fs);
+	if (err)
+		return translate_error(ff->fs, 0, err);
+
+	err = ext2fs_read_block_bitmap(ff->fs);
+	if (err)
+		return translate_error(ff->fs, 0, err);
+
+	return 0;
+}
+
 static void *op_init(struct fuse_conn_info *conn
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, struct fuse_config *cfg EXT2FS_ATTR((unused))
@@ -1028,6 +1043,10 @@ static void *op_init(struct fuse_conn_info *conn
 		uuid_unparse(fs->super->s_uuid, uuid);
 		log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
 	}
+
+	if (global_fs->flags & EXT2_FLAG_RW)
+		fuse2fs_read_bitmaps(ff);
+
 	return ff;
 }
 
@@ -5001,16 +5020,6 @@ int main(int argc, char *argv[])
  _("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);
-			goto out;
-		}
-		err = ext2fs_read_block_bitmap(global_fs);
-		if (err) {
-			translate_error(global_fs, 0, err);
-			goto out;
-		}
 	}
 
 	if (!(global_fs->super->s_state & EXT2_VALID_FS))


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

* [PATCH 08/19] fuse2fs: use file handles when possible
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (6 preceding siblings ...)
  2025-11-06 22:32   ` [PATCH 07/19] fuse2fs: read bitmaps asynchronously during initialization Darrick J. Wong
@ 2025-11-06 22:33   ` Darrick J. Wong
  2025-11-06 22:33   ` [PATCH 09/19] fuse2fs: implement dir seeking Darrick J. Wong
                     ` (10 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:33 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Use file handles when possible, so the f* family of file syscalls
doesn't have to do a complete path lookup for every single call.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index ebb0539abc2237..5e0a1f1fd58be4 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1035,6 +1035,7 @@ static void *op_init(struct fuse_conn_info *conn
 	cfg->use_ino = 1;
 	if (ff->debug)
 		cfg->debug = 1;
+	cfg->nullpath_ok = 1;
 #endif
 
 	if (ff->kernel) {
@@ -1102,9 +1103,49 @@ static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
 	return ret;
 }
 
+static int __fuse2fs_file_ino(struct fuse2fs *ff, const char *path,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+			      struct fuse_file_info *fp EXT2FS_ATTR((unused)),
+#endif
+			      ext2_ino_t *inop,
+			      const char *func,
+			      int line)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+	if (fp) {
+		struct fuse2fs_file_handle *fh =
+			(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+
+		if (fh->ino == 0)
+			return -ESTALE;
+
+		*inop = fh->ino;
+		dbg_printf(ff, "%s: get ino=%d\n", func, fh->ino);
+		return 0;
+	}
+#endif
+	dbg_printf(ff, "%s: get path=%s\n", func, path);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, inop);
+	if (err)
+		return __translate_error(fs, 0, err, func, line);
+
+	return 0;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+# define fuse2fs_file_ino(ff, path, fp, inop) \
+	__fuse2fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
+#else
+# define fuse2fs_file_ino(ff, path, fp, inop) \
+	__fuse2fs_file_ino((ff), (path), NULL, (inop), __func__, __LINE__)
+#endif
+
 static int op_getattr(const char *path, struct stat *statbuf
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi EXT2FS_ATTR((unused))
+			, struct fuse_file_info *fi
 #endif
 			)
 {
@@ -1112,18 +1153,14 @@ static int op_getattr(const char *path, struct stat *statbuf
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
 	ext2_filsys fs;
 	ext2_ino_t ino;
-	errcode_t err;
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
-	dbg_printf(ff, "%s: path=%s\n", __func__, path);
 	pthread_mutex_lock(&ff->bfl);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err) {
-		ret = translate_error(fs, 0, err);
+	ret = fuse2fs_file_ino(ff, path, fi, &ino);
+	if (ret)
 		goto out;
-	}
 	ret = stat_inode(fs, ino, statbuf);
 out:
 	pthread_mutex_unlock(&ff->bfl);
@@ -2439,7 +2476,7 @@ static int in_file_group(struct fuse_context *ctxt,
 
 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))
+			, struct fuse_file_info *fi
 #endif
 			)
 {
@@ -2454,11 +2491,9 @@ static int op_chmod(const char *path, mode_t mode
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err) {
-		ret = translate_error(fs, 0, err);
+	ret = fuse2fs_file_ino(ff, path, fi, &ino);
+	if (ret)
 		goto out;
-	}
 	dbg_printf(ff, "%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
 
 	err = fuse2fs_read_inode(fs, ino, &inode);
@@ -2513,7 +2548,7 @@ static int op_chmod(const char *path, mode_t mode
 
 static int op_chown(const char *path, uid_t owner, gid_t group
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi EXT2FS_ATTR((unused))
+			, struct fuse_file_info *fi
 #endif
 			)
 {
@@ -2528,11 +2563,9 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err) {
-		ret = translate_error(fs, 0, err);
+	ret = fuse2fs_file_ino(ff, path, fi, &ino);
+	if (ret)
 		goto out;
-	}
 	dbg_printf(ff, "%s: path=%s owner=%d group=%d ino=%d\n", __func__,
 		   path, owner, group, ino);
 
@@ -2658,29 +2691,20 @@ static int fuse2fs_truncate(struct fuse2fs *ff, ext2_ino_t ino, off_t new_size)
 
 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))
+			, struct fuse_file_info *fi
 #endif
 			)
 {
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	ext2_filsys fs;
 	ext2_ino_t ino;
-	errcode_t err;
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err) {
-		ret = translate_error(fs, 0, err);
+	ret = fuse2fs_file_ino(ff, path, fi, &ino);
+	if (ret)
 		goto out;
-	}
-	if (!ino) {
-		ret = -ESTALE;
-		goto out;
-	}
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
 
 	ret = check_inum_access(ff, ino, W_OK);
@@ -2693,7 +2717,7 @@ static int op_truncate(const char *path, off_t len
 
 out:
 	pthread_mutex_unlock(&ff->bfl);
-	return err;
+	return ret;
 }
 
 #ifdef __linux__
@@ -3747,7 +3771,7 @@ static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
 
 static int op_utimens(const char *path, const struct timespec ctv[2]
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi EXT2FS_ATTR((unused))
+			, struct fuse_file_info *fi
 #endif
 			)
 {
@@ -3764,11 +3788,9 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err) {
-		ret = translate_error(fs, 0, err);
+	ret = fuse2fs_file_ino(ff, path, fi, &ino);
+	if (ret)
 		goto out;
-	}
 	dbg_printf(ff, "%s: ino=%d atime=%lld.%ld mtime=%lld.%ld\n", __func__,
 			ino,
 			(long long int)ctv[0].tv_sec, ctv[0].tv_nsec,


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

* [PATCH 09/19] fuse2fs: implement dir seeking
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (7 preceding siblings ...)
  2025-11-06 22:33   ` [PATCH 08/19] fuse2fs: use file handles when possible Darrick J. Wong
@ 2025-11-06 22:33   ` Darrick J. Wong
  2025-11-06 22:33   ` [PATCH 10/19] fuse2fs: implement readdirplus Darrick J. Wong
                     ` (9 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:33 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Report (fake) directory offsets to readdir so that libfuse can send
smaller datasets to the kernel.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 5e0a1f1fd58be4..1f52d7e4e37713 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -3441,6 +3441,11 @@ struct readdir_iter {
 	void *buf;
 	ext2_filsys fs;
 	fuse_fill_dir_t func;
+
+	struct fuse2fs *ff;
+	unsigned int nr;
+	off_t startpos;
+	off_t dirpos;
 };
 
 static inline mode_t dirent_fmode(ext2_filsys fs,
@@ -3484,9 +3489,15 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 	};
 	int ret;
 
+	i->dirpos++;
+	if (i->startpos >= i->dirpos)
+		return 0;
+
+	dbg_printf(i->ff, "READDIR %u dirpos %llu\n", i->nr++,
+			(unsigned long long)i->dirpos);
 	memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
 	namebuf[dirent->name_len & 0xFF] = 0;
-	ret = i->func(i->buf, namebuf, &stat, 0
+	ret = i->func(i->buf, namebuf, &stat, i->dirpos
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, 0
 #endif
@@ -3499,7 +3510,7 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 
 static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 		      void *buf, fuse_fill_dir_t fill_func,
-		      off_t offset EXT2FS_ATTR((unused)),
+		      off_t offset,
 		      struct fuse_file_info *fp
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, enum fuse_readdir_flags flags EXT2FS_ATTR((unused))
@@ -3511,13 +3522,18 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 	struct fuse2fs_file_handle *fh =
 		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
 	errcode_t err;
-	struct readdir_iter i;
+	struct readdir_iter i = {
+		.ff = ff,
+		.dirpos = 0,
+		.startpos = offset,
+	};
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	i.fs = ff->fs;
 	FUSE2FS_CHECK_MAGIC(i.fs, fh, FUSE2FS_FILE_MAGIC);
-	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	dbg_printf(ff, "%s: ino=%d offset=%llu\n", __func__, fh->ino,
+			(unsigned long long)offset);
 	pthread_mutex_lock(&ff->bfl);
 	i.buf = buf;
 	i.func = fill_func;


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

* [PATCH 10/19] fuse2fs: implement readdirplus
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (8 preceding siblings ...)
  2025-11-06 22:33   ` [PATCH 09/19] fuse2fs: implement dir seeking Darrick J. Wong
@ 2025-11-06 22:33   ` Darrick J. Wong
  2025-11-06 22:34   ` [PATCH 11/19] fuse2fs: implement dirsync mode Darrick J. Wong
                     ` (8 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:33 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Implement "readdirplus", which I think means that we return full stat
information for directory entries as part of the readdir results.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 1f52d7e4e37713..16492d54f7ed1d 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -3443,6 +3443,9 @@ struct readdir_iter {
 	fuse_fill_dir_t func;
 
 	struct fuse2fs *ff;
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+	enum fuse_readdir_flags flags;
+#endif
 	unsigned int nr;
 	off_t startpos;
 	off_t dirpos;
@@ -3493,8 +3496,23 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 	if (i->startpos >= i->dirpos)
 		return 0;
 
-	dbg_printf(i->ff, "READDIR %u dirpos %llu\n", i->nr++,
+	dbg_printf(i->ff, "READDIR%s %u dirpos %llu\n",
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+			i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
+#else
+			"",
+#endif
+			i->nr++,
 			(unsigned long long)i->dirpos);
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+	if (i->flags == FUSE_READDIR_PLUS) {
+		ret = stat_inode(i->fs, dirent->inode, &stat);
+		if (ret)
+			return DIRENT_ABORT;
+	}
+#endif
+
 	memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
 	namebuf[dirent->name_len & 0xFF] = 0;
 	ret = i->func(i->buf, namebuf, &stat, i->dirpos
@@ -3513,7 +3531,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 		      off_t offset,
 		      struct fuse_file_info *fp
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, enum fuse_readdir_flags flags EXT2FS_ATTR((unused))
+			, enum fuse_readdir_flags flags
 #endif
 			)
 {
@@ -3526,6 +3544,9 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 		.ff = ff,
 		.dirpos = 0,
 		.startpos = offset,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
+		.flags = flags,
+#endif
 	};
 	int ret = 0;
 


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

* [PATCH 11/19] fuse2fs: implement dirsync mode
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (9 preceding siblings ...)
  2025-11-06 22:33   ` [PATCH 10/19] fuse2fs: implement readdirplus Darrick J. Wong
@ 2025-11-06 22:34   ` Darrick J. Wong
  2025-11-06 22:34   ` [PATCH 12/19] fuse2fs: only flush O_SYNC files on close Darrick J. Wong
                     ` (7 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:34 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Implement dirsync so that we only perform full metadata flushes on
directory updates when the sysadmin explicitly wants it.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.1.in |    3 +
 misc/fuse2fs.c    |  111 +++++++++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 102 insertions(+), 12 deletions(-)


diff --git a/misc/fuse2fs.1.in b/misc/fuse2fs.1.in
index 69fc6b01d7b639..b18b7f3464bc74 100644
--- a/misc/fuse2fs.1.in
+++ b/misc/fuse2fs.1.in
@@ -57,6 +57,9 @@ .SS "fuse2fs options:"
 \fB-o\fR direct
 Use O_DIRECT to access the block device.
 .TP
+\fB-o\fR dirsync
+Flush dirty metadata to disk after every directory update.
+.TP
 \fB-o\fR errors=panic
 dump core on error
 .TP
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 16492d54f7ed1d..0b4f7f10ddc97f 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -232,6 +232,7 @@ struct fuse2fs {
 	int kernel;
 	int directio;
 	int acl;
+	int dirsync;
 
 	int logfd;
 	int blocklog;
@@ -1367,6 +1368,41 @@ static int fuse2fs_new_child_gid(struct fuse2fs *ff, ext2_ino_t parent,
 	return 0;
 }
 
+/*
+ * Flush dirty data to disk if we're running in dirsync mode.  If @flushed is a
+ * non-null pointer, this function sets @flushed to 1 if we decided to flush
+ * data, or 0 if not.
+ */
+static inline int fuse2fs_dirsync_flush(struct fuse2fs *ff, ext2_ino_t ino,
+					int *flushed)
+{
+	struct ext2_inode_large inode;
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+
+	if (ff->dirsync)
+		goto flush;
+
+	err = fuse2fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, 0, err);
+
+	if (inode.i_flags & EXT2_DIRSYNC_FL)
+		goto flush;
+
+	if (flushed)
+		*flushed = 0;
+	return 0;
+flush:
+	err = ext2fs_flush2(fs, 0);
+	if (err)
+		return translate_error(fs, 0, err);
+
+	if (flushed)
+		*flushed = 1;
+	return 0;
+}
+
 static int op_mknod(const char *path, mode_t mode, dev_t dev)
 {
 	struct fuse_context *ctxt = fuse_get_context();
@@ -1486,6 +1522,11 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
 	if (ret)
 		goto out2;
+
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
 out2:
 	pthread_mutex_unlock(&ff->bfl);
 out:
@@ -1615,6 +1656,10 @@ static int op_mkdir(const char *path, mode_t mode)
 	if (ret)
 		goto out3;
 
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out3;
+
 out3:
 	ext2fs_free_mem(&block);
 out2:
@@ -1624,7 +1669,8 @@ static int op_mkdir(const char *path, mode_t mode)
 	return ret;
 }
 
-static int unlink_file_by_name(struct fuse2fs *ff, const char *path)
+static int fuse2fs_unlink(struct fuse2fs *ff, const char *path,
+			  ext2_ino_t *parent)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
@@ -1660,7 +1706,13 @@ static int unlink_file_by_name(struct fuse2fs *ff, const char *path)
 	if (err)
 		return translate_error(fs, dir, err);
 
-	return update_mtime(fs, dir, NULL);
+	ret = update_mtime(fs, dir, NULL);
+	if (ret)
+		return ret;
+
+	if (parent)
+		*parent = dir;
+	return 0;
 }
 
 static int remove_ea_inodes(struct fuse2fs *ff, ext2_ino_t ino,
@@ -1772,7 +1824,7 @@ static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
 static int __op_unlink(struct fuse2fs *ff, const char *path)
 {
 	ext2_filsys fs = ff->fs;
-	ext2_ino_t ino;
+	ext2_ino_t parent, ino;
 	errcode_t err;
 	int ret = 0;
 
@@ -1786,13 +1838,18 @@ static int __op_unlink(struct fuse2fs *ff, const char *path)
 	if (ret)
 		goto out;
 
-	ret = unlink_file_by_name(ff, path);
+	ret = fuse2fs_unlink(ff, path, &parent);
 	if (ret)
 		goto out;
 
 	ret = remove_inode(ff, ino);
 	if (ret)
 		goto out;
+
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out;
+
 out:
 	return ret;
 }
@@ -1841,7 +1898,7 @@ static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
 static int __op_rmdir(struct fuse2fs *ff, const char *path)
 {
 	ext2_filsys fs = ff->fs;
-	ext2_ino_t child;
+	ext2_ino_t parent, child;
 	errcode_t err;
 	struct ext2_inode_large inode;
 	struct rd_struct rds;
@@ -1882,7 +1939,7 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 		goto out;
 	}
 
-	ret = unlink_file_by_name(ff, path);
+	ret = fuse2fs_unlink(ff, path, &parent);
 	if (ret)
 		goto out;
 	/* Directories have to be "removed" twice. */
@@ -1913,6 +1970,10 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 		}
 	}
 
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out;
+
 out:
 	return ret;
 }
@@ -2028,6 +2089,11 @@ static int op_symlink(const char *src, const char *dest)
 		ret = translate_error(fs, child, err);
 		goto out2;
 	}
+
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
 out2:
 	pthread_mutex_unlock(&ff->bfl);
 out:
@@ -2073,6 +2139,7 @@ static int op_rename(const char *from, const char *to
 	char *cp, a;
 	struct ext2_inode inode;
 	struct update_dotdot ud;
+	int flushed = 0;
 	int ret = 0;
 
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
@@ -2276,14 +2343,19 @@ static int op_rename(const char *from, const char *to
 		goto out2;
 
 	/* Remove the old file */
-	ret = unlink_file_by_name(ff, from);
+	ret = fuse2fs_unlink(ff, from, NULL);
 	if (ret)
 		goto out2;
 
-	/* Flush the whole mess out */
-	err = ext2fs_flush2(fs, 0);
-	if (err)
-		ret = translate_error(fs, 0, err);
+	ret = fuse2fs_dirsync_flush(ff, from_dir_ino, &flushed);
+	if (ret)
+		goto out2;
+
+	if (from_dir_ino != to_dir_ino && !flushed) {
+		ret = fuse2fs_dirsync_flush(ff, to_dir_ino, NULL);
+		if (ret)
+			goto out2;
+	}
 
 out2:
 	free(temp_from);
@@ -2380,6 +2452,10 @@ static int op_link(const char *src, const char *dest)
 	if (ret)
 		goto out2;
 
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
 out2:
 	pthread_mutex_unlock(&ff->bfl);
 out:
@@ -3722,6 +3798,11 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	ret = __op_open(ff, path, fp);
 	if (ret)
 		goto out2;
+
+	ret = fuse2fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
 out2:
 	pthread_mutex_unlock(&ff->bfl);
 out:
@@ -4661,6 +4742,7 @@ enum {
 	FUSE2FS_HELP,
 	FUSE2FS_HELPFULL,
 	FUSE2FS_CACHE_SIZE,
+	FUSE2FS_DIRSYNC,
 };
 
 #define FUSE2FS_OPT(t, p, v) { t, offsetof(struct fuse2fs, p), v }
@@ -4681,12 +4763,13 @@ static struct fuse_opt fuse2fs_opts[] = {
 	FUSE2FS_OPT("directio",		directio,		1),
 	FUSE2FS_OPT("acl",		acl,			1),
 	FUSE2FS_OPT("noacl",		acl,			0),
+	FUSE2FS_OPT("lockfile=%s",	lockfile,		0),
 
 	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),
-	FUSE2FS_OPT("lockfile=%s",	lockfile,		0),
+	FUSE_OPT_KEY("dirsync",		FUSE2FS_DIRSYNC),
 
 	FUSE_OPT_KEY("-V",             FUSE2FS_VERSION),
 	FUSE_OPT_KEY("--version",      FUSE2FS_VERSION),
@@ -4703,6 +4786,10 @@ static int fuse2fs_opt_proc(void *data, const char *arg,
 	struct fuse2fs *ff = data;
 
 	switch (key) {
+	case FUSE2FS_DIRSYNC:
+		ff->dirsync = 1;
+		/* pass through to libfuse */
+		return 1;
 	case FUSE_OPT_KEY_NONOPT:
 		if (!ff->device) {
 			ff->device = strdup(arg);


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

* [PATCH 12/19] fuse2fs: only flush O_SYNC files on close
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (10 preceding siblings ...)
  2025-11-06 22:34   ` [PATCH 11/19] fuse2fs: implement dirsync mode Darrick J. Wong
@ 2025-11-06 22:34   ` Darrick J. Wong
  2025-11-06 22:34   ` [PATCH 13/19] fuse2fs: improve want_extra_isize handling Darrick J. Wong
                     ` (6 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:34 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Don't call ext2fs_flush2() from op_release unless the file was opened
with either synchronous write flag.

XXX: Maybe this should be replaced with actual incore state tracking for
inodes?

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 0b4f7f10ddc97f..4af22ea9e0d3a3 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -3059,11 +3059,15 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	pthread_mutex_lock(&ff->bfl);
-	if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
+
+	if ((fp->flags & O_SYNC) &&
+	    fs_writeable(fs) &&
+	    (fh->open_flags & EXT2_FILE_WRITE)) {
 		err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
 		if (err)
 			ret = translate_error(fs, fh->ino, err);
 	}
+
 	fp->fh = 0;
 	pthread_mutex_unlock(&ff->bfl);
 


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

* [PATCH 13/19] fuse2fs: improve want_extra_isize handling
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (11 preceding siblings ...)
  2025-11-06 22:34   ` [PATCH 12/19] fuse2fs: only flush O_SYNC files on close Darrick J. Wong
@ 2025-11-06 22:34   ` Darrick J. Wong
  2025-11-06 22:34   ` [PATCH 14/19] fuse2fs: cache symlink targets in the kernel Darrick J. Wong
                     ` (5 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:34 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

System administrators can set the {min,want}_extra_isize fields in the
superblock to try to influence the allocation of extra space in an
inode.  Currently fuse2fs ignores any such value and sets it to the
minimum possible size; let's actually follow it, like the kernel does.

Note: fuse2fs isn't quite as flexible as the kernel is about changing
extra_isize, so this isn't quite good enough to pass ext4/022.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 4af22ea9e0d3a3..f1fb7227f1d077 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1403,6 +1403,27 @@ static inline int fuse2fs_dirsync_flush(struct fuse2fs *ff, ext2_ino_t ino,
 	return 0;
 }
 
+static void fuse2fs_set_extra_isize(struct fuse2fs *ff, ext2_ino_t ino,
+				    struct ext2_inode_large *inode)
+{
+	ext2_filsys fs = ff->fs;
+	size_t extra = sizeof(struct ext2_inode_large) -
+		EXT2_GOOD_OLD_INODE_SIZE;
+
+	if (ext2fs_has_feature_extra_isize(fs->super)) {
+		dbg_printf(ff, "%s: ino=%u extra=%zu want=%u min=%u\n",
+			   __func__, ino, extra, fs->super->s_want_extra_isize,
+			   fs->super->s_min_extra_isize);
+
+		if (fs->super->s_want_extra_isize > extra)
+			extra = fs->super->s_want_extra_isize;
+		if (fs->super->s_min_extra_isize > extra)
+			extra = fs->super->s_min_extra_isize;
+	}
+
+	inode->i_extra_isize = extra;
+}
+
 static int op_mknod(const char *path, mode_t mode, dev_t dev)
 {
 	struct fuse_context *ctxt = fuse_get_context();
@@ -1498,8 +1519,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 	else
 		inode.i_block[0] = dev;
 	inode.i_links_count = 1;
-	inode.i_extra_isize = sizeof(struct ext2_inode_large) -
-		EXT2_GOOD_OLD_INODE_SIZE;
+	fuse2fs_set_extra_isize(ff, child, &inode);
 	fuse2fs_set_uid(&inode, ctxt->uid);
 	fuse2fs_set_gid(&inode, gid);
 
@@ -1617,6 +1637,7 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out2;
 	}
 
+	fuse2fs_set_extra_isize(ff, child, &inode);
 	fuse2fs_set_uid(&inode, ctxt->uid);
 	fuse2fs_set_gid(&inode, gid);
 	inode.i_mode = LINUX_S_IFDIR | (mode & ~S_ISUID);
@@ -2079,6 +2100,7 @@ static int op_symlink(const char *src, const char *dest)
 		goto out2;
 	}
 
+	fuse2fs_set_extra_isize(ff, child, &inode);
 	fuse2fs_set_uid(&inode, ctxt->uid);
 	fuse2fs_set_gid(&inode, gid);
 	inode.i_generation = ff->next_generation++;
@@ -3760,8 +3782,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	memset(&inode, 0, sizeof(inode));
 	inode.i_mode = mode;
 	inode.i_links_count = 1;
-	inode.i_extra_isize = sizeof(struct ext2_inode_large) -
-		EXT2_GOOD_OLD_INODE_SIZE;
+	fuse2fs_set_extra_isize(ff, child, &inode);
 	fuse2fs_set_uid(&inode, ctxt->uid);
 	fuse2fs_set_gid(&inode, gid);
 	if (ext2fs_has_feature_extents(fs->super)) {


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

* [PATCH 14/19] fuse2fs: cache symlink targets in the kernel
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (12 preceding siblings ...)
  2025-11-06 22:34   ` [PATCH 13/19] fuse2fs: improve want_extra_isize handling Darrick J. Wong
@ 2025-11-06 22:34   ` Darrick J. Wong
  2025-11-06 22:35   ` [PATCH 15/19] fuse2fs: constrain worker thread count Darrick J. Wong
                     ` (4 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:34 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Speed up symlinks by allowing the kernel to cache them.

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 f1fb7227f1d077..c1bd76ba449370 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1031,6 +1031,9 @@ static void *op_init(struct fuse_conn_info *conn
 	if (ff->acl)
 		conn->want |= FUSE_CAP_POSIX_ACL;
 #endif
+#ifdef FUSE_CAP_CACHE_SYMLINKS
+	conn->want |= FUSE_CAP_CACHE_SYMLINKS;
+#endif
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	conn->time_gran = 1;
 	cfg->use_ino = 1;


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

* [PATCH 15/19] fuse2fs: constrain worker thread count
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (13 preceding siblings ...)
  2025-11-06 22:34   ` [PATCH 14/19] fuse2fs: cache symlink targets in the kernel Darrick J. Wong
@ 2025-11-06 22:35   ` Darrick J. Wong
  2025-11-06 22:35   ` [PATCH 16/19] fuse2fs: improve error handling behaviors Darrick J. Wong
                     ` (3 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

fuse2fs isn't all that scalable -- there's a big kernel lock around all
the libext2fs code.  Constrain the fuse worker thread count to reduce
unnecessary lock contention.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index c1bd76ba449370..d890855df9c0f3 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -5259,6 +5259,14 @@ int main(int argc, char *argv[])
  "-oallow_other,default_permissions,suid,dev");
 	}
 
+	/*
+	 * Since there's a Big Kernel Lock around all the libext2fs code, we
+	 * only need to start four threads -- one to decode a request, another
+	 * to do the filesystem work, a third to transmit the reply, and a
+	 * fourth to handle fuse notifications.
+	 */
+	fuse_opt_insert_arg(&args, 1, "-omax_threads=4");
+
 	if (fctx.debug) {
 		int	i;
 


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

* [PATCH 16/19] fuse2fs: improve error handling behaviors
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (14 preceding siblings ...)
  2025-11-06 22:35   ` [PATCH 15/19] fuse2fs: constrain worker thread count Darrick J. Wong
@ 2025-11-06 22:35   ` Darrick J. Wong
  2025-11-06 22:35   ` [PATCH 17/19] fuse2fs: fix link count overflows on dir_nlink filesystems Darrick J. Wong
                     ` (2 subsequent siblings)
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Make the behavior of fuse2fs on filesystem errors consistent with what
the kernel driver does.  Sort of.  We can't panic the kernel, but we can
abort the server, which leaves a dead mount.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 misc/fuse2fs.1.in |    6 ++++++
 misc/fuse2fs.c    |   42 +++++++++++++++++++++++++++++++++++++++---
 2 files changed, 45 insertions(+), 3 deletions(-)


diff --git a/misc/fuse2fs.1.in b/misc/fuse2fs.1.in
index b18b7f3464bc74..6acfa092851292 100644
--- a/misc/fuse2fs.1.in
+++ b/misc/fuse2fs.1.in
@@ -60,6 +60,12 @@ .SS "fuse2fs options:"
 \fB-o\fR dirsync
 Flush dirty metadata to disk after every directory update.
 .TP
+\fB-o\fR errors=continue
+ignore errors
+.TP
+\fB-o\fR errors=remount-ro
+stop allowing writes after errors
+.TP
 \fB-o\fR errors=panic
 dump core on error
 .TP
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index d890855df9c0f3..fd21f546db7fb1 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -224,7 +224,7 @@ struct fuse2fs {
 	int ro;
 	int debug;
 	int no_default_opts;
-	int panic_on_error;
+	int errors_behavior; /* actually an enum */
 	int minixdf;
 	int fakeroot;
 	int alloc_all_blocks;
@@ -4771,6 +4771,7 @@ enum {
 	FUSE2FS_HELPFULL,
 	FUSE2FS_CACHE_SIZE,
 	FUSE2FS_DIRSYNC,
+	FUSE2FS_ERRORS_BEHAVIOR,
 };
 
 #define FUSE2FS_OPT(t, p, v) { t, offsetof(struct fuse2fs, p), v }
@@ -4778,7 +4779,6 @@ enum {
 static struct fuse_opt fuse2fs_opts[] = {
 	FUSE2FS_OPT("ro",		ro,			1),
 	FUSE2FS_OPT("rw",		ro,			0),
-	FUSE2FS_OPT("errors=panic",	panic_on_error,		1),
 	FUSE2FS_OPT("minixdf",		minixdf,		1),
 	FUSE2FS_OPT("bsddf",		minixdf,		0),
 	FUSE2FS_OPT("fakeroot",		fakeroot,		1),
@@ -4798,6 +4798,7 @@ static struct fuse_opt fuse2fs_opts[] = {
 	FUSE_OPT_KEY("nodelalloc",	FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("cache_size=%s",	FUSE2FS_CACHE_SIZE),
 	FUSE_OPT_KEY("dirsync",		FUSE2FS_DIRSYNC),
+	FUSE_OPT_KEY("errors=%s",	FUSE2FS_ERRORS_BEHAVIOR),
 
 	FUSE_OPT_KEY("-V",             FUSE2FS_VERSION),
 	FUSE_OPT_KEY("--version",      FUSE2FS_VERSION),
@@ -4832,6 +4833,21 @@ static int fuse2fs_opt_proc(void *data, const char *arg,
 			return -1;
 		}
 
+		/* do not pass through to libfuse */
+		return 0;
+	case FUSE2FS_ERRORS_BEHAVIOR:
+		if (strcmp(arg + 7, "continue") == 0)
+			ff->errors_behavior = EXT2_ERRORS_CONTINUE;
+		else if (strcmp(arg + 7, "remount-ro") == 0)
+			ff->errors_behavior = EXT2_ERRORS_RO;
+		else if (strcmp(arg + 7, "panic") == 0)
+			ff->errors_behavior = EXT2_ERRORS_PANIC;
+		else {
+			fprintf(stderr, "%s: %s\n", arg,
+ _("unknown errors behavior."));
+			return -1;
+		}
+
 		/* do not pass through to libfuse */
 		return 0;
 	case FUSE2FS_IGNORED:
@@ -4859,6 +4875,8 @@ static int fuse2fs_opt_proc(void *data, const char *arg,
 	"                           allow_others,default_permissions,suid,dev\n"
 	"    -o directio            use O_DIRECT to read and write the disk\n"
 	"    -o cache_size=N[KMG]   use a disk cache of this size\n"
+	"    -o errors=             behavior when an error is encountered:\n"
+	"                           continue|remount-ro|panic\n"
 	"\n",
 			outargs->argv[0]);
 		if (key == FUSE2FS_HELPFULL) {
@@ -5226,6 +5244,9 @@ int main(int argc, char *argv[])
 		}
 	}
 
+	if (!fctx.errors_behavior)
+		fctx.errors_behavior = global_fs->super->s_errors;
+
 	/* Initialize generation counter */
 	get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
 
@@ -5492,8 +5513,23 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 	fs->super->s_error_count++;
 	ext2fs_mark_super_dirty(fs);
 	ext2fs_flush(fs);
-	if (ff->panic_on_error)
+	switch (ff->errors_behavior) {
+	case EXT2_ERRORS_CONTINUE:
+		err_printf(ff, "%s\n",
+ _("Continuing after errors; is this a good idea?"));
+		break;
+	case EXT2_ERRORS_RO:
+		if (fs->flags & EXT2_FLAG_RW)
+			err_printf(ff, "%s\n",
+ _("Remounting read-only due to errors."));
+		fs->flags &= ~EXT2_FLAG_RW;
+		break;
+	case EXT2_ERRORS_PANIC:
+		err_printf(ff, "%s\n",
+ _("Aborting filesystem mount due to errors."));
 		abort();
+		break;
+	}
 
 	return ret;
 }


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

* [PATCH 17/19] fuse2fs: fix link count overflows on dir_nlink filesystems
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (15 preceding siblings ...)
  2025-11-06 22:35   ` [PATCH 16/19] fuse2fs: improve error handling behaviors Darrick J. Wong
@ 2025-11-06 22:35   ` Darrick J. Wong
  2025-11-06 22:35   ` [PATCH 18/19] libsupport: add background thread manager Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 19/19] fuse2fs: implement MMP updates Darrick J. Wong
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4, linux-ext4

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

On a dir_nlink filesystem, a dir with more than 65000 subdirs ends up
with i_links_count (aka nlink) of 1.  libext2fs wraps around and does
the wrong thing, which may have caused a lot of havoc over the years.
The kernel actually knows how to do this properly (it freezes the link
count at 1 when it would overflow) so use the helpers we added in the
previous patch to make fuse2fs behave the same as the kernel.

This is a convenient time to fix the annoying behavior that one has to
call remove_inode twice to rmdir a directory, and actually check for
link count overflows when renaming or hardlinking files.

Found via ext4/045.

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 |   85 +++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 69 insertions(+), 16 deletions(-)


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index fd21f546db7fb1..b1cac46ddce567 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1798,21 +1798,34 @@ static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
 	dbg_printf(ff, "%s: put ino=%d links=%d\n", __func__, ino,
 		   inode.i_links_count);
 
-	switch (inode.i_links_count) {
-	case 0:
-		return 0; /* XXX: already done? */
-	case 1:
-		inode.i_links_count--;
+	if (S_ISDIR(inode.i_mode)) {
+		/*
+		 * Caller should have checked that this is an empty directory
+		 * before starting the unlink process.  nlink is usually 2, but
+		 * it could be 1 if this dir ever had more than 65000 subdirs.
+		 * Zero the link count.
+		 */
+		if (!ext2fs_dir_link_empty(EXT2_INODE(&inode)))
+			return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+		inode.i_links_count = 0;
 		ext2fs_set_dtime(fs, EXT2_INODE(&inode));
-		break;
-	default:
+	} else {
+		/*
+		 * Any other file type can be hardlinked, so all we need to do
+		 * is decrement the nlink.
+		 */
+		if (inode.i_links_count == 0)
+			return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
 		inode.i_links_count--;
+		if (!inode.i_links_count)
+			ext2fs_set_dtime(fs, EXT2_INODE(&inode));
 	}
 
 	ret = update_ctime(fs, ino, &inode);
 	if (ret)
 		return ret;
 
+	/* Still linked?  Leave it be. */
 	if (inode.i_links_count)
 		goto write_out;
 
@@ -1964,10 +1977,6 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 	}
 
 	ret = fuse2fs_unlink(ff, path, &parent);
-	if (ret)
-		goto out;
-	/* Directories have to be "removed" twice. */
-	ret = remove_inode(ff, child);
 	if (ret)
 		goto out;
 	ret = remove_inode(ff, child);
@@ -1982,8 +1991,7 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 			ret = translate_error(fs, rds.parent, err);
 			goto out;
 		}
-		if (inode.i_links_count > 1)
-			inode.i_links_count--;
+		ext2fs_dec_nlink(EXT2_INODE(&inode));
 		ret = update_mtime(fs, rds.parent, &inode);
 		if (ret)
 			goto out;
@@ -2149,6 +2157,41 @@ static int update_dotdot_helper(ext2_ino_t dir EXT2FS_ATTR((unused)),
 	return 0;
 }
 
+/*
+ * If we're moving a directory, make sure that the new parent of that directory
+ * can handle the nlink bump.
+ */
+static int fuse2fs_check_from_dir_nlink(struct fuse2fs *ff, ext2_ino_t from_ino,
+					ext2_ino_t to_ino,
+					ext2_ino_t from_dir_ino,
+					ext2_ino_t to_dir_ino)
+{
+	struct ext2_inode_large inode;
+	errcode_t err;
+
+	err = fuse2fs_read_inode(ff->fs, from_ino, &inode);
+	if (err)
+		return translate_error(ff->fs, from_ino, err);
+
+	if (!S_ISDIR(inode.i_mode))
+		return 0;
+
+	if (to_ino != 0)
+		return 0;
+
+	if (to_dir_ino == from_dir_ino)
+		return 0;
+
+	err = fuse2fs_read_inode(ff->fs, to_dir_ino, &inode);
+	if (err)
+		return translate_error(ff->fs, from_ino, err);
+
+	if (ext2fs_dir_link_max(ff->fs, &inode))
+		return -EMLINK;
+
+	return 0;
+}
+
 static int op_rename(const char *from, const char *to
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, unsigned int flags EXT2FS_ATTR((unused))
@@ -2275,6 +2318,11 @@ static int op_rename(const char *from, const char *to
 	if (ret)
 		goto out2;
 
+	ret = fuse2fs_check_from_dir_nlink(ff, from_ino, to_ino, from_dir_ino,
+					   to_dir_ino);
+	if (ret)
+		goto out2;
+
 	/* If the target exists, unlink it first */
 	if (to_ino != 0) {
 		err = ext2fs_read_inode(fs, to_ino, &inode);
@@ -2337,7 +2385,7 @@ static int op_rename(const char *from, const char *to
 			ret = translate_error(fs, from_dir_ino, err);
 			goto out2;
 		}
-		inode.i_links_count--;
+		ext2fs_dec_nlink(&inode);
 		err = ext2fs_write_inode(fs, from_dir_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, from_dir_ino, err);
@@ -2350,7 +2398,7 @@ static int op_rename(const char *from, const char *to
 			ret = translate_error(fs, to_dir_ino, err);
 			goto out2;
 		}
-		inode.i_links_count++;
+		ext2fs_inc_nlink(fs, &inode);
 		err = ext2fs_write_inode(fs, to_dir_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, to_dir_ino, err);
@@ -2453,7 +2501,12 @@ static int op_link(const char *src, const char *dest)
 	if (ret)
 		goto out2;
 
-	inode.i_links_count++;
+	if (ext2fs_dir_link_max(ff->fs, &inode)) {
+		ret = -EMLINK;
+		goto out2;
+	}
+
+	ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
 	ret = update_ctime(fs, ino, &inode);
 	if (ret)
 		goto out2;


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

* [PATCH 18/19] libsupport: add background thread manager
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (16 preceding siblings ...)
  2025-11-06 22:35   ` [PATCH 17/19] fuse2fs: fix link count overflows on dir_nlink filesystems Darrick J. Wong
@ 2025-11-06 22:35   ` Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 19/19] fuse2fs: implement MMP updates Darrick J. Wong
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:35 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Add some simple code to manage a background thread that wakes up
periodically.  This will be needed for MMP in fuse2fs, among other
things.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/bthread.h   |   27 ++++++
 lib/support/Makefile.in |    6 +
 lib/support/bthread.c   |  201 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 233 insertions(+), 1 deletion(-)
 create mode 100644 lib/support/bthread.h
 create mode 100644 lib/support/bthread.c


diff --git a/lib/support/bthread.h b/lib/support/bthread.h
new file mode 100644
index 00000000000000..cb29a655b815e5
--- /dev/null
+++ b/lib/support/bthread.h
@@ -0,0 +1,27 @@
+/*
+ * bthread.h - Background thread manager
+ *
+ * Copyright (C) 2025 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#ifndef __BTHREAD_H__
+#define __BTHREAD_H__
+
+typedef void (*bthread_fn_t)(void *data);
+struct bthread;
+
+int bthread_create(const char *name, bthread_fn_t fn, void *data,
+		   unsigned int period, struct bthread **btp);
+void bthread_destroy(struct bthread **btp);
+
+int bthread_start(struct bthread *bt);
+void bthread_stop(struct bthread *bt);
+
+int bthread_cancel(struct bthread *bt);
+int bthread_cancelled(struct bthread *bt);
+
+#endif /* __BTHREAD_H__ */
diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
index 3f26cd30172f51..6383816fd99cd4 100644
--- a/lib/support/Makefile.in
+++ b/lib/support/Makefile.in
@@ -13,7 +13,8 @@ MKDIR_P = @MKDIR_P@
 
 all::
 
-OBJS=		cstring.o \
+OBJS=		bthread.o \
+		cstring.o \
 		mkquota.o \
 		plausible.o \
 		profile.o \
@@ -28,6 +29,7 @@ OBJS=		cstring.o \
 		devname.o
 
 SRCS=		$(srcdir)/argv_parse.c \
+		$(srcdir)/bthread.c \
 		$(srcdir)/cstring.c \
 		$(srcdir)/mkquota.c \
 		$(srcdir)/parse_qtype.c \
@@ -108,6 +110,8 @@ $(OBJS):
 #
 argv_parse.o: $(srcdir)/argv_parse.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/argv_parse.h
+bthread.o: $(srcdir)/bthread.c $(top_builddir)/lib/config.h \
+ $(srcdir)/bthread.h
 cstring.o: $(srcdir)/cstring.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/cstring.h
 mkquota.o: $(srcdir)/mkquota.c $(top_builddir)/lib/config.h \
diff --git a/lib/support/bthread.c b/lib/support/bthread.c
new file mode 100644
index 00000000000000..f59fde976920f9
--- /dev/null
+++ b/lib/support/bthread.c
@@ -0,0 +1,201 @@
+/*
+ * bthread.c - Background thread manager
+ *
+ * Copyright (C) 2025 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "support/bthread.h"
+
+enum bthread_state {
+	/* waiting to be put in the running state */
+	BT_WAITING,
+	/* running */
+	BT_RUNNING,
+	/* cancelled */
+	BT_CANCELLED,
+};
+
+struct bthread {
+	enum bthread_state state;
+	pthread_t thread;
+	pthread_mutex_t lock;
+	pthread_cond_t cond;
+	bthread_fn_t fn;
+	void *data;
+	unsigned int period; /* seconds */
+	int can_join:1;
+};
+
+/* Wait for a signal or for the periodic interval */
+static inline int bthread_wait(struct bthread *bt)
+{
+	struct timespec ts;
+
+	clock_gettime(CLOCK_REALTIME, &ts);
+	ts.tv_sec += bt->period;
+	return pthread_cond_timedwait(&bt->cond, &bt->lock, &ts);
+}
+
+static void *bthread_run(void *arg)
+{
+	struct bthread *bt = arg;
+	int ret;
+
+	while (1) {
+		pthread_mutex_lock(&bt->lock);
+		ret = bthread_wait(bt);
+		switch (bt->state) {
+		case BT_WAITING:
+			/* waiting to be runnable, go around again */
+			pthread_mutex_unlock(&bt->lock);
+			break;
+		case BT_RUNNING:
+			/* running; call our function if we timed out */
+			pthread_mutex_unlock(&bt->lock);
+			if (ret == ETIMEDOUT)
+				bt->fn(bt->data);
+			break;
+		case BT_CANCELLED:
+			/* exit if we're cancelled */
+			pthread_mutex_unlock(&bt->lock);
+			return NULL;
+		}
+	}
+
+	return NULL;
+}
+
+/* Create background thread and have it wait to be started */
+int bthread_create(const char *name,  bthread_fn_t fn, void *data,
+		   unsigned int period, struct bthread **btp)
+{
+	struct bthread *bt;
+	int error;
+
+	if (!period)
+		return EINVAL;
+
+	bt = calloc(1, sizeof(struct bthread));
+	if (!bt)
+		return ENOMEM;
+	bt->state = BT_WAITING;
+	bt->fn = fn;
+	bt->data = data;
+	bt->period = period;
+	bt->can_join = 1;
+
+	bt->lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
+	bt->cond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
+
+	error = pthread_create(&bt->thread, NULL, bthread_run, bt);
+	if (error)
+		goto out_cond;
+
+	pthread_setname_np(bt->thread, name);
+
+	*btp = bt;
+	return 0;
+
+out_cond:
+	pthread_cond_destroy(&bt->cond);
+	pthread_mutex_destroy(&bt->lock);
+	free(bt);
+	return error;
+}
+
+/* Stop the thread (if running) and tear everything down */
+void bthread_destroy(struct bthread **btp)
+{
+	struct bthread *bt = *btp;
+
+	if (bt) {
+		bthread_stop(bt);
+
+		pthread_cond_destroy(&bt->cond);
+		pthread_mutex_destroy(&bt->lock);
+
+		free(bt);
+	}
+
+	*btp = NULL;
+}
+
+/* Start background thread, put it in waiting state */
+int bthread_start(struct bthread *bt)
+{
+	int err;
+
+	pthread_mutex_lock(&bt->lock);
+	bt->state = BT_RUNNING;
+	err = pthread_cond_signal(&bt->cond);
+	pthread_mutex_unlock(&bt->lock);
+
+	return err;
+}
+
+/* Has this thread been cancelled? */
+int bthread_cancelled(struct bthread *bt)
+{
+	int ret;
+
+	pthread_mutex_lock(&bt->lock);
+	ret = bt->state == BT_CANCELLED;
+	pthread_mutex_unlock(&bt->lock);
+
+	return ret;
+}
+
+/* Ask the thread to cancel itself, but don't wait */
+int bthread_cancel(struct bthread *bt)
+{
+	int err = 0;
+
+	pthread_mutex_lock(&bt->lock);
+	switch (bt->state) {
+	case BT_CANCELLED:
+		break;
+	case BT_WAITING:
+	case BT_RUNNING:
+		bt->state = BT_CANCELLED;
+		err = pthread_cond_signal(&bt->cond);
+		break;
+	}
+	pthread_mutex_unlock(&bt->lock);
+
+	return err;
+}
+
+/* Ask the thread to cancel itself and wait for it */
+void bthread_stop(struct bthread *bt)
+{
+	int need_join = 0;
+
+	pthread_mutex_lock(&bt->lock);
+	switch (bt->state) {
+	case BT_CANCELLED:
+		need_join = bt->can_join;
+		break;
+	case BT_WAITING:
+	case BT_RUNNING:
+		bt->state = BT_CANCELLED;
+		need_join = 1;
+		pthread_cond_signal(&bt->cond);
+		break;
+	}
+	if (need_join)
+		bt->can_join = 0;
+	pthread_mutex_unlock(&bt->lock);
+
+	if (need_join)
+		pthread_join(bt->thread, NULL);
+}


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

* [PATCH 19/19] fuse2fs: implement MMP updates
  2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
                     ` (17 preceding siblings ...)
  2025-11-06 22:35   ` [PATCH 18/19] libsupport: add background thread manager Darrick J. Wong
@ 2025-11-06 22:36   ` Darrick J. Wong
  18 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Periodically rewrite the MMP block while we're mounted like the Linux
driver does, so that other potential users don't see EXT4_MMP_SEQ_FSCK
and erroneously report that the ext4 filesystem is being fscked.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index b1cac46ddce567..589599f91b4390 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -46,6 +46,7 @@
 #include "ext2fs/ext2fs.h"
 #include "ext2fs/ext2_fs.h"
 #include "ext2fs/ext2fsP.h"
+#include "support/bthread.h"
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 # define FUSE_PLATFORM_OPTS	""
 #else
@@ -241,6 +242,10 @@ struct fuse2fs {
 	unsigned int next_generation;
 	unsigned long long cache_size;
 	char *lockfile;
+#ifdef CONFIG_MMP
+	struct bthread *mmp_thread;
+	unsigned int mmp_update_interval;
+#endif
 };
 
 #define FUSE2FS_CHECK_MAGIC(fs, ptr, num) do {if ((ptr)->magic != (num)) \
@@ -466,6 +471,133 @@ static inline errcode_t fuse2fs_write_inode(ext2_filsys fs, ext2_ino_t ino,
 				       sizeof(*inode));
 }
 
+#ifdef CONFIG_MMP
+static bool fuse2fs_mmp_wanted(const struct fuse2fs *ff)
+{
+	ext2_filsys fs = ff->fs;
+
+	if (!fs || !ext2fs_has_feature_mmp(fs->super) ||
+	    !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
+		return false;
+	return true;
+}
+
+static int fuse2fs_mmp_touch(struct fuse2fs *ff, bool immediate)
+{
+	ext2_filsys fs = ff->fs;
+	struct mmp_struct *mmp = fs->mmp_buf;
+	struct mmp_struct *mmp_cmp = fs->mmp_cmp;
+	struct timeval tv;
+	errcode_t retval = 0;
+
+	gettimeofday(&tv, 0);
+	if (!immediate &&
+	    tv.tv_sec - fs->mmp_last_written < ff->mmp_update_interval)
+		return 0;
+
+	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
+	if (retval)
+		return translate_error(fs, 0, retval);
+
+	if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
+		return translate_error(fs, 0, EXT2_ET_MMP_CHANGE_ABORT);
+
+	/*
+	 * Believe it or not, ext2fs_mmp_read actually overwrites fs->mmp_cmp
+	 * and leaves fs->mmp_buf untouched.  Hence we copy mmp_cmp into
+	 * mmp_buf, update mmp_buf, and write mmp_buf out to disk.
+	 */
+	memcpy(mmp, mmp_cmp, sizeof(*mmp_cmp));
+	mmp->mmp_time = tv.tv_sec;
+	mmp->mmp_seq = ext2fs_mmp_new_seq();
+
+	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
+	if (retval)
+		return translate_error(fs, 0, retval);
+
+	return 0;
+}
+
+static void fuse2fs_mmp_bthread(void *data)
+{
+	struct fuse2fs *ff = data;
+
+	pthread_mutex_lock(&ff->bfl);
+	if (fuse2fs_mmp_wanted(ff) && !bthread_cancelled(ff->mmp_thread))
+		fuse2fs_mmp_touch(ff, false);
+	pthread_mutex_unlock(&ff->bfl);
+}
+
+static void fuse2fs_mmp_start(struct fuse2fs *ff)
+{
+	int ret;
+
+	if (!fuse2fs_mmp_wanted(ff))
+		return;
+
+	ret = bthread_create("fuse2fs_mmp", fuse2fs_mmp_bthread, ff,
+			     ff->mmp_update_interval, &ff->mmp_thread);
+	if (ret) {
+		err_printf(ff, "MMP: %s.\n", error_message(ret));
+		return;
+	}
+
+	ret = bthread_start(ff->mmp_thread);
+	if (ret)
+		err_printf(ff, "MMP: %s.\n", error_message(ret));
+}
+
+static void fuse2fs_mmp_cancel(struct fuse2fs *ff)
+{
+	if (ff->mmp_thread)
+		bthread_cancel(ff->mmp_thread);
+}
+
+static void fuse2fs_mmp_config(struct fuse2fs *ff)
+{
+	ext2_filsys fs = ff->fs;
+	struct mmp_struct *mmp_s = fs->mmp_buf;
+	unsigned int mmp_update_interval = fs->super->s_mmp_update_interval;
+
+	if (!ext2fs_has_feature_mmp(fs->super) ||
+	    !(fs->flags & EXT2_FLAG_RW) ||
+	    (fs->flags & EXT2_FLAG_SKIP_MMP))
+		return;
+
+	/*
+	 * If update_interval in MMP block is larger, use that instead of
+	 * update_interval from the superblock.
+	 */
+	if (mmp_s->mmp_check_interval > mmp_update_interval)
+		mmp_update_interval = mmp_s->mmp_check_interval;
+
+	/* Clamp to the relevant(?) interval values */
+	if (mmp_update_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
+		mmp_update_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
+	if (mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
+		mmp_update_interval = EXT4_MMP_MAX_UPDATE_INTERVAL;
+
+	ff->mmp_update_interval = mmp_update_interval;
+
+	/*
+	 * libext2fs writes EXT4_MMP_SEQ_FSCK after mounting, so we need to
+	 * update it immediately so that it doesn't look like another node is
+	 * actually running fsck.
+	 */
+	fuse2fs_mmp_touch(ff, true);
+}
+
+static void fuse2fs_mmp_destroy(struct fuse2fs *ff)
+{
+	bthread_destroy(&ff->mmp_thread);
+}
+#else
+# define fuse2fs_mmp_start(...)		((void)0)
+# define fuse2fs_mmp_cancel(...)	((void)0)
+# define fuse2fs_mmp_config(...)	((void)0)
+# define fuse2fs_mmp_destroy(...)	((void)0)
+#endif
+
 static void get_now(struct timespec *now)
 {
 #ifdef CLOCK_REALTIME
@@ -1052,6 +1184,13 @@ static void *op_init(struct fuse_conn_info *conn
 	if (global_fs->flags & EXT2_FLAG_RW)
 		fuse2fs_read_bitmaps(ff);
 
+	/*
+	 * Background threads must be started from op_init because libfuse
+	 * might daemonize us in fuse_main() by forking, and threads are not
+	 * conveyed to the new child process.
+	 */
+	fuse2fs_mmp_start(ff);
+
 	return ff;
 }
 
@@ -5018,6 +5157,7 @@ int main(int argc, char *argv[])
 	memset(&fctx, 0, sizeof(fctx));
 	fctx.magic = FUSE2FS_MAGIC;
 	fctx.logfd = -1;
+	fctx.bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
 
 	ret = fuse_opt_parse(&args, &fctx, fuse2fs_opts, fuse2fs_opt_proc);
 	if (ret)
@@ -5193,6 +5333,8 @@ int main(int argc, char *argv[])
 	fctx.blocklog = u_log2(fctx.fs->blocksize);
 	fctx.blockmask = fctx.fs->blocksize - 1;
 
+	fuse2fs_mmp_config(&fctx);
+
 	if (!fctx.cache_size)
 		fctx.cache_size = default_cache_size();
 	if (fctx.cache_size) {
@@ -5351,10 +5493,7 @@ int main(int argc, char *argv[])
 		fflush(stdout);
 	}
 
-	pthread_mutex_init(&fctx.bfl, NULL);
 	ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
-	pthread_mutex_destroy(&fctx.bfl);
-
 	switch(ret) {
 	case 0:
 		/* success */
@@ -5389,6 +5528,7 @@ int main(int argc, char *argv[])
  _("Mount failed while opening filesystem.  Check dmesg(1) for details."));
 		fflush(orig_stderr);
 	}
+	fuse2fs_mmp_destroy(&fctx);
 	if (global_fs) {
 		err = ext2fs_close_free(&global_fs);
 		if (err)
@@ -5405,6 +5545,7 @@ int main(int argc, char *argv[])
 	}
 	if (fctx.device)
 		free(fctx.device);
+	pthread_mutex_destroy(&fctx.bfl);
 	fuse_opt_free_args(&args);
 	return ret;
 }
@@ -5575,6 +5716,7 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 		if (fs->flags & EXT2_FLAG_RW)
 			err_printf(ff, "%s\n",
  _("Remounting read-only due to errors."));
+		fuse2fs_mmp_cancel(ff);
 		fs->flags &= ~EXT2_FLAG_RW;
 		break;
 	case EXT2_ERRORS_PANIC:


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

* [PATCH 1/9] fuse2fs: rework FUSE2FS_CHECK_CONTEXT not to rely on global_fs
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
@ 2025-11-06 22:36   ` Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 2/9] fuse2fs: rework checking file handles Darrick J. Wong
                     ` (7 subsequent siblings)
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

If the fuse2fs object that libfuse hands to us has the wrong magic
number, something is clearly wrong.  In that case, we don't really want
to rely on the global_fs variable actually pointing to a valid
ext2_filsys object because something could be wrong there too.  Instead,
try to emit an error on stderr and just bail out.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 589599f91b4390..e973f75736a245 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -252,9 +252,23 @@ struct fuse2fs {
 	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_FILESYSTEM_CORRUPTED); \
-} while (0)
+#define __FUSE2FS_CHECK_CONTEXT(ff, retcode) \
+	do { \
+		if ((ff) == NULL || (ff)->magic != FUSE2FS_MAGIC) { \
+			fprintf(stderr, \
+				"FUSE2FS: Corrupt in-memory data at %s:%d!\n", \
+				__func__, __LINE__); \
+			fflush(stderr); \
+			retcode; \
+		} \
+	} while (0)
+
+#define FUSE2FS_CHECK_CONTEXT(ff) \
+	__FUSE2FS_CHECK_CONTEXT((ff), return -EUCLEAN)
+#define FUSE2FS_CHECK_CONTEXT_DESTROY(ff) \
+	__FUSE2FS_CHECK_CONTEXT((ff), return)
+#define FUSE2FS_CHECK_CONTEXT_INIT(ff) \
+	__FUSE2FS_CHECK_CONTEXT((ff), abort())
 
 static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 			     const char *file, int line);
@@ -963,10 +977,7 @@ static void op_destroy(void *p EXT2FS_ATTR((unused)))
 	ext2_filsys fs;
 	errcode_t err;
 
-	if (ff->magic != FUSE2FS_MAGIC) {
-		translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC);
-		return;
-	}
+	FUSE2FS_CHECK_CONTEXT_DESTROY(ff);
 
 	pthread_mutex_lock(&ff->bfl);
 	fs = ff->fs;
@@ -1139,10 +1150,7 @@ static void *op_init(struct fuse_conn_info *conn
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
 	ext2_filsys fs;
 
-	if (ff->magic != FUSE2FS_MAGIC) {
-		translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC);
-		return NULL;
-	}
+	FUSE2FS_CHECK_CONTEXT_INIT(ff);
 
 	/*
 	 * Configure logging a second time, because libfuse might have
@@ -5602,16 +5610,13 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 	case EXT2_ET_EA_KEY_NOT_FOUND:
 		ret = -ENODATA;
 		break;
-	/* Sometimes fuse returns a garbage file handle pointer to us... */
-	case EXT2_ET_MAGIC_EXT2_FILE:
-		ret = -EFAULT;
-		break;
 	case EXT2_ET_UNIMPLEMENTED:
 		ret = -EOPNOTSUPP;
 		break;
 	case EXT2_ET_RO_FILSYS:
 		ret = -EROFS;
 		break;
+	case EXT2_ET_MAGIC_EXT2_FILE:
 	case EXT2_ET_MAGIC_EXT2FS_FILSYS:
 	case EXT2_ET_MAGIC_BADBLOCKS_LIST:
 	case EXT2_ET_MAGIC_BADBLOCKS_ITERATE:


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

* [PATCH 2/9] fuse2fs: rework checking file handles
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 1/9] fuse2fs: rework FUSE2FS_CHECK_CONTEXT not to rely on global_fs Darrick J. Wong
@ 2025-11-06 22:36   ` Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 3/9] fuse2fs: rework fallocate file handle extraction Darrick J. Wong
                     ` (6 subsequent siblings)
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

We only use FUSE2FS_CHECK_MAGIC for one thing -- to check the magic
number of file handles.  Make the whole macro more specific to file
handles, and move it to the top of each function so that we don't take
locks or any other silly stuff like that.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index e973f75736a245..da37c095c7a304 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -248,9 +248,16 @@ struct fuse2fs {
 #endif
 };
 
-#define FUSE2FS_CHECK_MAGIC(fs, ptr, num) do {if ((ptr)->magic != (num)) \
-	return translate_error((fs), 0, EXT2_ET_FILESYSTEM_CORRUPTED); \
-} while (0)
+#define FUSE2FS_CHECK_HANDLE(ff, fh) \
+	do { \
+		if ((fh) == NULL || (fh)->magic != FUSE2FS_FILE_MAGIC) { \
+			fprintf(stderr, \
+				"FUSE2FS: Corrupt in-memory file handle at %s:%d!\n", \
+				__func__, __LINE__); \
+			fflush(stderr); \
+			return -EUCLEAN; \
+		} \
+	} while (0)
 
 #define __FUSE2FS_CHECK_CONTEXT(ff, retcode) \
 	do { \
@@ -3153,8 +3160,8 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
 		   (intmax_t) offset, len);
 	pthread_mutex_lock(&ff->bfl);
@@ -3210,8 +3217,8 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
 		   (intmax_t) offset, (intmax_t) len);
 	pthread_mutex_lock(&ff->bfl);
@@ -3280,8 +3287,8 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	pthread_mutex_lock(&ff->bfl);
 
@@ -3314,8 +3321,8 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	/* For now, flush everything, even if it's slow */
 	pthread_mutex_lock(&ff->bfl);
@@ -3856,8 +3863,8 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	i.fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(i.fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d offset=%llu\n", __func__, fh->ino,
 			(unsigned long long)offset);
 	pthread_mutex_lock(&ff->bfl);
@@ -4052,8 +4059,8 @@ static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, fh->ino,
 		   (intmax_t) len);
 	pthread_mutex_lock(&ff->bfl);
@@ -4104,8 +4111,8 @@ static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	pthread_mutex_lock(&ff->bfl);
 	ret = stat_inode(fs, fh->ino, statbuf);
@@ -4209,7 +4216,7 @@ static int ioctl_getflags(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	errcode_t err;
 	struct ext2_inode_large inode;
 
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4229,7 +4236,7 @@ static int ioctl_setflags(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	__u32 flags = *(__u32 *)data;
 	struct fuse_context *ctxt = fuse_get_context();
 
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4260,7 +4267,7 @@ static int ioctl_getversion(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	errcode_t err;
 	struct ext2_inode_large inode;
 
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4280,7 +4287,7 @@ static int ioctl_setversion(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	__u32 generation = *(__u32 *)data;
 	struct fuse_context *ctxt = fuse_get_context();
 
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4334,7 +4341,7 @@ static int ioctl_fsgetxattr(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	struct fsxattr *fsx = data;
 	unsigned int inode_size;
 
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4407,7 +4414,7 @@ static int ioctl_fssetxattr(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	struct fsxattr *fsx = data;
 	unsigned int inode_size;
 
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4637,8 +4644,8 @@ static int fallocate_helper(struct fuse_file_info *fp, int mode, off_t offset,
 	int flags;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	start = FUSE2FS_B_TO_FSBT(ff, offset);
 	end = FUSE2FS_B_TO_FSBT(ff, offset + len - 1);
 	dbg_printf(ff, "%s: ino=%d mode=0x%x start=%llu end=%llu\n", __func__,
@@ -4771,8 +4778,8 @@ static int punch_helper(struct fuse_file_info *fp, int mode, off_t offset,
 	char *buf = NULL;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	fs = ff->fs;
-	FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
 	dbg_printf(ff, "%s: offset=%jd len=%jd\n", __func__,
 		   (intmax_t) offset, (intmax_t) len);
 


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

* [PATCH 3/9] fuse2fs: rework fallocate file handle extraction
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 1/9] fuse2fs: rework FUSE2FS_CHECK_CONTEXT not to rely on global_fs Darrick J. Wong
  2025-11-06 22:36   ` [PATCH 2/9] fuse2fs: rework checking file handles Darrick J. Wong
@ 2025-11-06 22:36   ` Darrick J. Wong
  2025-11-06 22:37   ` [PATCH 4/9] fuse2fs: consolidate file handle checking in op_ioctl Darrick J. Wong
                     ` (5 subsequent siblings)
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:36 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Move the context and file handle checking to op_fallocate so that we can
pass them to the alloc/punch/zero helpers.  This eliminates redundant
checking in the zero_range path.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index da37c095c7a304..07bb4cdb889c17 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4629,23 +4629,17 @@ static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
 # ifdef SUPPORT_FALLOCATE
-static int fallocate_helper(struct fuse_file_info *fp, int mode, off_t offset,
-			    off_t len)
+static int fuse2fs_allocate_range(struct fuse2fs *ff,
+				  struct fuse2fs_file_handle *fh, int mode,
+				  off_t offset, off_t len)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
-	ext2_filsys fs;
+	ext2_filsys fs = ff->fs;
 	struct ext2_inode_large inode;
 	blk64_t start, end;
 	__u64 fsize;
 	errcode_t err;
 	int flags;
 
-	FUSE2FS_CHECK_CONTEXT(ff);
-	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	start = FUSE2FS_B_TO_FSBT(ff, offset);
 	end = FUSE2FS_B_TO_FSBT(ff, offset + len - 1);
 	dbg_printf(ff, "%s: ino=%d mode=0x%x start=%llu end=%llu\n", __func__,
@@ -4764,22 +4758,16 @@ static errcode_t clean_block_edge(struct fuse2fs *ff, ext2_ino_t ino,
 	return io_channel_write_blk64(fs->io, blk, 1, *buf);
 }
 
-static int punch_helper(struct fuse_file_info *fp, int mode, off_t offset,
-			off_t len)
+static int fuse2fs_punch_range(struct fuse2fs *ff,
+			       struct fuse2fs_file_handle *fh, int mode,
+			       off_t offset, off_t len)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
-	ext2_filsys fs;
+	ext2_filsys fs = ff->fs;
 	struct ext2_inode_large inode;
 	blk64_t start, end;
 	errcode_t err;
 	char *buf = NULL;
 
-	FUSE2FS_CHECK_CONTEXT(ff);
-	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: offset=%jd len=%jd\n", __func__,
 		   (intmax_t) offset, (intmax_t) len);
 
@@ -4850,13 +4838,15 @@ static int punch_helper(struct fuse_file_info *fp, int mode, off_t offset,
 	return 0;
 }
 
-static int zero_helper(struct fuse_file_info *fp, int mode, off_t offset,
-		       off_t len)
+static int fuse2fs_zero_range(struct fuse2fs *ff,
+			      struct fuse2fs_file_handle *fh, int mode,
+			      off_t offset, off_t len)
 {
-	int ret = punch_helper(fp, mode | FL_KEEP_SIZE_FLAG, offset, len);
+	int ret = fuse2fs_punch_range(ff, fh, mode | FL_KEEP_SIZE_FLAG, offset,
+				      len);
 
 	if (!ret)
-		ret = fallocate_helper(fp, mode, offset, len);
+		ret = fuse2fs_allocate_range(ff, fh, mode, offset, len);
 	return ret;
 }
 
@@ -4866,24 +4856,29 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 {
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	ext2_filsys fs = ff->fs;
+	struct fuse2fs_file_handle *fh =
+		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	ext2_filsys fs;
 	int ret;
 
 	/* Catch unknown flags */
 	if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
 		return -EOPNOTSUPP;
 
+	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
 		goto out;
 	}
 	if (mode & FL_ZERO_RANGE_FLAG)
-		ret = zero_helper(fp, mode, offset, len);
+		ret = fuse2fs_zero_range(ff, fh, mode, offset, len);
 	else if (mode & FL_PUNCH_HOLE_FLAG)
-		ret = punch_helper(fp, mode, offset, len);
+		ret = fuse2fs_punch_range(ff, fh, mode, offset, len);
 	else
-		ret = fallocate_helper(fp, mode, offset, len);
+		ret = fuse2fs_allocate_range(ff, fh, mode, offset, len);
 out:
 	pthread_mutex_unlock(&ff->bfl);
 


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

* [PATCH 4/9] fuse2fs: consolidate file handle checking in op_ioctl
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (2 preceding siblings ...)
  2025-11-06 22:36   ` [PATCH 3/9] fuse2fs: rework fallocate file handle extraction Darrick J. Wong
@ 2025-11-06 22:37   ` Darrick J. Wong
  2025-11-06 22:37   ` [PATCH 5/9] fuse2fs: move fs assignment closer to locking the bfl Darrick J. Wong
                     ` (4 subsequent siblings)
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Reduce code size by checking the file handle in op_ioctl dispatch
instead of every single ioctl implementation.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 07bb4cdb889c17..171eff246e272c 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -4216,7 +4216,6 @@ static int ioctl_getflags(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	errcode_t err;
 	struct ext2_inode_large inode;
 
-	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4236,7 +4235,6 @@ static int ioctl_setflags(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	__u32 flags = *(__u32 *)data;
 	struct fuse_context *ctxt = fuse_get_context();
 
-	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4267,7 +4265,6 @@ static int ioctl_getversion(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	errcode_t err;
 	struct ext2_inode_large inode;
 
-	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4287,7 +4284,6 @@ static int ioctl_setversion(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	__u32 generation = *(__u32 *)data;
 	struct fuse_context *ctxt = fuse_get_context();
 
-	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4341,7 +4337,6 @@ static int ioctl_fsgetxattr(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	struct fsxattr *fsx = data;
 	unsigned int inode_size;
 
-	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4414,7 +4409,6 @@ static int ioctl_fssetxattr(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	struct fsxattr *fsx = data;
 	unsigned int inode_size;
 
-	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4557,6 +4551,7 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
+	FUSE2FS_CHECK_HANDLE(ff, fh);
 	pthread_mutex_lock(&ff->bfl);
 	switch ((unsigned long) cmd) {
 #ifdef SUPPORT_I_FLAGS


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

* [PATCH 5/9] fuse2fs: move fs assignment closer to locking the bfl
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (3 preceding siblings ...)
  2025-11-06 22:37   ` [PATCH 4/9] fuse2fs: consolidate file handle checking in op_ioctl Darrick J. Wong
@ 2025-11-06 22:37   ` Darrick J. Wong
  2025-11-06 22:37   ` [PATCH 6/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (3 subsequent siblings)
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Move the "fs = ff->fs" statements closer to where we take the BFL so
that we can refactor them easily in the next patch.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 171eff246e272c..63c1227faa0d7a 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1338,8 +1338,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: path=%s\n", __func__, path);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
 	if (err || ino == 0) {
@@ -1596,7 +1596,6 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
 		   (unsigned int)dev);
 	temp_path = strdup(path);
@@ -1613,6 +1612,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 	a = *node_name;
 	*node_name = 0;
 
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_can_allocate(ff, 2)) {
 		ret = -ENOSPC;
@@ -1728,7 +1728,6 @@ static int op_mkdir(const char *path, mode_t mode)
 	int parent_sgid;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
 	temp_path = strdup(path);
 	if (!temp_path) {
@@ -1744,6 +1743,7 @@ static int op_mkdir(const char *path, mode_t mode)
 	a = *node_name;
 	*node_name = 0;
 
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
@@ -2191,7 +2191,6 @@ static int op_symlink(const char *src, const char *dest)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: symlink %s to %s\n", __func__, src, dest);
 	temp_path = strdup(dest);
 	if (!temp_path) {
@@ -2207,6 +2206,7 @@ static int op_symlink(const char *src, const char *dest)
 	a = *node_name;
 	*node_name = 0;
 
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
@@ -2371,8 +2371,8 @@ static int op_rename(const char *from, const char *to
 #endif
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_can_allocate(ff, 5)) {
 		ret = -ENOSPC;
@@ -2605,7 +2605,6 @@ static int op_link(const char *src, const char *dest)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: src=%s dest=%s\n", __func__, src, dest);
 	temp_path = strdup(dest);
 	if (!temp_path) {
@@ -2621,6 +2620,7 @@ static int op_link(const char *src, const char *dest)
 	a = *node_name;
 	*node_name = 0;
 
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_can_allocate(ff, 2)) {
 		ret = -ENOSPC;
@@ -3161,9 +3161,9 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
 		   (intmax_t) offset, len);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
 	if (err) {
@@ -3218,9 +3218,9 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
 		   (intmax_t) offset, (intmax_t) len);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
@@ -3288,8 +3288,8 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 
 	if ((fp->flags & O_SYNC) &&
@@ -3322,10 +3322,10 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	fs = ff->fs;
+	pthread_mutex_lock(&ff->bfl);
 	/* For now, flush everything, even if it's slow */
-	pthread_mutex_lock(&ff->bfl);
 	if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
 		err = ext2fs_flush2(fs, 0);
 		if (err)
@@ -3864,9 +3864,9 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	i.fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d offset=%llu\n", __func__, fh->ino,
 			(unsigned long long)offset);
+	i.fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	i.buf = buf;
 	i.func = fill_func;
@@ -3896,8 +3896,8 @@ static int op_access(const char *path, int mask)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: path=%s mask=0x%x\n", __func__, path, mask);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
 	if (err || ino == 0) {
@@ -3929,7 +3929,6 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
 	temp_path = strdup(path);
 	if (!temp_path) {
@@ -3945,6 +3944,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	a = *node_name;
 	*node_name = 0;
 
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
@@ -4060,9 +4060,9 @@ static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, fh->ino,
 		   (intmax_t) len);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
@@ -4112,8 +4112,8 @@ static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	fs = ff->fs;
 	pthread_mutex_lock(&ff->bfl);
 	ret = stat_inode(fs, fh->ino, statbuf);
 	pthread_mutex_unlock(&ff->bfl);


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

* [PATCH 6/9] fuse2fs: clean up operation startup
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (4 preceding siblings ...)
  2025-11-06 22:37   ` [PATCH 5/9] fuse2fs: move fs assignment closer to locking the bfl Darrick J. Wong
@ 2025-11-06 22:37   ` Darrick J. Wong
  2025-11-06 22:37   ` [PATCH 7/9] fuse2fs: clean up operation completion Darrick J. Wong
                     ` (2 subsequent siblings)
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Create a helper to take the BFL and give us a reference to the
ext2_filsys that we're protecting with the BFL.  This eliminates a
theoretical race with any code that sets or clears fuse2fs:fs.  But
really it just cuts down on the boilerplate.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 63c1227faa0d7a..3baeec67c2b4b7 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -492,6 +492,12 @@ static inline errcode_t fuse2fs_write_inode(ext2_filsys fs, ext2_ino_t ino,
 				       sizeof(*inode));
 }
 
+static inline ext2_filsys fuse2fs_start(struct fuse2fs *ff)
+{
+	pthread_mutex_lock(&ff->bfl);
+	return ff->fs;
+}
+
 #ifdef CONFIG_MMP
 static bool fuse2fs_mmp_wanted(const struct fuse2fs *ff)
 {
@@ -543,7 +549,7 @@ static void fuse2fs_mmp_bthread(void *data)
 {
 	struct fuse2fs *ff = data;
 
-	pthread_mutex_lock(&ff->bfl);
+	fuse2fs_start(ff);
 	if (fuse2fs_mmp_wanted(ff) && !bthread_cancelled(ff->mmp_thread))
 		fuse2fs_mmp_touch(ff, false);
 	pthread_mutex_unlock(&ff->bfl);
@@ -986,8 +992,7 @@ static void op_destroy(void *p EXT2FS_ATTR((unused)))
 
 	FUSE2FS_CHECK_CONTEXT_DESTROY(ff);
 
-	pthread_mutex_lock(&ff->bfl);
-	fs = ff->fs;
+	fs = fuse2fs_start(ff);
 
 	dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
 	if (fs->flags & EXT2_FLAG_RW) {
@@ -1314,8 +1319,7 @@ static int op_getattr(const char *path, struct stat *statbuf
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	ret = fuse2fs_file_ino(ff, path, fi, &ino);
 	if (ret)
 		goto out;
@@ -1339,8 +1343,7 @@ static int op_readlink(const char *path, char *buf, size_t len)
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	dbg_printf(ff, "%s: path=%s\n", __func__, path);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
 	if (err || ino == 0) {
 		ret = translate_error(fs, 0, err);
@@ -1612,8 +1615,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 	a = *node_name;
 	*node_name = 0;
 
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_can_allocate(ff, 2)) {
 		ret = -ENOSPC;
 		goto out2;
@@ -1743,8 +1745,7 @@ static int op_mkdir(const char *path, mode_t mode)
 	a = *node_name;
 	*node_name = 0;
 
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
@@ -2052,7 +2053,7 @@ static int op_unlink(const char *path)
 	int ret;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	pthread_mutex_lock(&ff->bfl);
+	fuse2fs_start(ff);
 	ret = __op_unlink(ff, path);
 	pthread_mutex_unlock(&ff->bfl);
 	return ret;
@@ -2171,7 +2172,7 @@ static int op_rmdir(const char *path)
 	int ret;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	pthread_mutex_lock(&ff->bfl);
+	fuse2fs_start(ff);
 	ret = __op_rmdir(ff, path);
 	pthread_mutex_unlock(&ff->bfl);
 	return ret;
@@ -2206,8 +2207,7 @@ static int op_symlink(const char *src, const char *dest)
 	a = *node_name;
 	*node_name = 0;
 
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
@@ -2372,8 +2372,7 @@ static int op_rename(const char *from, const char *to
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_can_allocate(ff, 5)) {
 		ret = -ENOSPC;
 		goto out;
@@ -2620,8 +2619,7 @@ static int op_link(const char *src, const char *dest)
 	a = *node_name;
 	*node_name = 0;
 
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_can_allocate(ff, 2)) {
 		ret = -ENOSPC;
 		goto out2;
@@ -2797,8 +2795,7 @@ static int op_chmod(const char *path, mode_t mode
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	ret = fuse2fs_file_ino(ff, path, fi, &ino);
 	if (ret)
 		goto out;
@@ -2869,8 +2866,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	ret = fuse2fs_file_ino(ff, path, fi, &ino);
 	if (ret)
 		goto out;
@@ -3009,7 +3005,7 @@ static int op_truncate(const char *path, off_t len
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	pthread_mutex_lock(&ff->bfl);
+	fuse2fs_start(ff);
 	ret = fuse2fs_file_ino(ff, path, fi, &ino);
 	if (ret)
 		goto out;
@@ -3139,7 +3135,7 @@ static int op_open(const char *path, struct fuse_file_info *fp)
 	int ret;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	pthread_mutex_lock(&ff->bfl);
+	fuse2fs_start(ff);
 	ret = __op_open(ff, path, fp);
 	pthread_mutex_unlock(&ff->bfl);
 	return ret;
@@ -3163,8 +3159,7 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
 		   (intmax_t) offset, len);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
 	if (err) {
 		ret = translate_error(fs, fh->ino, err);
@@ -3220,8 +3215,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
 		   (intmax_t) offset, (intmax_t) len);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
 		goto out;
@@ -3289,8 +3283,7 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 
 	if ((fp->flags & O_SYNC) &&
 	    fs_writeable(fs) &&
@@ -3323,8 +3316,7 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	/* For now, flush everything, even if it's slow */
 	if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
 		err = ext2fs_flush2(fs, 0);
@@ -3347,8 +3339,7 @@ static int op_statfs(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	dbg_printf(ff, "%s: path=%s\n", __func__, path);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	buf->f_bsize = fs->blocksize;
 	buf->f_frsize = 0;
 
@@ -3423,8 +3414,7 @@ static int op_getxattr(const char *path, const char *key, char *value,
 		return -ENODATA;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
@@ -3495,8 +3485,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
@@ -3578,8 +3567,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 		return -EINVAL;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
@@ -3676,8 +3664,7 @@ static int op_removexattr(const char *path, const char *key)
 		return -ENODATA;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
@@ -3866,8 +3853,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d offset=%llu\n", __func__, fh->ino,
 			(unsigned long long)offset);
-	i.fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	i.fs = fuse2fs_start(ff);
 	i.buf = buf;
 	i.func = fill_func;
 	err = ext2fs_dir_iterate2(i.fs, fh->ino, 0, NULL, op_readdir_iter, &i);
@@ -3897,8 +3883,7 @@ static int op_access(const char *path, int mask)
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	dbg_printf(ff, "%s: path=%s mask=0x%x\n", __func__, path, mask);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
 	if (err || ino == 0) {
 		ret = translate_error(fs, 0, err);
@@ -3944,8 +3929,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	a = *node_name;
 	*node_name = 0;
 
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
@@ -4062,8 +4046,7 @@ static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, fh->ino,
 		   (intmax_t) len);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
 		goto out;
@@ -4113,8 +4096,7 @@ static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	ret = stat_inode(fs, fh->ino, statbuf);
 	pthread_mutex_unlock(&ff->bfl);
 
@@ -4139,8 +4121,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	ret = fuse2fs_file_ino(ff, path, fi, &ino);
 	if (ret)
 		goto out;
@@ -4552,7 +4533,7 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	pthread_mutex_lock(&ff->bfl);
+	fuse2fs_start(ff);
 	switch ((unsigned long) cmd) {
 #ifdef SUPPORT_I_FLAGS
 	case EXT2_IOC_GETFLAGS:
@@ -4602,8 +4583,7 @@ static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
 	if (err) {
 		ret = translate_error(fs, 0, err);
@@ -4862,8 +4842,7 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = ff->fs;
-	pthread_mutex_lock(&ff->bfl);
+	fs = fuse2fs_start(ff);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
 		goto out;


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

* [PATCH 7/9] fuse2fs: clean up operation completion
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (5 preceding siblings ...)
  2025-11-06 22:37   ` [PATCH 6/9] fuse2fs: clean up operation startup Darrick J. Wong
@ 2025-11-06 22:37   ` Darrick J. Wong
  2025-11-06 22:38   ` [PATCH 8/9] fuse2fs: clean up more boilerplate Darrick J. Wong
  2025-11-06 22:38   ` [PATCH 9/9] fuse2fs: collect runtime of various operations Darrick J. Wong
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:37 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Create a helper to release the BFL and log any errors.  This cuts down
on the boilerplate.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 3baeec67c2b4b7..77d0eddbe805dc 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -498,6 +498,15 @@ static inline ext2_filsys fuse2fs_start(struct fuse2fs *ff)
 	return ff->fs;
 }
 
+static inline void __fuse2fs_finish(struct fuse2fs *ff, int ret,
+				    const char *func)
+{
+	if (ret)
+		dbg_printf(ff, "%s: libfuse ret=%d\n", func, ret);
+	pthread_mutex_unlock(&ff->bfl);
+}
+#define fuse2fs_finish(ff, ret) __fuse2fs_finish((ff), (ret), __func__)
+
 #ifdef CONFIG_MMP
 static bool fuse2fs_mmp_wanted(const struct fuse2fs *ff)
 {
@@ -552,7 +561,7 @@ static void fuse2fs_mmp_bthread(void *data)
 	fuse2fs_start(ff);
 	if (fuse2fs_mmp_wanted(ff) && !bthread_cancelled(ff->mmp_thread))
 		fuse2fs_mmp_touch(ff, false);
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, 0);
 }
 
 static void fuse2fs_mmp_start(struct fuse2fs *ff)
@@ -1029,7 +1038,7 @@ static void op_destroy(void *p EXT2FS_ATTR((unused)))
 		log_printf(ff, "%s %s.\n", _("unmounting filesystem"), uuid);
 	}
 
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, 0);
 }
 
 /* Reopen @stream with @fileno */
@@ -1325,7 +1334,7 @@ static int op_getattr(const char *path, struct stat *statbuf
 		goto out;
 	ret = stat_inode(fs, ino, statbuf);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -1398,7 +1407,7 @@ static int op_readlink(const char *path, char *buf, size_t len)
 	}
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -1707,7 +1716,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 		goto out2;
 
 out2:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 out:
 	free(temp_path);
 	return ret;
@@ -1842,7 +1851,7 @@ static int op_mkdir(const char *path, mode_t mode)
 out3:
 	ext2fs_free_mem(&block);
 out2:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 out:
 	free(temp_path);
 	return ret;
@@ -2055,7 +2064,7 @@ static int op_unlink(const char *path)
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fuse2fs_start(ff);
 	ret = __op_unlink(ff, path);
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -2174,7 +2183,7 @@ static int op_rmdir(const char *path)
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fuse2fs_start(ff);
 	ret = __op_rmdir(ff, path);
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -2282,7 +2291,7 @@ static int op_symlink(const char *src, const char *dest)
 		goto out2;
 
 out2:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 out:
 	free(temp_path);
 	return ret;
@@ -2587,7 +2596,7 @@ static int op_rename(const char *from, const char *to
 	free(temp_from);
 	free(temp_to);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -2687,7 +2696,7 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 
 out2:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 out:
 	free(temp_path);
 	return ret;
@@ -2847,7 +2856,7 @@ static int op_chmod(const char *path, mode_t mode
 	}
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -2917,7 +2926,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 	}
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -3020,7 +3029,7 @@ static int op_truncate(const char *path, off_t len
 		goto out;
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -3137,7 +3146,7 @@ static int op_open(const char *path, struct fuse_file_info *fp)
 	FUSE2FS_CHECK_CONTEXT(ff);
 	fuse2fs_start(ff);
 	ret = __op_open(ff, path, fp);
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -3193,7 +3202,7 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 			goto out;
 	}
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return got ? (int) got : ret;
 }
 
@@ -3265,7 +3274,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 		goto out;
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return got ? (int) got : ret;
 }
 
@@ -3294,7 +3303,7 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 	}
 
 	fp->fh = 0;
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	ext2fs_free_mem(&fh);
 
@@ -3323,7 +3332,7 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 		if (err)
 			ret = translate_error(fs, fh->ino, err);
 	}
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -3372,7 +3381,7 @@ static int op_statfs(const char *path EXT2FS_ATTR((unused)),
 	if (!(fs->flags & EXT2_FLAG_RW))
 		buf->f_flag |= ST_RDONLY;
 	buf->f_namemax = EXT2_NAME_LEN;
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, 0);
 
 	return 0;
 }
@@ -3446,7 +3455,7 @@ static int op_getxattr(const char *path, const char *key, char *value,
 
 	ext2fs_free_mem(&ptr);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -3543,7 +3552,7 @@ static int op_listxattr(const char *path, char *names, size_t len)
 	if (err && !ret)
 		ret = translate_error(fs, ino, err);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -3636,7 +3645,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 	if (!ret && err)
 		ret = translate_error(fs, ino, err);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -3731,7 +3740,7 @@ static int op_removexattr(const char *path, const char *key)
 	if (err && !ret)
 		ret = translate_error(fs, ino, err);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -3868,7 +3877,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 			goto out;
 	}
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -3895,7 +3904,7 @@ static int op_access(const char *path, int mask)
 		goto out;
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -4023,7 +4032,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 
 out2:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 out:
 	free(temp_path);
 	return ret;
@@ -4078,7 +4087,7 @@ static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 		goto out;
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -4098,7 +4107,7 @@ static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	fs = fuse2fs_start(ff);
 	ret = stat_inode(fs, fh->ino, statbuf);
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -4171,7 +4180,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 	}
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -4566,7 +4575,7 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 		dbg_printf(ff, "%s: Unknown ioctl %d\n", __func__, cmd);
 		ret = -ENOTTY;
 	}
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }
@@ -4598,7 +4607,7 @@ static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 	}
 
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 	return ret;
 }
 
@@ -4854,7 +4863,7 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 	else
 		ret = fuse2fs_allocate_range(ff, fh, mode, offset, len);
 out:
-	pthread_mutex_unlock(&ff->bfl);
+	fuse2fs_finish(ff, ret);
 
 	return ret;
 }


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

* [PATCH 8/9] fuse2fs: clean up more boilerplate
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (6 preceding siblings ...)
  2025-11-06 22:37   ` [PATCH 7/9] fuse2fs: clean up operation completion Darrick J. Wong
@ 2025-11-06 22:38   ` Darrick J. Wong
  2025-11-06 22:38   ` [PATCH 9/9] fuse2fs: collect runtime of various operations Darrick J. Wong
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Clean up these opencoded bits everywhere.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 77d0eddbe805dc..9d50ebb6d08f8a 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -634,6 +634,25 @@ static void fuse2fs_mmp_destroy(struct fuse2fs *ff)
 # define fuse2fs_mmp_destroy(...)	((void)0)
 #endif
 
+static inline struct fuse2fs *fuse2fs_get(void)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+
+	return ctxt->private_data;
+}
+
+static inline struct fuse2fs_file_handle *
+fuse2fs_get_handle(const struct fuse_file_info *fp)
+{
+	return (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+}
+
+static inline void
+fuse2fs_set_handle(struct fuse_file_info *fp, struct fuse2fs_file_handle *fh)
+{
+	fp->fh = (uintptr_t)fh;
+}
+
 static void get_now(struct timespec *now)
 {
 #ifdef CLOCK_REALTIME
@@ -994,8 +1013,7 @@ static errcode_t fuse2fs_check_support(struct fuse2fs *ff)
 
 static void op_destroy(void *p EXT2FS_ATTR((unused)))
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	errcode_t err;
 
@@ -1167,8 +1185,7 @@ static void *op_init(struct fuse_conn_info *conn
 #endif
 			)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 
 	FUSE2FS_CHECK_CONTEXT_INIT(ff);
@@ -1288,8 +1305,7 @@ static int __fuse2fs_file_ino(struct fuse2fs *ff, const char *path,
 
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	if (fp) {
-		struct fuse2fs_file_handle *fh =
-			(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+		struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 
 		if (fh->ino == 0)
 			return -ESTALE;
@@ -1321,8 +1337,7 @@ static int op_getattr(const char *path, struct stat *statbuf
 #endif
 			)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	ext2_ino_t ino;
 	int ret = 0;
@@ -1340,8 +1355,7 @@ static int op_getattr(const char *path, struct stat *statbuf
 
 static int op_readlink(const char *path, char *buf, size_t len)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	errcode_t err;
 	ext2_ino_t ino;
@@ -1596,7 +1610,7 @@ static void fuse2fs_set_extra_isize(struct fuse2fs *ff, ext2_ino_t ino,
 static int op_mknod(const char *path, mode_t mode, dev_t dev)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
 	char *temp_path;
@@ -1725,7 +1739,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 static int op_mkdir(const char *path, mode_t mode)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
 	char *temp_path;
@@ -2057,8 +2071,7 @@ static int __op_unlink(struct fuse2fs *ff, const char *path)
 
 static int op_unlink(const char *path)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	int ret;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -2176,8 +2189,7 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
 
 static int op_rmdir(const char *path)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	int ret;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -2190,7 +2202,7 @@ static int op_rmdir(const char *path)
 static int op_symlink(const char *src, const char *dest)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
 	char *temp_path;
@@ -2361,8 +2373,7 @@ static int op_rename(const char *from, const char *to
 #endif
 			)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	errcode_t err;
 	ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
@@ -2602,8 +2613,7 @@ static int op_rename(const char *from, const char *to
 
 static int op_link(const char *src, const char *dest)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	char *temp_path;
 	errcode_t err;
@@ -2752,7 +2762,7 @@ static int get_req_groups(struct fuse2fs *ff, gid_t **gids, size_t *nr_gids)
 static int in_file_group(struct fuse_context *ctxt,
 			 const struct ext2_inode_large *inode)
 {
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	gid_t *gids = NULL;
 	size_t i, nr_gids = 0;
 	gid_t gid = inode_gid(*inode);
@@ -2796,7 +2806,7 @@ static int op_chmod(const char *path, mode_t mode
 			)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	errcode_t err;
 	ext2_ino_t ino;
@@ -2867,7 +2877,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group
 			)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	errcode_t err;
 	ext2_ino_t ino;
@@ -3008,8 +3018,7 @@ static int op_truncate(const char *path, off_t len
 #endif
 			)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_ino_t ino;
 	int ret = 0;
 
@@ -3129,7 +3138,7 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 	}
 
 	file->check_flags = check;
-	fp->fh = (uintptr_t)file;
+	fuse2fs_set_handle(fp, file);
 
 out:
 	if (ret)
@@ -3139,8 +3148,7 @@ static int __op_open(struct fuse2fs *ff, const char *path,
 
 static int op_open(const char *path, struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	int ret;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -3154,10 +3162,8 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 		   size_t len, off_t offset,
 		   struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	ext2_filsys fs;
 	ext2_file_t efp;
 	errcode_t err;
@@ -3210,10 +3216,8 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 		    const char *buf, size_t len, off_t offset,
 		    struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	ext2_filsys fs;
 	ext2_file_t efp;
 	errcode_t err;
@@ -3281,10 +3285,8 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 static int op_release(const char *path EXT2FS_ATTR((unused)),
 		      struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	ext2_filsys fs;
 	errcode_t err;
 	int ret = 0;
@@ -3314,10 +3316,8 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 		    int datasync EXT2FS_ATTR((unused)),
 		    struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	ext2_filsys fs;
 	errcode_t err;
 	int ret = 0;
@@ -3340,8 +3340,7 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 static int op_statfs(const char *path EXT2FS_ATTR((unused)),
 		     struct statvfs *buf)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	uint64_t fsid, *f;
 	blk64_t overhead, reserved, free;
@@ -3410,8 +3409,7 @@ static int validate_xattr_name(const char *name)
 static int op_getxattr(const char *path, const char *key, char *value,
 		       size_t len)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	void *ptr;
 	size_t plen;
@@ -3484,8 +3482,7 @@ static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
 
 static int op_listxattr(const char *path, char *names, size_t len)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
 	unsigned int bufsz;
@@ -3561,8 +3558,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 		       const char *key, const char *value,
 		       size_t len, int flags)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
 	ext2_ino_t ino;
@@ -3652,8 +3648,7 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 
 static int op_removexattr(const char *path, const char *key)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
 	void *buf;
@@ -3843,10 +3838,8 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 #endif
 			)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	errcode_t err;
 	struct readdir_iter i = {
 		.ff = ff,
@@ -3883,8 +3876,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 
 static int op_access(const char *path, int mask)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	errcode_t err;
 	ext2_ino_t ino;
@@ -3911,7 +3903,7 @@ static int op_access(const char *path, int mask)
 static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 {
 	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
 	char *temp_path;
@@ -4042,10 +4034,8 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 			off_t len, struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	ext2_filsys fs;
 	ext2_file_t efp;
 	errcode_t err;
@@ -4095,11 +4085,9 @@ static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
 		       struct stat *statbuf,
 		       struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -4119,8 +4107,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2]
 #endif
 			)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	struct timespec tv[2];
 	ext2_filsys fs;
 	errcode_t err;
@@ -4534,10 +4521,8 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 		    struct fuse_file_info *fp,
 		    unsigned int flags EXT2FS_ATTR((unused)), void *data)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	int ret = 0;
 
 	FUSE2FS_CHECK_CONTEXT(ff);
@@ -4584,8 +4569,7 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 		   uint64_t *idx)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
 	ext2_ino_t ino;
 	errcode_t err;
@@ -4838,10 +4822,8 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 			off_t offset, off_t len,
 			struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
-	struct fuse2fs_file_handle *fh =
-		(struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+	struct fuse2fs *ff = fuse2fs_get();
+	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 	ext2_filsys fs;
 	int ret;
 


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

* [PATCH 9/9] fuse2fs: collect runtime of various operations
  2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
                     ` (7 preceding siblings ...)
  2025-11-06 22:38   ` [PATCH 8/9] fuse2fs: clean up more boilerplate Darrick J. Wong
@ 2025-11-06 22:38   ` Darrick J. Wong
  8 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Collect the run time of various operations so that we can do some simple
profiling.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 configure       |   37 +++++++++++++++++++++++++++++++++++++
 configure.ac    |   19 +++++++++++++++++++
 lib/config.h.in |    3 +++
 misc/fuse2fs.c  |   43 ++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 101 insertions(+), 1 deletion(-)


diff --git a/configure b/configure
index 2ed61db3602dc9..ba2e5380f6d20b 100755
--- a/configure
+++ b/configure
@@ -14725,6 +14725,43 @@ then
 printf "%s\n" "#define FUSE_USE_VERSION $FUSE_USE_VERSION" >>confdefs.h
 
 fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CLOCK_MONOTONIC" >&5
+printf %s "checking for CLOCK_MONOTONIC... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+#define _GNU_SOURCE
+#include <time.h>
+
+int
+main (void)
+{
+
+struct timespec nuts;
+clock_gettime(CLOCK_MONOTONIC, &nuts);
+
+  ;
+  return 0;
+}
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  have_clock_monotonic=yes
+   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+if test "$have_clock_monotonic" = yes; then
+
+printf "%s\n" "#define HAVE_CLOCK_MONOTONIC 1" >>confdefs.h
+
+fi
+
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for optreset" >&5
 printf %s "checking for optreset... " >&6; }
 if test ${ac_cv_have_optreset+y}
diff --git a/configure.ac b/configure.ac
index bdd5f1f69d267d..051161c5848072 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1452,6 +1452,25 @@ then
 	AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
 		[Define to the version of FUSE to use])
 fi
+dnl
+dnl see if CLOCK_MONOTONIC exists
+dnl
+AC_MSG_CHECKING(for CLOCK_MONOTONIC)
+AC_LINK_IFELSE(
+[	AC_LANG_PROGRAM([[
+#define _GNU_SOURCE
+#include <time.h>
+	]], [[
+struct timespec nuts;
+clock_gettime(CLOCK_MONOTONIC, &nuts);
+	]])
+], have_clock_monotonic=yes
+   AC_MSG_RESULT(yes),
+   AC_MSG_RESULT(no))
+if test "$have_clock_monotonic" = yes; then
+  AC_DEFINE(HAVE_CLOCK_MONOTONIC, 1, [Define to 1 if CLOCK_MONOTONIC is present])
+fi
+
 dnl
 dnl See if optreset exists
 dnl
diff --git a/lib/config.h.in b/lib/config.h.in
index 480717abd9b4be..364d6e865b7115 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -683,4 +683,7 @@
 /* Define for large files, on AIX-style hosts. */
 #undef _LARGE_FILES
 
+/* Define to 1 if CLOCK_MONOTONIC is present */
+#undef HAVE_CLOCK_MONOTONIC
+
 #include <dirpaths.h>
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 9d50ebb6d08f8a..9db0b17af27438 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -166,6 +166,12 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)
 		fflush(stderr); \
 	} while (0)
 
+#define timing_printf(fuse2fs, format, ...) \
+	while ((fuse2fs)->timing) { \
+		printf("FUSE2FS (%s): " format, (fuse2fs)->shortdev, ##__VA_ARGS__); \
+		break; \
+	}
+
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
 # ifdef _IOR
 #  ifdef _IOW
@@ -246,6 +252,13 @@ struct fuse2fs {
 	struct bthread *mmp_thread;
 	unsigned int mmp_update_interval;
 #endif
+#ifdef HAVE_CLOCK_MONOTONIC
+	double lock_start_time;
+	double op_start_time;
+
+	/* options set by fuse_opt_parse must be of type int */
+	int timing;
+#endif
 };
 
 #define FUSE2FS_CHECK_HANDLE(ff, fh) \
@@ -494,13 +507,38 @@ static inline errcode_t fuse2fs_write_inode(ext2_filsys fs, ext2_ino_t ino,
 
 static inline ext2_filsys fuse2fs_start(struct fuse2fs *ff)
 {
-	pthread_mutex_lock(&ff->bfl);
+	if (ff->timing) {
+		double lock_time = gettime_monotonic();
+
+		pthread_mutex_lock(&ff->bfl);
+
+		ff->op_start_time = gettime_monotonic();
+		ff->lock_start_time = lock_time;
+	} else {
+		pthread_mutex_lock(&ff->bfl);
+	}
+
 	return ff->fs;
 }
 
+static inline void fuse2fs_finish_timing(struct fuse2fs *ff, const char *func)
+{
+	double now;
+
+	if (!ff->timing)
+		return;
+
+	now = gettime_monotonic();
+
+	timing_printf(ff, "%s: lock=%.2fms elapsed=%.2fms\n", func,
+		      (ff->op_start_time - ff->lock_start_time) * 1000.0,
+		      (now - ff->op_start_time) * 1000.0);
+}
+
 static inline void __fuse2fs_finish(struct fuse2fs *ff, int ret,
 				    const char *func)
 {
+	fuse2fs_finish_timing(ff, func);
 	if (ret)
 		dbg_printf(ff, "%s: libfuse ret=%d\n", func, ret);
 	pthread_mutex_unlock(&ff->bfl);
@@ -4959,6 +4997,9 @@ static struct fuse_opt fuse2fs_opts[] = {
 	FUSE2FS_OPT("acl",		acl,			1),
 	FUSE2FS_OPT("noacl",		acl,			0),
 	FUSE2FS_OPT("lockfile=%s",	lockfile,		0),
+#ifdef HAVE_CLOCK_MONOTONIC
+	FUSE2FS_OPT("timing",		timing,			1),
+#endif
 
 	FUSE_OPT_KEY("user_xattr",	FUSE2FS_IGNORED),
 	FUSE_OPT_KEY("noblock_validity", FUSE2FS_IGNORED),


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

* [PATCH 1/3] fuse2fs: get rid of the global_fs variable
  2025-11-06 22:28 ` [PATCHSET 4/9] fuse2fs: refactor unmount code Darrick J. Wong
@ 2025-11-06 22:38   ` Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 2/3] fuse2fs: hoist lockfile code Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 3/3] fuse2fs: hoist unmount code from main Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:38 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Get rid of this global variable now that we don't need it anywhere.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 9db0b17af27438..0020d149949835 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -123,8 +123,6 @@
 #endif
 #endif /* !defined(ENODATA) */
 
-static ext2_filsys global_fs; /* Try not to use this directly */
-
 static inline uint64_t round_up(uint64_t b, unsigned int align)
 {
 	unsigned int m;
@@ -1265,7 +1263,7 @@ static void *op_init(struct fuse_conn_info *conn
 		log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
 	}
 
-	if (global_fs->flags & EXT2_FLAG_RW)
+	if (ff->fs->flags & EXT2_FLAG_RW)
 		fuse2fs_read_bitmaps(ff);
 
 	/*
@@ -5287,7 +5285,7 @@ int main(int argc, char *argv[])
 	deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
 	do {
 		err = ext2fs_open2(fctx.device, options, flags, 0, 0,
-				   unix_io_manager, &global_fs);
+				   unix_io_manager, &fctx.fs);
 		if ((err == EPERM || err == EACCES) &&
 		    (!fctx.ro || (flags & EXT2_FLAG_RW))) {
 			/*
@@ -5325,17 +5323,17 @@ int main(int argc, char *argv[])
 	 * uevent and cause weird userspace stalls, and block devices have
 	 * O_EXCL so we don't need this there.
 	 */
-	if (!(global_fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
+	if (!(fctx.fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
 		unsigned int lock_flags = IO_CHANNEL_FLOCK_TRYLOCK;
 
-		if (global_fs->flags & IO_FLAG_RW)
+		if (fctx.fs->flags & IO_FLAG_RW)
 			lock_flags |= IO_CHANNEL_FLOCK_EXCLUSIVE;
 		else
 			lock_flags |= IO_CHANNEL_FLOCK_SHARED;
 
 		deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
 		do {
-			err = io_channel_flock(global_fs->io, lock_flags);
+			err = io_channel_flock(fctx.fs->io, lock_flags);
 		} while (err == EWOULDBLOCK && retry_before_deadline(deadline));
 		if (err) {
 			err_printf(&fctx, "%s: %s\n",
@@ -5344,8 +5342,7 @@ int main(int argc, char *argv[])
 		}
 	}
 
-	fctx.fs = global_fs;
-	global_fs->priv_data = &fctx;
+	fctx.fs->priv_data = &fctx;
 	fctx.blocklog = u_log2(fctx.fs->blocksize);
 	fctx.blockmask = fctx.fs->blocksize - 1;
 
@@ -5358,7 +5355,7 @@ int main(int argc, char *argv[])
 
 		snprintf(buf, sizeof(buf), "cache_blocks=%llu",
 			 FUSE2FS_B_TO_FSBT(&fctx, fctx.cache_size));
-		err = io_channel_set_options(global_fs->io, buf);
+		err = io_channel_set_options(fctx.fs->io, buf);
 		if (err) {
 			err_printf(&fctx, "%s %lluk: %s\n",
 				   _("cannot set disk cache size to"),
@@ -5377,23 +5374,23 @@ int main(int argc, char *argv[])
 	 * ext4 can't do COW of shared blocks, so if the feature is enabled,
 	 * we must force ro mode.
 	 */
-	if (ext2fs_has_feature_shared_blocks(global_fs->super))
+	if (ext2fs_has_feature_shared_blocks(fctx.fs->super))
 		fctx.ro = 1;
 
-	if (ext2fs_has_feature_journal_needs_recovery(global_fs->super)) {
+	if (ext2fs_has_feature_journal_needs_recovery(fctx.fs->super)) {
 		if (fctx.norecovery) {
 			log_printf(&fctx, "%s\n",
  _("Mounting read-only without recovering journal."));
 			fctx.ro = 1;
-			global_fs->flags &= ~EXT2_FLAG_RW;
-		} else if (!(global_fs->flags & EXT2_FLAG_RW)) {
+			fctx.fs->flags &= ~EXT2_FLAG_RW;
+		} else if (!(fctx.fs->flags & EXT2_FLAG_RW)) {
 			err_printf(&fctx, "%s\n",
  _("Cannot replay journal on read-only device."));
 			ret = 32;
 			goto out;
 		} else {
 			log_printf(&fctx, "%s\n", _("Recovering journal."));
-			err = ext2fs_run_ext3_journal(&global_fs);
+			err = ext2fs_run_ext3_journal(&fctx.fs);
 			if (err) {
 				err_printf(&fctx, "%s.\n", error_message(err));
 				err_printf(&fctx, "%s\n",
@@ -5405,58 +5402,58 @@ int main(int argc, char *argv[])
 			if (err)
 				goto out;
 		}
-	} else if (ext2fs_has_feature_journal(global_fs->super)) {
-		err = ext2fs_check_ext3_journal(global_fs);
+	} else if (ext2fs_has_feature_journal(fctx.fs->super)) {
+		err = ext2fs_check_ext3_journal(fctx.fs);
 		if (err) {
-			translate_error(global_fs, 0, err);
+			translate_error(fctx.fs, 0, err);
 			goto out;
 		}
 	}
 
-	ret = fuse2fs_check_root_dir(global_fs);
+	ret = fuse2fs_check_root_dir(fctx.fs);
 	if (ret)
 		goto out;
 
-	if (global_fs->flags & EXT2_FLAG_RW) {
-		if (ext2fs_has_feature_journal(global_fs->super))
+	if (fctx.fs->flags & EXT2_FLAG_RW) {
+		if (ext2fs_has_feature_journal(fctx.fs->super))
 			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"));
 	}
 
-	if (!(global_fs->super->s_state & EXT2_VALID_FS))
+	if (!(fctx.fs->super->s_state & EXT2_VALID_FS))
 		err_printf(&fctx, "%s\n",
  _("Warning: Mounting unchecked fs, running e2fsck is recommended."));
-	if (global_fs->super->s_max_mnt_count > 0 &&
-	    global_fs->super->s_mnt_count >= global_fs->super->s_max_mnt_count)
+	if (fctx.fs->super->s_max_mnt_count > 0 &&
+	    fctx.fs->super->s_mnt_count >= fctx.fs->super->s_max_mnt_count)
 		err_printf(&fctx, "%s\n",
  _("Warning: Maximal mount count reached, running e2fsck is recommended."));
-	if (global_fs->super->s_checkinterval > 0 &&
-	    (time_t) (global_fs->super->s_lastcheck +
-		      global_fs->super->s_checkinterval) <= time(0))
+	if (fctx.fs->super->s_checkinterval > 0 &&
+	    (time_t) (fctx.fs->super->s_lastcheck +
+		      fctx.fs->super->s_checkinterval) <= time(0))
 		err_printf(&fctx, "%s\n",
  _("Warning: Check time reached; running e2fsck is recommended."));
-	if (global_fs->super->s_last_orphan)
+	if (fctx.fs->super->s_last_orphan)
 		err_printf(&fctx, "%s\n",
  _("Orphans detected; running e2fsck is recommended."));
 
 	/* Clear the valid flag so that an unclean shutdown forces a fsck */
-	if (global_fs->flags & EXT2_FLAG_RW) {
-		global_fs->super->s_mnt_count++;
-		ext2fs_set_tstamp(global_fs->super, s_mtime, time(NULL));
-		global_fs->super->s_state &= ~EXT2_VALID_FS;
-		ext2fs_mark_super_dirty(global_fs);
-		err = ext2fs_flush2(global_fs, 0);
+	if (fctx.fs->flags & EXT2_FLAG_RW) {
+		fctx.fs->super->s_mnt_count++;
+		ext2fs_set_tstamp(fctx.fs->super, s_mtime, time(NULL));
+		fctx.fs->super->s_state &= ~EXT2_VALID_FS;
+		ext2fs_mark_super_dirty(fctx.fs);
+		err = ext2fs_flush2(fctx.fs, 0);
 		if (err) {
-			translate_error(global_fs, 0, err);
+			translate_error(fctx.fs, 0, err);
 			ret |= 32;
 			goto out;
 		}
 	}
 
 	if (!fctx.errors_behavior)
-		fctx.errors_behavior = global_fs->super->s_errors;
+		fctx.errors_behavior = fctx.fs->super->s_errors;
 
 	/* Initialize generation counter */
 	get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
@@ -5545,8 +5542,8 @@ int main(int argc, char *argv[])
 		fflush(orig_stderr);
 	}
 	fuse2fs_mmp_destroy(&fctx);
-	if (global_fs) {
-		err = ext2fs_close_free(&global_fs);
+	if (fctx.fs) {
+		err = ext2fs_close_free(&fctx.fs);
 		if (err)
 			com_err(argv[0], err, "while closing fs");
 	}


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

* [PATCH 2/3] fuse2fs: hoist lockfile code
  2025-11-06 22:28 ` [PATCHSET 4/9] fuse2fs: refactor unmount code Darrick J. Wong
  2025-11-06 22:38   ` [PATCH 1/3] fuse2fs: get rid of the global_fs variable Darrick J. Wong
@ 2025-11-06 22:39   ` Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 3/3] fuse2fs: hoist unmount code from main Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Hoist the lockfile handling code into separate helpers before we start
rearranging the code that opens and closes filesystems.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 0020d149949835..cefc6442ae45ec 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1047,6 +1047,54 @@ static errcode_t fuse2fs_check_support(struct fuse2fs *ff)
 	return 0;
 }
 
+static errcode_t fuse2fs_acquire_lockfile(struct fuse2fs *ff)
+{
+	char *resolved;
+	int lockfd;
+	errcode_t err;
+
+	lockfd = open(ff->lockfile, O_RDWR | O_CREAT | O_EXCL, 0400);
+	if (lockfd < 0) {
+		if (errno == EEXIST)
+			err = EWOULDBLOCK;
+		else
+			err = errno;
+		err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+			   _("opening lockfile failed"),
+			   strerror(err));
+		ff->lockfile = NULL;
+		return err;
+	}
+	close(lockfd);
+
+	resolved = realpath(ff->lockfile, NULL);
+	if (!resolved) {
+		err = errno;
+		err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+			   _("resolving lockfile failed"),
+			   strerror(err));
+		unlink(ff->lockfile);
+		ff->lockfile = NULL;
+		return err;
+	}
+	free(ff->lockfile);
+	ff->lockfile = resolved;
+
+	return 0;
+}
+
+static void fuse2fs_release_lockfile(struct fuse2fs *ff)
+{
+	if (unlink(ff->lockfile)) {
+		errcode_t err = errno;
+
+		err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+			   _("removing lockfile failed"),
+			   strerror(err));
+	}
+	free(ff->lockfile);
+}
+
 static void op_destroy(void *p EXT2FS_ATTR((unused)))
 {
 	struct fuse2fs *ff = fuse2fs_get();
@@ -5212,38 +5260,9 @@ int main(int argc, char *argv[])
 		fctx.alloc_all_blocks = 1;
 	}
 
-	if (fctx.lockfile) {
-		char *resolved;
-		int lockfd;
-
-		lockfd = open(fctx.lockfile, O_RDWR | O_CREAT | O_EXCL, 0400);
-		if (lockfd < 0) {
-			if (errno == EEXIST)
-				err = EWOULDBLOCK;
-			else
-				err = errno;
-			err_printf(&fctx, "%s: %s: %s\n", fctx.lockfile,
-				   _("opening lockfile failed"),
-				   strerror(err));
-			fctx.lockfile = NULL;
-			ret |= 32;
-			goto out;
-		}
-		close(lockfd);
-
-		resolved = realpath(fctx.lockfile, NULL);
-		if (!resolved) {
-			err = errno;
-			err_printf(&fctx, "%s: %s: %s\n", fctx.lockfile,
-				   _("resolving lockfile failed"),
-				   strerror(err));
-			unlink(fctx.lockfile);
-			fctx.lockfile = NULL;
-			ret |= 32;
-			goto out;
-		}
-		free(fctx.lockfile);
-		fctx.lockfile = resolved;
+	if (fctx.lockfile && fuse2fs_acquire_lockfile(&fctx)) {
+		ret |= 32;
+		goto out;
 	}
 
 	/* Start up the fs (while we still can use stdout) */
@@ -5547,15 +5566,8 @@ int main(int argc, char *argv[])
 		if (err)
 			com_err(argv[0], err, "while closing fs");
 	}
-	if (fctx.lockfile) {
-		if (unlink(fctx.lockfile)) {
-			err = errno;
-			err_printf(&fctx, "%s: %s: %s\n", fctx.lockfile,
-				   _("removing lockfile failed"),
-				   strerror(err));
-		}
-		free(fctx.lockfile);
-	}
+	if (fctx.lockfile)
+		fuse2fs_release_lockfile(&fctx);
 	if (fctx.device)
 		free(fctx.device);
 	pthread_mutex_destroy(&fctx.bfl);


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

* [PATCH 3/3] fuse2fs: hoist unmount code from main
  2025-11-06 22:28 ` [PATCHSET 4/9] fuse2fs: refactor unmount code Darrick J. Wong
  2025-11-06 22:38   ` [PATCH 1/3] fuse2fs: get rid of the global_fs variable Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 2/3] fuse2fs: hoist lockfile code Darrick J. Wong
@ 2025-11-06 22:39   ` Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Hoist the unmount code into a separate function so that we can reduce
the complexity of main().  This also sets us up for unmounting the
filesystem from op_destroy, which we'll need for fuse2fs+iomap mode to
maintain the expected behavior that the block device is free when
umount(8) returns.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index cefc6442ae45ec..b096c3f496d740 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1095,6 +1095,27 @@ static void fuse2fs_release_lockfile(struct fuse2fs *ff)
 	free(ff->lockfile);
 }
 
+static void fuse2fs_unmount(struct fuse2fs *ff)
+{
+	char uuid[UUID_STR_SIZE];
+	errcode_t err;
+
+	if (ff->fs) {
+		uuid_unparse(ff->fs->super->s_uuid, uuid);
+		err = ext2fs_close_free(&ff->fs);
+		if (err)
+			err_printf(ff, "%s: %s\n", _("while closing fs"),
+				   error_message(err));
+
+		if (ff->kernel)
+			log_printf(ff, "%s %s.\n", _("unmounted filesystem"),
+				   uuid);
+	}
+
+	if (ff->lockfile)
+		fuse2fs_release_lockfile(ff);
+}
+
 static void op_destroy(void *p EXT2FS_ATTR((unused)))
 {
 	struct fuse2fs *ff = fuse2fs_get();
@@ -1133,13 +1154,6 @@ static void op_destroy(void *p EXT2FS_ATTR((unused)))
 				(stats->cache_hits + stats->cache_misses));
 	}
 
-	if (ff->kernel) {
-		char uuid[UUID_STR_SIZE];
-
-		uuid_unparse(fs->super->s_uuid, uuid);
-		log_printf(ff, "%s %s.\n", _("unmounting filesystem"), uuid);
-	}
-
 	fuse2fs_finish(ff, 0);
 }
 
@@ -5561,13 +5575,7 @@ int main(int argc, char *argv[])
 		fflush(orig_stderr);
 	}
 	fuse2fs_mmp_destroy(&fctx);
-	if (fctx.fs) {
-		err = ext2fs_close_free(&fctx.fs);
-		if (err)
-			com_err(argv[0], err, "while closing fs");
-	}
-	if (fctx.lockfile)
-		fuse2fs_release_lockfile(&fctx);
+	fuse2fs_unmount(&fctx);
 	if (fctx.device)
 		free(fctx.device);
 	pthread_mutex_destroy(&fctx.bfl);


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

* [PATCH 1/3] fuse2fs: split filesystem mounting into helper functions
  2025-11-06 22:28 ` [PATCHSET 5/9] fuse2fs: refactor mount code Darrick J. Wong
@ 2025-11-06 22:39   ` Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 2/3] fuse2fs: register as an IO flusher thread Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 3/3] fuse2fs: adjust OOM killer score if possible Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Break up main() by moving the filesystem mounting logic into separate
helper functions.  This originally made it easier to move that part
around for fuseblk support, and I kept it around because splitting up
the mounting code into multiple smaller functions makes them easier to
understand.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index b096c3f496d740..e0134834d342dd 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1116,6 +1116,258 @@ static void fuse2fs_unmount(struct fuse2fs *ff)
 		fuse2fs_release_lockfile(ff);
 }
 
+static errcode_t fuse2fs_open(struct fuse2fs *ff)
+{
+	char options[128];
+	double deadline;
+	int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_RW |
+		    EXT2_FLAG_EXCLUSIVE;
+	errcode_t err;
+
+	if (ff->lockfile) {
+		err = fuse2fs_acquire_lockfile(ff);
+		if (err)
+			return err;
+	}
+
+	snprintf(options, sizeof(options) - 1, "offset=%lu", ff->offset);
+
+	if (ff->directio)
+		flags |= EXT2_FLAG_DIRECT_IO;
+
+	/*
+	 * If the filesystem is stored on a block device, the _EXCLUSIVE flag
+	 * causes libext2fs to try to open the block device with O_EXCL.  If
+	 * the block device is already opened O_EXCL by something else, the
+	 * open call returns EBUSY.
+	 *
+	 * Unfortunately, there's a nasty race between fuse2fs going through
+	 * its startup sequence (open fs, parse superblock, daemonize, create
+	 * mount, respond to FUSE_INIT) in response to a mount(8) invocation
+	 * and another process that calls umount(2) on the same mount.
+	 *
+	 * If fuse2fs is being run as a mount(8) helper and has daemonized, the
+	 * original fuse2fs subprocess exits and so will mount(8).  This can
+	 * occur before the kernel issues a FUSE_INIT request to fuse2fs.  If
+	 * a process then umount(2)'s the mount, the kernel will abort the
+	 * fuse connection.  If the FUSE_INIT request hasn't been issued, now
+	 * it won't ever be issued.  The kernel tears down the mount and
+	 * returns from umount(2), but fuse2fs has no idea that any of this has
+	 * happened because it receives no requests.
+	 *
+	 * At this point, the original fuse2fs server holds the block device
+	 * open O_EXCL.  If mount(8) is invoked again on the same device, the
+	 * new fuse2fs server will try to open the block device O_EXCL and
+	 * fail.  A crappy solution here is to retry for 5 seconds, hoping that
+	 * the first fuse2fs server will wake up and exit.
+	 *
+	 * If the filesystem is in a regular file, O_EXCL (without O_CREAT) has
+	 * no defined behavior, but it never returns EBUSY.
+	 */
+	deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
+	do {
+		err = ext2fs_open2(ff->device, options, flags, 0, 0,
+				   unix_io_manager, &ff->fs);
+		if ((err == EPERM || err == EACCES) &&
+		    (!ff->ro || (flags & EXT2_FLAG_RW))) {
+			/*
+			 * Source device cannot be opened for write.  Under
+			 * these circumstances, mount(8) will try again with a
+			 * ro mount, and the kernel will open the block device
+			 * readonly.
+			 */
+			log_printf(ff, "%s\n",
+ _("WARNING: source write-protected, mounted read-only."));
+			flags &= ~EXT2_FLAG_RW;
+			ff->ro = 1;
+
+			/* Force the loop to run once more */
+			err = -1;
+		}
+	} while (err == -1 ||
+		 (err == EBUSY && retry_before_deadline(deadline)));
+	if (err == EBUSY) {
+		err_printf(ff, "%s: %s.\n",
+ _("Could not lock filesystem block device"), error_message(err));
+		return err;
+	}
+	if (err) {
+		err_printf(ff, "%s.\n", error_message(err));
+		err_printf(ff, "%s\n", _("Please run e2fsck -fy."));
+		return err;
+	}
+
+	/*
+	 * If the filesystem is stored in a regular file, take an (advisory)
+	 * exclusive lock to prevent other instances of e2fsprogs from writing
+	 * to the filesystem image.  On Linux we don't want to do this for
+	 * block devices because udev will spin forever trying to settle a
+	 * uevent and cause weird userspace stalls, and block devices have
+	 * O_EXCL so we don't need this there.
+	 */
+	if (!(ff->fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
+		unsigned int lock_flags = IO_CHANNEL_FLOCK_TRYLOCK;
+
+		if (ff->fs->flags & IO_FLAG_RW)
+			lock_flags |= IO_CHANNEL_FLOCK_EXCLUSIVE;
+		else
+			lock_flags |= IO_CHANNEL_FLOCK_SHARED;
+
+		deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
+		do {
+			err = io_channel_flock(ff->fs->io, lock_flags);
+		} while (err == EWOULDBLOCK && retry_before_deadline(deadline));
+		if (err) {
+			err_printf(ff, "%s: %s\n",
+ _("Could not lock filesystem image"), error_message(err));
+			return err;
+		}
+	}
+
+	if (ff->kernel) {
+		char uuid[UUID_STR_SIZE];
+
+		uuid_unparse(ff->fs->super->s_uuid, uuid);
+		log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
+	}
+
+	ff->fs->priv_data = ff;
+	ff->blocklog = u_log2(ff->fs->blocksize);
+	ff->blockmask = ff->fs->blocksize - 1;
+
+	fuse2fs_mmp_config(ff);
+	return 0;
+}
+
+/* Figure out a reasonable default size for the disk cache */
+static unsigned long long default_cache_size(void)
+{
+	long pages = 0, pagesize = 0;
+	unsigned long long max_cache;
+	unsigned long long ret = 32ULL << 20; /* 32 MB */
+
+#ifdef _SC_PHYS_PAGES
+	pages = sysconf(_SC_PHYS_PAGES);
+#endif
+#ifdef _SC_PAGESIZE
+	pagesize = sysconf(_SC_PAGESIZE);
+#endif
+	if (pages > 0 && pagesize > 0) {
+		max_cache = (unsigned long long)pagesize * pages / 20;
+
+		if (max_cache > 0 && ret > max_cache)
+			ret = max_cache;
+	}
+	return ret;
+}
+
+static errcode_t fuse2fs_config_cache(struct fuse2fs *ff)
+{
+	char buf[128];
+	errcode_t err;
+
+	if (!ff->cache_size)
+		ff->cache_size = default_cache_size();
+	if (!ff->cache_size)
+		return 0;
+
+	snprintf(buf, sizeof(buf), "cache_blocks=%llu",
+		 FUSE2FS_B_TO_FSBT(ff, ff->cache_size));
+	err = io_channel_set_options(ff->fs->io, buf);
+	if (err) {
+		err_printf(ff, "%s %lluk: %s\n",
+			   _("cannot set disk cache size to"),
+			   ff->cache_size >> 10,
+			   error_message(err));
+		return err;
+	}
+
+	return 0;
+}
+
+static int fuse2fs_mount(struct fuse2fs *ff)
+{
+	struct ext2_inode_large inode;
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+
+	if (ext2fs_has_feature_journal_needs_recovery(fs->super)) {
+		if (ff->norecovery) {
+			log_printf(ff, "%s\n",
+ _("Mounting read-only without recovering journal."));
+			ff->ro = 1;
+			ff->fs->flags &= ~EXT2_FLAG_RW;
+		} else if (!(fs->flags & EXT2_FLAG_RW)) {
+			err_printf(ff, "%s\n",
+ _("Cannot replay journal on read-only device."));
+			return -1;
+		} else {
+			log_printf(ff, "%s\n", _("Recovering journal."));
+			err = ext2fs_run_ext3_journal(&ff->fs);
+			if (err) {
+				err_printf(ff, "%s.\n", error_message(err));
+				err_printf(ff, "%s\n",
+						_("Please run e2fsck -fy."));
+				return translate_error(fs, 0, err);
+			}
+			fs = ff->fs;
+
+			err = fuse2fs_check_support(ff);
+			if (err)
+				return err;
+		}
+	} else if (ext2fs_has_feature_journal(fs->super)) {
+		err = ext2fs_check_ext3_journal(fs);
+		if (err)
+			return translate_error(fs, 0, err);
+	}
+
+	/* Make sure the root directory is readable. */
+	err = fuse2fs_read_inode(fs, EXT2_ROOT_INO, &inode);
+	if (err)
+		return translate_error(fs, EXT2_ROOT_INO, err);
+
+	if (fs->flags & EXT2_FLAG_RW) {
+		if (ext2fs_has_feature_journal(fs->super))
+			log_printf(ff, "%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"));
+	}
+
+	if (!(fs->super->s_state & EXT2_VALID_FS))
+		err_printf(ff, "%s\n",
+ _("Warning: Mounting unchecked fs, running e2fsck is recommended."));
+	if (fs->super->s_max_mnt_count > 0 &&
+	    fs->super->s_mnt_count >= fs->super->s_max_mnt_count)
+		err_printf(ff, "%s\n",
+ _("Warning: Maximal mount count reached, running e2fsck is recommended."));
+	if (fs->super->s_checkinterval > 0 &&
+	    (time_t) (fs->super->s_lastcheck +
+		      fs->super->s_checkinterval) <= time(0))
+		err_printf(ff, "%s\n",
+ _("Warning: Check time reached; running e2fsck is recommended."));
+	if (fs->super->s_last_orphan)
+		err_printf(ff, "%s\n",
+ _("Orphans detected; running e2fsck is recommended."));
+
+	if (!ff->errors_behavior)
+		ff->errors_behavior = fs->super->s_errors;
+
+	/* Clear the valid flag so that an unclean shutdown forces a fsck */
+	if (fs->flags & EXT2_FLAG_RW) {
+		fs->super->s_mnt_count++;
+		ext2fs_set_tstamp(fs->super, s_mtime, time(NULL));
+		fs->super->s_state &= ~EXT2_VALID_FS;
+		ext2fs_mark_super_dirty(fs);
+		err = ext2fs_flush2(fs, 0);
+		if (err)
+			return translate_error(fs, 0, err);
+	}
+
+	return 0;
+}
+
 static void op_destroy(void *p EXT2FS_ATTR((unused)))
 {
 	struct fuse2fs *ff = fuse2fs_get();
@@ -1318,13 +1570,6 @@ static void *op_init(struct fuse_conn_info *conn
 	cfg->nullpath_ok = 1;
 #endif
 
-	if (ff->kernel) {
-		char uuid[UUID_STR_SIZE];
-
-		uuid_unparse(fs->super->s_uuid, uuid);
-		log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
-	}
-
 	if (ff->fs->flags & EXT2_FLAG_RW)
 		fuse2fs_read_bitmaps(ff);
 
@@ -5183,39 +5428,59 @@ static const char *get_subtype(const char *argv0)
 	return "ext4";
 }
 
-/* Figure out a reasonable default size for the disk cache */
-static unsigned long long default_cache_size(void)
+static void fuse2fs_compute_libfuse_args(struct fuse2fs *ff,
+					 struct fuse_args *args,
+					 const char *argv0)
 {
-	long pages = 0, pagesize = 0;
-	unsigned long long max_cache;
-	unsigned long long ret = 32ULL << 20; /* 32 MB */
+	char extra_args[BUFSIZ];
 
-#ifdef _SC_PHYS_PAGES
-	pages = sysconf(_SC_PHYS_PAGES);
+	/* Set up default fuse parameters */
+	snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
+		 "fsname=%s,attr_timeout=0" FUSE_PLATFORM_OPTS,
+		 get_subtype(argv0),
+		 ff->device);
+	if (ff->no_default_opts == 0)
+		fuse_opt_add_arg(args, extra_args);
+
+	if (ff->ro)
+		fuse_opt_add_arg(args, "-oro");
+
+	if (ff->fakeroot) {
+#ifdef HAVE_MOUNT_NODEV
+		fuse_opt_add_arg(args,"-onodev");
 #endif
-#ifdef _SC_PAGESIZE
-	pagesize = sysconf(_SC_PAGESIZE);
+#ifdef HAVE_MOUNT_NOSUID
+		fuse_opt_add_arg(args,"-onosuid");
 #endif
-	if (pages > 0 && pagesize > 0) {
-		max_cache = (unsigned long long)pagesize * pages / 20;
+	}
 
-		if (max_cache > 0 && ret > max_cache)
-			ret = max_cache;
+	if (ff->kernel) {
+		/*
+		 * ACLs are always enforced when kernel mode is enabled, to
+		 * match the kernel ext4 driver which always enables ACLs.
+		 */
+		ff->acl = 1;
+		fuse_opt_insert_arg(args, 1,
+ "-oallow_other,default_permissions,suid,dev");
 	}
-	return ret;
-}
 
-/* Make sure the root directory is readable. */
-static errcode_t fuse2fs_check_root_dir(ext2_filsys fs)
-{
-	struct ext2_inode_large inode;
-	errcode_t err;
+	/*
+	 * Since there's a Big Kernel Lock around all the libext2fs code, we
+	 * only need to start four threads -- one to decode a request, another
+	 * to do the filesystem work, a third to transmit the reply, and a
+	 * fourth to handle fuse notifications.
+	 */
+	fuse_opt_insert_arg(args, 1, "-omax_threads=4");
 
-	err = fuse2fs_read_inode(fs, EXT2_ROOT_INO, &inode);
-	if (err)
-		return translate_error(fs, EXT2_ROOT_INO, err);
+	if (ff->debug) {
+		int	i;
 
-	return 0;
+		printf("FUSE2FS (%s): fuse arguments:", ff->shortdev);
+		for (i = 0; i < args->argc; i++)
+			printf(" '%s'", args->argv[i]);
+		printf("\n");
+		fflush(stdout);
+	}
 }
 
 int main(int argc, char *argv[])
@@ -5224,11 +5489,7 @@ int main(int argc, char *argv[])
 	struct fuse2fs fctx;
 	errcode_t err;
 	FILE *orig_stderr = stderr;
-	char extra_args[BUFSIZ];
-	double deadline;
 	int ret;
-	int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_EXCLUSIVE |
-		    EXT2_FLAG_RW;
 
 	memset(&fctx, 0, sizeof(fctx));
 	fctx.magic = FUSE2FS_MAGIC;
@@ -5274,134 +5535,23 @@ int main(int argc, char *argv[])
 		fctx.alloc_all_blocks = 1;
 	}
 
-	if (fctx.lockfile && fuse2fs_acquire_lockfile(&fctx)) {
-		ret |= 32;
+	err = fuse2fs_open(&fctx);
+	if (err) {
+		ret = 32;
 		goto out;
 	}
 
-	/* Start up the fs (while we still can use stdout) */
-	ret = 2;
-	char options[50];
-	sprintf(options, "offset=%lu", fctx.offset);
-	if (fctx.directio)
-		flags |= EXT2_FLAG_DIRECT_IO;
-
-	/*
-	 * If the filesystem is stored on a block device, the _EXCLUSIVE flag
-	 * causes libext2fs to try to open the block device with O_EXCL.  If
-	 * the block device is already opened O_EXCL by something else, the
-	 * open call returns EBUSY.
-	 *
-	 * Unfortunately, there's a nasty race between fuse2fs going through
-	 * its startup sequence (open fs, parse superblock, daemonize, create
-	 * mount, respond to FUSE_INIT) in response to a mount(8) invocation
-	 * and another process that calls umount(2) on the same mount.
-	 *
-	 * If fuse2fs is being run as a mount(8) helper and has daemonized, the
-	 * original fuse2fs subprocess exits and so will mount(8).  This can
-	 * occur before the kernel issues a FUSE_INIT request to fuse2fs.  If
-	 * a process then umount(2)'s the mount, the kernel will abort the
-	 * fuse connection.  If the FUSE_INIT request hasn't been issued, now
-	 * it won't ever be issued.  The kernel tears down the mount and
-	 * returns from umount(2), but fuse2fs has no idea that any of this has
-	 * happened because it receives no requests.
-	 *
-	 * At this point, the original fuse2fs server holds the block device
-	 * open O_EXCL.  If mount(8) is invoked again on the same device, the
-	 * new fuse2fs server will try to open the block device O_EXCL and
-	 * fail.  A crappy solution here is to retry for 5 seconds, hoping that
-	 * the first fuse2fs server will wake up and exit.
-	 *
-	 * If the filesystem is in a regular file, O_EXCL (without O_CREAT) has
-	 * no defined behavior, but it never returns EBUSY.
-	 */
-	deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
-	do {
-		err = ext2fs_open2(fctx.device, options, flags, 0, 0,
-				   unix_io_manager, &fctx.fs);
-		if ((err == EPERM || err == EACCES) &&
-		    (!fctx.ro || (flags & EXT2_FLAG_RW))) {
-			/*
-			 * Source device cannot be opened for write.  Under
-			 * these circumstances, mount(8) will try again with a
-			 * ro mount, and the kernel will open the block device
-			 * readonly.
-			 */
-			log_printf(&fctx, "%s\n",
- _("WARNING: source write-protected, mounted read-only."));
-			flags &= ~EXT2_FLAG_RW;
-			fctx.ro = 1;
-
-			/* Force the loop to run once more */
-			err = -1;
-		}
-	} while (err == -1 ||
-		 (err == EBUSY && retry_before_deadline(deadline)));
-	if (err == EBUSY) {
-		err_printf(&fctx, "%s: %s.\n",
- _("Could not lock filesystem block device"), error_message(err));
-		goto out;
-	}
+	err = fuse2fs_config_cache(&fctx);
 	if (err) {
-		err_printf(&fctx, "%s.\n", error_message(err));
-		err_printf(&fctx, "%s\n", _("Please run e2fsck -fy."));
+		ret = 32;
 		goto out;
 	}
 
-	/*
-	 * If the filesystem is stored in a regular file, take an (advisory)
-	 * exclusive lock to prevent other instances of e2fsprogs from writing
-	 * to the filesystem image.  On Linux we don't want to do this for
-	 * block devices because udev will spin forever trying to settle a
-	 * uevent and cause weird userspace stalls, and block devices have
-	 * O_EXCL so we don't need this there.
-	 */
-	if (!(fctx.fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
-		unsigned int lock_flags = IO_CHANNEL_FLOCK_TRYLOCK;
-
-		if (fctx.fs->flags & IO_FLAG_RW)
-			lock_flags |= IO_CHANNEL_FLOCK_EXCLUSIVE;
-		else
-			lock_flags |= IO_CHANNEL_FLOCK_SHARED;
-
-		deadline = init_deadline(FUSE2FS_OPEN_TIMEOUT);
-		do {
-			err = io_channel_flock(fctx.fs->io, lock_flags);
-		} while (err == EWOULDBLOCK && retry_before_deadline(deadline));
-		if (err) {
-			err_printf(&fctx, "%s: %s\n",
- _("Could not lock filesystem image"), error_message(err));
-			goto out;
-		}
-	}
-
-	fctx.fs->priv_data = &fctx;
-	fctx.blocklog = u_log2(fctx.fs->blocksize);
-	fctx.blockmask = fctx.fs->blocksize - 1;
-
-	fuse2fs_mmp_config(&fctx);
-
-	if (!fctx.cache_size)
-		fctx.cache_size = default_cache_size();
-	if (fctx.cache_size) {
-		char buf[55];
-
-		snprintf(buf, sizeof(buf), "cache_blocks=%llu",
-			 FUSE2FS_B_TO_FSBT(&fctx, fctx.cache_size));
-		err = io_channel_set_options(fctx.fs->io, buf);
-		if (err) {
-			err_printf(&fctx, "%s %lluk: %s\n",
-				   _("cannot set disk cache size to"),
-				   fctx.cache_size >> 10,
-				   error_message(err));
-			goto out;
-		}
-	}
-
-	ret = 3;
 	err = fuse2fs_check_support(&fctx);
-	if (err)
+	if (err) {
+		ret = 32;
 		goto out;
+	}
 
 	/*
 	 * ext4 can't do COW of shared blocks, so if the feature is enabled,
@@ -5410,134 +5560,16 @@ int main(int argc, char *argv[])
 	if (ext2fs_has_feature_shared_blocks(fctx.fs->super))
 		fctx.ro = 1;
 
-	if (ext2fs_has_feature_journal_needs_recovery(fctx.fs->super)) {
-		if (fctx.norecovery) {
-			log_printf(&fctx, "%s\n",
- _("Mounting read-only without recovering journal."));
-			fctx.ro = 1;
-			fctx.fs->flags &= ~EXT2_FLAG_RW;
-		} else if (!(fctx.fs->flags & EXT2_FLAG_RW)) {
-			err_printf(&fctx, "%s\n",
- _("Cannot replay journal on read-only device."));
-			ret = 32;
-			goto out;
-		} else {
-			log_printf(&fctx, "%s\n", _("Recovering journal."));
-			err = ext2fs_run_ext3_journal(&fctx.fs);
-			if (err) {
-				err_printf(&fctx, "%s.\n", error_message(err));
-				err_printf(&fctx, "%s\n",
-						_("Please run e2fsck -fy."));
-				goto out;
-			}
-
-			err = fuse2fs_check_support(&fctx);
-			if (err)
-				goto out;
-		}
-	} else if (ext2fs_has_feature_journal(fctx.fs->super)) {
-		err = ext2fs_check_ext3_journal(fctx.fs);
-		if (err) {
-			translate_error(fctx.fs, 0, err);
-			goto out;
-		}
-	}
-
-	ret = fuse2fs_check_root_dir(fctx.fs);
-	if (ret)
+	err = fuse2fs_mount(&fctx);
+	if (err) {
+		ret = 32;
 		goto out;
-
-	if (fctx.fs->flags & EXT2_FLAG_RW) {
-		if (ext2fs_has_feature_journal(fctx.fs->super))
-			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"));
 	}
 
-	if (!(fctx.fs->super->s_state & EXT2_VALID_FS))
-		err_printf(&fctx, "%s\n",
- _("Warning: Mounting unchecked fs, running e2fsck is recommended."));
-	if (fctx.fs->super->s_max_mnt_count > 0 &&
-	    fctx.fs->super->s_mnt_count >= fctx.fs->super->s_max_mnt_count)
-		err_printf(&fctx, "%s\n",
- _("Warning: Maximal mount count reached, running e2fsck is recommended."));
-	if (fctx.fs->super->s_checkinterval > 0 &&
-	    (time_t) (fctx.fs->super->s_lastcheck +
-		      fctx.fs->super->s_checkinterval) <= time(0))
-		err_printf(&fctx, "%s\n",
- _("Warning: Check time reached; running e2fsck is recommended."));
-	if (fctx.fs->super->s_last_orphan)
-		err_printf(&fctx, "%s\n",
- _("Orphans detected; running e2fsck is recommended."));
-
-	/* Clear the valid flag so that an unclean shutdown forces a fsck */
-	if (fctx.fs->flags & EXT2_FLAG_RW) {
-		fctx.fs->super->s_mnt_count++;
-		ext2fs_set_tstamp(fctx.fs->super, s_mtime, time(NULL));
-		fctx.fs->super->s_state &= ~EXT2_VALID_FS;
-		ext2fs_mark_super_dirty(fctx.fs);
-		err = ext2fs_flush2(fctx.fs, 0);
-		if (err) {
-			translate_error(fctx.fs, 0, err);
-			ret |= 32;
-			goto out;
-		}
-	}
-
-	if (!fctx.errors_behavior)
-		fctx.errors_behavior = fctx.fs->super->s_errors;
-
 	/* Initialize generation counter */
 	get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
 
-	/* Set up default fuse parameters */
-	snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
-		 "fsname=%s,attr_timeout=0" FUSE_PLATFORM_OPTS,
-		 get_subtype(argv[0]),
-		 fctx.device);
-	if (fctx.no_default_opts == 0)
-		fuse_opt_add_arg(&args, extra_args);
-
-	if (fctx.ro)
-		fuse_opt_add_arg(&args, "-oro");
-
-	if (fctx.fakeroot) {
-#ifdef HAVE_MOUNT_NODEV
-		fuse_opt_add_arg(&args,"-onodev");
-#endif
-#ifdef HAVE_MOUNT_NOSUID
-		fuse_opt_add_arg(&args,"-onosuid");
-#endif
-	}
-
-	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");
-	}
-
-	/*
-	 * Since there's a Big Kernel Lock around all the libext2fs code, we
-	 * only need to start four threads -- one to decode a request, another
-	 * to do the filesystem work, a third to transmit the reply, and a
-	 * fourth to handle fuse notifications.
-	 */
-	fuse_opt_insert_arg(&args, 1, "-omax_threads=4");
-
-	if (fctx.debug) {
-		int	i;
-
-		printf("FUSE2FS (%s): fuse arguments:", fctx.shortdev);
-		for (i = 0; i < args.argc; i++)
-			printf(" '%s'", args.argv[i]);
-		printf("\n");
-		fflush(stdout);
-	}
+	fuse2fs_compute_libfuse_args(&fctx, &args, argv[0]);
 
 	ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
 	switch(ret) {


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

* [PATCH 2/3] fuse2fs: register as an IO flusher thread
  2025-11-06 22:28 ` [PATCHSET 5/9] fuse2fs: refactor mount code Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 1/3] fuse2fs: split filesystem mounting into helper functions Darrick J. Wong
@ 2025-11-06 22:39   ` Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 3/3] fuse2fs: adjust OOM killer score if possible Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:39 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

fuse2fs is involved in the filesystem I/O path and can allocate memory
while processing I/O requests.  Therefore, we need to register that fact
with the kernel so that memory allocations done by libext2fs don't start
a round of filesystem memory reclaim, which could cause more writeout to
get dumped on fuse2fs.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 configure       |   37 +++++++++++++++++++++++++++++++++++++
 configure.ac    |   19 +++++++++++++++++++
 lib/config.h.in |    3 +++
 misc/fuse2fs.c  |   27 +++++++++++++++++++++++++++
 4 files changed, 86 insertions(+)


diff --git a/configure b/configure
index ba2e5380f6d20b..356644beed651e 100755
--- a/configure
+++ b/configure
@@ -14725,6 +14725,43 @@ then
 printf "%s\n" "#define FUSE_USE_VERSION $FUSE_USE_VERSION" >>confdefs.h
 
 fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PR_SET_IO_FLUSHER" >&5
+printf %s "checking for PR_SET_IO_FLUSHER... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+#define _GNU_SOURCE
+#include <sys/prctl.h>
+
+int
+main (void)
+{
+
+prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);
+
+  ;
+  return 0;
+}
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  have_pr_set_io_flusher=yes
+   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+if test "$have_pr_set_io_flusher" = yes; then
+
+printf "%s\n" "#define HAVE_PR_SET_IO_FLUSHER 1" >>confdefs.h
+
+fi
+
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CLOCK_MONOTONIC" >&5
 printf %s "checking for CLOCK_MONOTONIC... " >&6; }
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/configure.ac b/configure.ac
index 051161c5848072..f065cd395cf33c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1452,6 +1452,25 @@ then
 	AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
 		[Define to the version of FUSE to use])
 fi
+
+dnl
+dnl see if PR_SET_IO_FLUSHER exists
+dnl
+AC_MSG_CHECKING(for PR_SET_IO_FLUSHER)
+AC_LINK_IFELSE(
+[	AC_LANG_PROGRAM([[
+#define _GNU_SOURCE
+#include <sys/prctl.h>
+	]], [[
+prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);
+	]])
+], have_pr_set_io_flusher=yes
+   AC_MSG_RESULT(yes),
+   AC_MSG_RESULT(no))
+if test "$have_pr_set_io_flusher" = yes; then
+  AC_DEFINE(HAVE_PR_SET_IO_FLUSHER, 1, [Define to 1 if PR_SET_IO_FLUSHER is present])
+fi
+
 dnl
 dnl see if CLOCK_MONOTONIC exists
 dnl
diff --git a/lib/config.h.in b/lib/config.h.in
index 364d6e865b7115..a4d8ce1c3765ed 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -70,6 +70,9 @@
 /* Define to 1 if you have the BSD-style 'qsort_r' function. */
 #undef HAVE_BSD_QSORT_R
 
+/* Define to 1 if PR_SET_IO_FLUSHER is present */
+#undef HAVE_PR_SET_IO_FLUSHER
+
 /* Define to 1 if you have the Mac OS X function
    CFLocaleCopyPreferredLanguages in the CoreFoundation framework. */
 #undef HAVE_CFLOCALECOPYPREFERREDLANGUAGES
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index e0134834d342dd..86e0222a51369a 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -17,6 +17,7 @@
 # include <linux/fs.h>
 # include <linux/falloc.h>
 # include <linux/xattr.h>
+# include <sys/prctl.h>
 #endif
 #ifdef HAVE_SYS_XATTR_H
 #include <sys/xattr.h>
@@ -5483,6 +5484,30 @@ static void fuse2fs_compute_libfuse_args(struct fuse2fs *ff,
 	}
 }
 
+/*
+ * Try to register as a filesystem I/O server process so that our memory
+ * allocations don't cause fs reclaim.
+ */
+static void try_set_io_flusher(struct fuse2fs *ff)
+{
+#ifdef HAVE_PR_SET_IO_FLUSHER
+	int ret = prctl(PR_GET_IO_FLUSHER, 0, 0, 0, 0);
+
+	/*
+	 * positive ret means it's already set, negative means we can't even
+	 * look at the value so don't bother setting it
+	 */
+	if (ret)
+		return;
+
+	ret = prctl(PR_SET_IO_FLUSHER, 1, 0, 0, 0);
+	if (ret < 0)
+		err_printf(ff, "%s: %s.\n",
+ _("Could not register as IO flusher thread"),
+			   strerror(errno));
+#endif
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
@@ -5528,6 +5553,8 @@ int main(int argc, char *argv[])
 		goto out;
 	}
 
+	try_set_io_flusher(&fctx);
+
 	/* Will we allow users to allocate every last block? */
 	if (getenv("FUSE2FS_ALLOC_ALL_BLOCKS")) {
 		log_printf(&fctx, "%s\n",


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

* [PATCH 3/3] fuse2fs: adjust OOM killer score if possible
  2025-11-06 22:28 ` [PATCHSET 5/9] fuse2fs: refactor mount code Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 1/3] fuse2fs: split filesystem mounting into helper functions Darrick J. Wong
  2025-11-06 22:39   ` [PATCH 2/3] fuse2fs: register as an IO flusher thread Darrick J. Wong
@ 2025-11-06 22:40   ` Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Users don't like it when their filesystems go down unexpectedly.  Set
the OOM score adjustment to -500 to try to prevent this, particularly
because fuse2fs doesn't support journal transactions.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 86e0222a51369a..d6be5e9968567c 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -242,6 +242,7 @@ struct fuse2fs {
 
 	int logfd;
 	int blocklog;
+	int oom_score_adj;
 	unsigned int blockmask;
 	unsigned long offset;
 	unsigned int next_generation;
@@ -5298,6 +5299,7 @@ static struct fuse_opt fuse2fs_opts[] = {
 	FUSE2FS_OPT("norecovery",	norecovery,		1),
 	FUSE2FS_OPT("noload",		norecovery,		1),
 	FUSE2FS_OPT("offset=%lu",	offset,			0),
+	FUSE2FS_OPT("oom_score_adj=%d",	oom_score_adj,		-500),
 	FUSE2FS_OPT("kernel",		kernel,			1),
 	FUSE2FS_OPT("directio",		directio,		1),
 	FUSE2FS_OPT("acl",		acl,			1),
@@ -5508,19 +5510,31 @@ static void try_set_io_flusher(struct fuse2fs *ff)
 #endif
 }
 
+/* Try to adjust the OOM score so that we don't get killed */
+static void try_adjust_oom_score(struct fuse2fs *ff)
+{
+	FILE *fp = fopen("/proc/self/oom_score_adj", "w+");
+
+	if (!fp)
+		return;
+
+	fprintf(fp, "%d\n", ff->oom_score_adj);
+	fclose(fp);
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
-	struct fuse2fs fctx;
+	struct fuse2fs fctx = {
+		.magic = FUSE2FS_MAGIC,
+		.logfd = -1,
+		.bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER,
+		.oom_score_adj = -500,
+	};
 	errcode_t err;
 	FILE *orig_stderr = stderr;
 	int ret;
 
-	memset(&fctx, 0, sizeof(fctx));
-	fctx.magic = FUSE2FS_MAGIC;
-	fctx.logfd = -1;
-	fctx.bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
-
 	ret = fuse_opt_parse(&args, &fctx, fuse2fs_opts, fuse2fs_opt_proc);
 	if (ret)
 		exit(1);
@@ -5554,6 +5568,7 @@ int main(int argc, char *argv[])
 	}
 
 	try_set_io_flusher(&fctx);
+	try_adjust_oom_score(&fctx);
 
 	/* Will we allow users to allocate every last block? */
 	if (getenv("FUSE2FS_ALLOC_ALL_BLOCKS")) {


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

* [PATCH 1/4] fuse2fs: hook library error message printing
  2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
@ 2025-11-06 22:40   ` Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 2/4] fuse2fs: print the function name in error messages, not the file name Darrick J. Wong
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Hook the com_err library so that error messages coming from libext2fs
such as:
Illegal block number passed to ext2fs_test_block_bitmap #9462 for block bitmap for /dev/sda

are actually printed with the standard "FUSE2FS (sda):" prefix.
Libraries shouldn't be printing that kind of stuff, but it is what it
is, and what it is is against the normal conventions.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index d6be5e9968567c..c2896de2316bce 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -201,6 +201,8 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)
 errcode_t ext2fs_check_ext3_journal(ext2_filsys fs);
 errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs);
 
+const char *err_shortdev;
+
 #ifdef CONFIG_JBD_DEBUG		/* Enabled by configure --enable-jbd-debug */
 int journal_enable_debug = -1;
 #endif
@@ -5522,6 +5524,18 @@ static void try_adjust_oom_score(struct fuse2fs *ff)
 	fclose(fp);
 }
 
+static void fuse2fs_com_err_proc(const char *whoami, errcode_t code,
+				 const char *fmt, va_list args)
+{
+	fprintf(stderr, "FUSE2FS (%s): ", err_shortdev ? err_shortdev : "?");
+	if (whoami)
+		fprintf(stderr, "%s: ", whoami);
+	fprintf(stderr, "%s ", error_message(code));
+        vfprintf(stderr, fmt, args);
+	fprintf(stderr, "\n");
+	fflush(stderr);
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
@@ -5551,6 +5565,10 @@ int main(int argc, char *argv[])
 	else
 		fctx.shortdev = fctx.device;
 
+	/* capture library error messages */
+	err_shortdev = fctx.shortdev;
+	set_com_err_hook(fuse2fs_com_err_proc);
+
 #ifdef ENABLE_NLS
 	setlocale(LC_MESSAGES, "");
 	setlocale(LC_CTYPE, "");
@@ -5650,6 +5668,8 @@ int main(int argc, char *argv[])
 	}
 	fuse2fs_mmp_destroy(&fctx);
 	fuse2fs_unmount(&fctx);
+	reset_com_err_hook();
+	err_shortdev = NULL;
 	if (fctx.device)
 		free(fctx.device);
 	pthread_mutex_destroy(&fctx.bfl);


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

* [PATCH 2/4] fuse2fs: print the function name in error messages, not the file name
  2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 1/4] fuse2fs: hook library error message printing Darrick J. Wong
@ 2025-11-06 22:40   ` Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 3/4] fuse2fs: improve tracing for file range operations Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 4/4] fuse2fs: record thread id in debug trace data Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

It would be nice to know which fuse op actually causes failures such as:
FUSE2FS (sda4): Directory block checksum does not match directory block at ../../misc/fuse2fs.c:819.

The filename is utterly pointless, there's only one for the whole
daemon.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index c2896de2316bce..cede10765f5c1b 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -293,9 +293,9 @@ struct fuse2fs {
 	__FUSE2FS_CHECK_CONTEXT((ff), abort())
 
 static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
-			     const char *file, int line);
+			     const char *func, int line);
 #define translate_error(fs, ino, err) __translate_error((fs), (ino), (err), \
-			__FILE__, __LINE__)
+			__func__, __LINE__)
 
 /* for macosx */
 #ifndef W_OK
@@ -5678,7 +5678,7 @@ int main(int argc, char *argv[])
 }
 
 static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
-			     const char *file, int line)
+			     const char *func, int line)
 {
 	struct timespec now;
 	int ret = err;
@@ -5805,10 +5805,10 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 
 	if (ino)
 		err_printf(ff, "%s (inode #%d) at %s:%d.\n",
-			error_message(err), ino, file, line);
+			error_message(err), ino, func, line);
 	else
 		err_printf(ff, "%s at %s:%d.\n",
-			error_message(err), file, line);
+			error_message(err), func, line);
 
 	/* Make a note in the error log */
 	get_now(&now);
@@ -5816,14 +5816,14 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 	fs->super->s_last_error_ino = ino;
 	fs->super->s_last_error_line = line;
 	fs->super->s_last_error_block = err; /* Yeah... */
-	strncpy((char *)fs->super->s_last_error_func, file,
+	strncpy((char *)fs->super->s_last_error_func, func,
 		sizeof(fs->super->s_last_error_func));
 	if (ext2fs_get_tstamp(fs->super, s_first_error_time) == 0) {
 		ext2fs_set_tstamp(fs->super, s_first_error_time, now.tv_sec);
 		fs->super->s_first_error_ino = ino;
 		fs->super->s_first_error_line = line;
 		fs->super->s_first_error_block = err;
-		strncpy((char *)fs->super->s_first_error_func, file,
+		strncpy((char *)fs->super->s_first_error_func, func,
 			sizeof(fs->super->s_first_error_func));
 	}
 


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

* [PATCH 3/4] fuse2fs: improve tracing for file range operations
  2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 1/4] fuse2fs: hook library error message printing Darrick J. Wong
  2025-11-06 22:40   ` [PATCH 2/4] fuse2fs: print the function name in error messages, not the file name Darrick J. Wong
@ 2025-11-06 22:40   ` Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 4/4] fuse2fs: record thread id in debug trace data Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:40 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Improve the tracing for read, write, readdir, and fallocate by reporting
the inode number and the file range in all tracepoints relating to file
IO.  Make the file ranges hexadecimal to make it easier for the
programmer to convert bytes to block numbers and back.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index cede10765f5c1b..9927a5d14c8fd2 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -3519,8 +3519,8 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
-		   (intmax_t) offset, len);
+	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
+		   (unsigned long long)offset, len);
 	fs = fuse2fs_start(ff);
 	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
 	if (err) {
@@ -3573,8 +3573,8 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	dbg_printf(ff, "%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino,
-		   (intmax_t) offset, (intmax_t) len);
+	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
+		   (unsigned long long) offset, len);
 	fs = fuse2fs_start(ff);
 	if (!fs_writeable(fs)) {
 		ret = -EROFS;
@@ -4146,12 +4146,13 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 	if (i->startpos >= i->dirpos)
 		return 0;
 
-	dbg_printf(i->ff, "READDIR%s %u dirpos %llu\n",
+	dbg_printf(i->ff, "READDIR%s ino=%d %u offset=0x%llx\n",
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
 #else
 			"",
 #endif
+			dir,
 			i->nr++,
 			(unsigned long long)i->dirpos);
 
@@ -4200,7 +4201,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	dbg_printf(ff, "%s: ino=%d offset=%llu\n", __func__, fh->ino,
+	dbg_printf(ff, "%s: ino=%d offset=0x%llx\n", __func__, fh->ino,
 			(unsigned long long)offset);
 	i.fs = fuse2fs_start(ff);
 	i.buf = buf;
@@ -4786,7 +4787,7 @@ static int ioctl_fitrim(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	    fr->len < fs->blocksize)
 		return -EINVAL;
 
-	dbg_printf(ff, "%s: start=%llu end=%llu minlen=%llu\n", __func__,
+	dbg_printf(ff, "%s: start=0x%llx end=0x%llx minlen=0x%llx\n", __func__,
 		   start, end, minlen);
 
 	if (start < fs->super->s_first_data_block)
@@ -4957,8 +4958,12 @@ static int fuse2fs_allocate_range(struct fuse2fs *ff,
 
 	start = FUSE2FS_B_TO_FSBT(ff, offset);
 	end = FUSE2FS_B_TO_FSBT(ff, offset + len - 1);
-	dbg_printf(ff, "%s: ino=%d mode=0x%x start=%llu end=%llu\n", __func__,
-		   fh->ino, mode, start, end);
+	dbg_printf(ff, "%s: ino=%d mode=0x%x offset=0x%llx len=0x%llx start=0x%llx end=0x%llx\n",
+		   __func__, fh->ino, mode,
+		   (unsigned long long)offset,
+		   (unsigned long long)len,
+		   (unsigned long long)start,
+		   (unsigned long long)end);
 	if (!fs_can_allocate(ff, FUSE2FS_B_TO_FSB(ff, len)))
 		return -ENOSPC;
 
@@ -5029,6 +5034,10 @@ static errcode_t clean_block_middle(struct fuse2fs *ff, ext2_ino_t ino,
 	if (err)
 		return err;
 
+	dbg_printf(ff, "%s: ino=%d offset=0x%llx len=0x%llx\n",
+		   __func__, ino,
+		   (unsigned long long)offset + residue,
+		   (unsigned long long)len);
 	memset(*buf + residue, 0, len);
 
 	return io_channel_write_blk64(fs->io, blk, 1, *buf);
@@ -5065,10 +5074,19 @@ static errcode_t clean_block_edge(struct fuse2fs *ff, ext2_ino_t ino,
 	if (!blk || (retflags & BMAP_RET_UNINIT))
 		return 0;
 
-	if (clean_before)
+	if (clean_before) {
+		dbg_printf(ff, "%s: ino=%d before offset=0x%llx len=0x%llx\n",
+			   __func__, ino,
+			   (unsigned long long)offset,
+			   (unsigned long long)residue);
 		memset(*buf, 0, residue);
-	else
+	} else {
+		dbg_printf(ff, "%s: ino=%d after offset=0x%llx len=0x%llx\n",
+			   __func__, ino,
+			   (unsigned long long)offset,
+			   (unsigned long long)fs->blocksize - residue);
 		memset(*buf + residue, 0, fs->blocksize - residue);
+	}
 
 	return io_channel_write_blk64(fs->io, blk, 1, *buf);
 }
@@ -5083,9 +5101,6 @@ static int fuse2fs_punch_range(struct fuse2fs *ff,
 	errcode_t err;
 	char *buf = NULL;
 
-	dbg_printf(ff, "%s: offset=%jd len=%jd\n", __func__,
-		   (intmax_t) offset, (intmax_t) len);
-
 	/* kernel ext4 punch requires this flag to be set */
 	if (!(mode & FL_KEEP_SIZE_FLAG))
 		return -EINVAL;
@@ -5100,8 +5115,12 @@ static int fuse2fs_punch_range(struct fuse2fs *ff,
 	end = FUSE2FS_B_TO_FSBT(ff, round_down(offset + len, fs->blocksize));
 
 	dbg_printf(ff,
- "%s: ino=%d mode=0x%x offset=0x%jx len=0x%jx start=0x%llx end=0x%llx\n",
-		   __func__, fh->ino, mode, offset, len, start, end);
+ "%s: ino=%d mode=0x%x offset=0x%llx len=0x%llx start=0x%llx end=0x%llx\n",
+		   __func__, fh->ino, mode,
+		   (unsigned long long)offset,
+		   (unsigned long long)len,
+		   (unsigned long long)start,
+		   (unsigned long long)end);
 
 	err = fuse2fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -5185,6 +5204,12 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 		ret = -EROFS;
 		goto out;
 	}
+
+	dbg_printf(ff, "%s: ino=%d mode=0x%x start=0x%llx end=0x%llx\n", __func__,
+		   fh->ino, mode,
+		   (unsigned long long)offset,
+		   (unsigned long long)offset + len);
+
 	if (mode & FL_ZERO_RANGE_FLAG)
 		ret = fuse2fs_zero_range(ff, fh, mode, offset, len);
 	else if (mode & FL_PUNCH_HOLE_FLAG)


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

* [PATCH 4/4] fuse2fs: record thread id in debug trace data
  2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
                     ` (2 preceding siblings ...)
  2025-11-06 22:40   ` [PATCH 3/4] fuse2fs: improve tracing for file range operations Darrick J. Wong
@ 2025-11-06 22:41   ` Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Record the thread ID in the debug trace data so that we can trace
operations going through the fuse server more easily.

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 9927a5d14c8fd2..33e456aa0a964c 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -148,7 +148,7 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)
 
 #define dbg_printf(fuse2fs, format, ...) \
 	while ((fuse2fs)->debug) { \
-		printf("FUSE2FS (%s): " format, (fuse2fs)->shortdev, ##__VA_ARGS__); \
+		printf("FUSE2FS (%s): tid=%d " format, (fuse2fs)->shortdev, gettid(), ##__VA_ARGS__); \
 		fflush(stdout); \
 		break; \
 	}


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

* [PATCH 1/3] fuse2fs: pass a struct fuse2fs to fs_writeable
  2025-11-06 22:29 ` [PATCHSET 7/9] fuse2fs: better tracking of writable state Darrick J. Wong
@ 2025-11-06 22:41   ` Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 2/3] fuse2fs: track our own writable state Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 3/3] fuse2fs: enable the shutdown ioctl Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Pass the outer fuse2fs context to fs_writable in preparation for the
next patch.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 33e456aa0a964c..1aa391b5a56456 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -882,8 +882,10 @@ static int fs_can_allocate(struct fuse2fs *ff, blk64_t num)
 	return ext2fs_free_blocks_count(fs->super) > reserved + num;
 }
 
-static int fs_writeable(ext2_filsys fs)
+static int fuse2fs_is_writeable(struct fuse2fs *ff)
 {
+	ext2_filsys fs = ff->fs;
+
 	return (fs->flags & EXT2_FLAG_RW) && (fs->super->s_error_count == 0);
 }
 
@@ -912,12 +914,10 @@ static inline int want_check_owner(struct fuse2fs *ff,
 static int check_iflags_access(struct fuse2fs *ff, ext2_ino_t ino,
 			       const struct ext2_inode *inode, int mask)
 {
-	ext2_filsys fs = ff->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))
+	if ((mask & (W_OK | A_OK)) && !fuse2fs_is_writeable(ff))
 		return -EROFS;
 
 	dbg_printf(ff, "access ino=%d mask=e%s%s%s%s iflags=0x%x\n",
@@ -950,7 +950,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 | A_OK)) && !fs_writeable(fs))
+	if ((mask & (W_OK | A_OK)) && !fuse2fs_is_writeable(ff))
 		return -EROFS;
 
 	err = ext2fs_read_inode(fs, ino, &inode);
@@ -1761,7 +1761,7 @@ static int op_readlink(const char *path, char *buf, size_t len)
 	}
 	buf[len] = 0;
 
-	if (fs_writeable(fs)) {
+	if (fuse2fs_is_writeable(ff)) {
 		ret = update_atime(fs, ino);
 		if (ret)
 			goto out;
@@ -3549,7 +3549,7 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 		goto out;
 	}
 
-	if (fh->check_flags != X_OK && fs_writeable(fs)) {
+	if (fh->check_flags != X_OK && fuse2fs_is_writeable(ff)) {
 		ret = update_atime(fs, fh->ino);
 		if (ret)
 			goto out;
@@ -3576,7 +3576,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
 		   (unsigned long long) offset, len);
 	fs = fuse2fs_start(ff);
-	if (!fs_writeable(fs)) {
+	if (!fuse2fs_is_writeable(ff)) {
 		ret = -EROFS;
 		goto out;
 	}
@@ -3644,7 +3644,7 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 	fs = fuse2fs_start(ff);
 
 	if ((fp->flags & O_SYNC) &&
-	    fs_writeable(fs) &&
+	    fuse2fs_is_writeable(ff) &&
 	    (fh->open_flags & EXT2_FILE_WRITE)) {
 		err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
 		if (err)
@@ -3674,7 +3674,7 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	fs = fuse2fs_start(ff);
 	/* For now, flush everything, even if it's slow */
-	if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
+	if (fuse2fs_is_writeable(ff) && fh->open_flags & EXT2_FILE_WRITE) {
 		err = ext2fs_flush2(fs, 0);
 		if (err)
 			ret = translate_error(fs, fh->ino, err);
@@ -4212,7 +4212,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 		goto out;
 	}
 
-	if (fs_writeable(i.fs)) {
+	if (fuse2fs_is_writeable(ff)) {
 		ret = update_atime(i.fs, fh->ino);
 		if (ret)
 			goto out;
@@ -4394,7 +4394,7 @@ static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, fh->ino,
 		   (intmax_t) len);
 	fs = fuse2fs_start(ff);
-	if (!fs_writeable(fs)) {
+	if (!fuse2fs_is_writeable(ff)) {
 		ret = -EROFS;
 		goto out;
 	}
@@ -4772,7 +4772,7 @@ static int ioctl_fitrim(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	blk64_t max_blks = ext2fs_blocks_count(fs->super);
 	errcode_t err = 0;
 
-	if (!fs_writeable(fs))
+	if (!fuse2fs_is_writeable(ff))
 		return -EROFS;
 
 	start = FUSE2FS_B_TO_FSBT(ff, fr->start);
@@ -5190,7 +5190,6 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
-	ext2_filsys fs;
 	int ret;
 
 	/* Catch unknown flags */
@@ -5199,8 +5198,8 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	FUSE2FS_CHECK_HANDLE(ff, fh);
-	fs = fuse2fs_start(ff);
-	if (!fs_writeable(fs)) {
+	fuse2fs_start(ff);
+	if (!fuse2fs_is_writeable(ff)) {
 		ret = -EROFS;
 		goto out;
 	}


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

* [PATCH 2/3] fuse2fs: track our own writable state
  2025-11-06 22:29 ` [PATCHSET 7/9] fuse2fs: better tracking of writable state Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 1/3] fuse2fs: pass a struct fuse2fs to fs_writeable Darrick J. Wong
@ 2025-11-06 22:41   ` Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 3/3] fuse2fs: enable the shutdown ioctl Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Track our own willingness to write to the filesystem in a separate
variable from that of libext2fs.  There's a small window between opening
the filesystem and mounting it where the library is rw but we might fail
a mount task and therefore don't want to write anything more.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 1aa391b5a56456..8d5b705280b72f 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -219,6 +219,11 @@ struct fuse2fs_file_handle {
 	int check_flags;
 };
 
+enum fuse2fs_opstate {
+	F2OP_READONLY,
+	F2OP_WRITABLE,
+};
+
 /* Main program context */
 #define FUSE2FS_MAGIC		(0xEF53DEADUL)
 struct fuse2fs {
@@ -242,6 +247,7 @@ struct fuse2fs {
 	int acl;
 	int dirsync;
 
+	enum fuse2fs_opstate opstate;
 	int logfd;
 	int blocklog;
 	int oom_score_adj;
@@ -553,7 +559,7 @@ static bool fuse2fs_mmp_wanted(const struct fuse2fs *ff)
 	ext2_filsys fs = ff->fs;
 
 	if (!fs || !ext2fs_has_feature_mmp(fs->super) ||
-	    !(fs->flags & EXT2_FLAG_RW) || (fs->flags & EXT2_FLAG_SKIP_MMP))
+	    ff->opstate != F2OP_WRITABLE || (fs->flags & EXT2_FLAG_SKIP_MMP))
 		return false;
 	return true;
 }
@@ -767,8 +773,6 @@ static int update_atime(ext2_filsys fs, ext2_ino_t ino)
 	struct timespec atime, mtime, now;
 	double datime, dmtime, dnow;
 
-	if (!(fs->flags & EXT2_FLAG_RW))
-		return 0;
 	err = fuse2fs_read_inode(fs, ino, &inode);
 	if (err)
 		return translate_error(fs, ino, err);
@@ -884,9 +888,8 @@ static int fs_can_allocate(struct fuse2fs *ff, blk64_t num)
 
 static int fuse2fs_is_writeable(struct fuse2fs *ff)
 {
-	ext2_filsys fs = ff->fs;
-
-	return (fs->flags & EXT2_FLAG_RW) && (fs->super->s_error_count == 0);
+	return ff->opstate == F2OP_WRITABLE &&
+		(ff->fs->super->s_error_count == 0);
 }
 
 static inline int is_superuser(struct fuse2fs *ff, struct fuse_context *ctxt)
@@ -1135,6 +1138,7 @@ static errcode_t fuse2fs_open(struct fuse2fs *ff)
 	}
 
 	snprintf(options, sizeof(options) - 1, "offset=%lu", ff->offset);
+	ff->opstate = F2OP_READONLY;
 
 	if (ff->directio)
 		flags |= EXT2_FLAG_DIRECT_IO;
@@ -1337,6 +1341,7 @@ static int fuse2fs_mount(struct fuse2fs *ff)
  _("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"));
+		ff->opstate = F2OP_WRITABLE;
 	}
 
 	if (!(fs->super->s_state & EXT2_VALID_FS))
@@ -1359,7 +1364,7 @@ static int fuse2fs_mount(struct fuse2fs *ff)
 		ff->errors_behavior = fs->super->s_errors;
 
 	/* Clear the valid flag so that an unclean shutdown forces a fsck */
-	if (fs->flags & EXT2_FLAG_RW) {
+	if (ff->opstate == F2OP_WRITABLE) {
 		fs->super->s_mnt_count++;
 		ext2fs_set_tstamp(fs->super, s_mtime, time(NULL));
 		fs->super->s_state &= ~EXT2_VALID_FS;
@@ -1383,7 +1388,7 @@ static void op_destroy(void *p EXT2FS_ATTR((unused)))
 	fs = fuse2fs_start(ff);
 
 	dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
-	if (fs->flags & EXT2_FLAG_RW) {
+	if (ff->opstate == F2OP_WRITABLE) {
 		fs->super->s_state |= EXT2_VALID_FS;
 		if (fs->super->s_error_count)
 			fs->super->s_state |= EXT2_ERROR_FS;
@@ -1574,7 +1579,7 @@ static void *op_init(struct fuse_conn_info *conn
 	cfg->nullpath_ok = 1;
 #endif
 
-	if (ff->fs->flags & EXT2_FLAG_RW)
+	if (ff->opstate == F2OP_WRITABLE)
 		fuse2fs_read_bitmaps(ff);
 
 	/*
@@ -3724,7 +3729,7 @@ static int op_statfs(const char *path EXT2FS_ATTR((unused)),
 	fsid ^= *f;
 	buf->f_fsid = fsid;
 	buf->f_flag = 0;
-	if (!(fs->flags & EXT2_FLAG_RW))
+	if (ff->opstate != F2OP_WRITABLE)
 		buf->f_flag |= ST_RDONLY;
 	buf->f_namemax = EXT2_NAME_LEN;
 	fuse2fs_finish(ff, 0);
@@ -5568,6 +5573,7 @@ int main(int argc, char *argv[])
 		.logfd = -1,
 		.bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER,
 		.oom_score_adj = -500,
+		.opstate = F2OP_WRITABLE,
 	};
 	errcode_t err;
 	FILE *orig_stderr = stderr;
@@ -5861,9 +5867,11 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
  _("Continuing after errors; is this a good idea?"));
 		break;
 	case EXT2_ERRORS_RO:
-		if (fs->flags & EXT2_FLAG_RW)
+		if (ff->opstate == F2OP_WRITABLE) {
 			err_printf(ff, "%s\n",
  _("Remounting read-only due to errors."));
+			ff->opstate = F2OP_READONLY;
+		}
 		fuse2fs_mmp_cancel(ff);
 		fs->flags &= ~EXT2_FLAG_RW;
 		break;


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

* [PATCH 3/3] fuse2fs: enable the shutdown ioctl
  2025-11-06 22:29 ` [PATCHSET 7/9] fuse2fs: better tracking of writable state Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 1/3] fuse2fs: pass a struct fuse2fs to fs_writeable Darrick J. Wong
  2025-11-06 22:41   ` [PATCH 2/3] fuse2fs: track our own writable state Darrick J. Wong
@ 2025-11-06 22:41   ` Darrick J. Wong
  2 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:41 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Implement a bastardized version of EXT4_IOC_SHUTDOWN, because the people
who invented the ioctl got the direction wrong, so we can't actually
read the flags.  So all we do is flush the filesystem, clear the
writable flags, and hope that's what the user wanted.

Since we change __FUSE2FS_CHECK_CONTEXT to return an error code on a
shut down filesystem, remove FUSE2FS_CHECK_CONTEXT_RETURN so that
op_destroy always tears everything down and logs the unmount message.
Later on in the iomap patchset this will become critical because we need
to try to close the block device before unmount completes to avoid
problems with fstests.

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 8d5b705280b72f..51e6b3b1969d62 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -222,6 +222,7 @@ struct fuse2fs_file_handle {
 enum fuse2fs_opstate {
 	F2OP_READONLY,
 	F2OP_WRITABLE,
+	F2OP_SHUTDOWN,
 };
 
 /* Main program context */
@@ -280,7 +281,7 @@ struct fuse2fs {
 		} \
 	} while (0)
 
-#define __FUSE2FS_CHECK_CONTEXT(ff, retcode) \
+#define __FUSE2FS_CHECK_CONTEXT(ff, retcode, shutcode) \
 	do { \
 		if ((ff) == NULL || (ff)->magic != FUSE2FS_MAGIC) { \
 			fprintf(stderr, \
@@ -289,14 +290,17 @@ struct fuse2fs {
 			fflush(stderr); \
 			retcode; \
 		} \
+		if ((ff)->opstate == F2OP_SHUTDOWN) { \
+			shutcode; \
+		} \
 	} while (0)
 
 #define FUSE2FS_CHECK_CONTEXT(ff) \
-	__FUSE2FS_CHECK_CONTEXT((ff), return -EUCLEAN)
+	__FUSE2FS_CHECK_CONTEXT((ff), return -EUCLEAN, return -EIO)
 #define FUSE2FS_CHECK_CONTEXT_DESTROY(ff) \
-	__FUSE2FS_CHECK_CONTEXT((ff), return)
+	__FUSE2FS_CHECK_CONTEXT((ff), return, /* do not return */)
 #define FUSE2FS_CHECK_CONTEXT_INIT(ff) \
-	__FUSE2FS_CHECK_CONTEXT((ff), abort())
+	__FUSE2FS_CHECK_CONTEXT((ff), abort(), abort())
 
 static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 			     const char *func, int line);
@@ -4863,6 +4867,36 @@ static int ioctl_fitrim(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 }
 #endif /* FITRIM */
 
+#ifndef EXT4_IOC_SHUTDOWN
+# define EXT4_IOC_SHUTDOWN	_IOR('X', 125, __u32)
+#endif
+
+static int ioctl_shutdown(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
+			  void *data)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	ext2_filsys fs = ff->fs;
+
+	if (!is_superuser(ff, ctxt))
+		return -EPERM;
+
+	err_printf(ff, "%s.\n", _("shut down requested"));
+
+	fuse2fs_mmp_cancel(ff);
+
+	/*
+	 * EXT4_IOC_SHUTDOWN inherited the inverted polarity on the ioctl
+	 * direction from XFS.  Unfortunately, that means we can't implement
+	 * any of the flags.  Flush whatever is dirty and shut down.
+	 */
+	if (ff->opstate == F2OP_WRITABLE)
+		ext2fs_flush2(fs, 0);
+	ff->opstate = F2OP_SHUTDOWN;
+	fs->flags &= ~EXT2_FLAG_RW;
+
+	return 0;
+}
+
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
 static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
@@ -4909,6 +4943,9 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 		ret = ioctl_fitrim(ff, fh, data);
 		break;
 #endif
+	case EXT4_IOC_SHUTDOWN:
+		ret = ioctl_shutdown(ff, fh, data);
+		break;
 	default:
 		dbg_printf(ff, "%s: Unknown ioctl %d\n", __func__, cmd);
 		ret = -ENOTTY;


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

* [PATCH 1/4] fuse2fs: bump library version
  2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
@ 2025-11-06 22:42   ` Darrick J. Wong
  2025-11-06 22:42   ` [PATCH 2/4] fuse2fs: wrap the fuse_set_feature_flag helper for older libfuse Darrick J. Wong
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:42 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Bump the library version so we can take advantage of new functionality
since libfuse 3.5.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 configure    |    4 ++--
 configure.ac |    4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)


diff --git a/configure b/configure
index 356644beed651e..71750b1a8ee972 100755
--- a/configure
+++ b/configure
@@ -14687,14 +14687,14 @@ fi
 
 if test "$FUSE_LIB" = "-lfuse3"
 then
-	FUSE_USE_VERSION=35
+	FUSE_USE_VERSION=314
 	CFLAGS="$fuse3_CFLAGS $CFLAGS"
 	FUSE_LIB="$fuse3_LIBS"
 	       for ac_header in pthread.h fuse.h
 do :
   as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
 ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 35
+#define FUSE_USE_VERSION 314
 #ifdef __linux__
 #include <linux/fs.h>
 #include <linux/falloc.h>
diff --git a/configure.ac b/configure.ac
index f065cd395cf33c..0591999b52b019 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1431,13 +1431,13 @@ AC_SUBST(FUSE_LIB)
 AC_SUBST(FUSE_CMT)
 if test "$FUSE_LIB" = "-lfuse3"
 then
-	FUSE_USE_VERSION=35
+	FUSE_USE_VERSION=314
 	CFLAGS="$fuse3_CFLAGS $CFLAGS"
 	FUSE_LIB="$fuse3_LIBS"
 	AC_CHECK_HEADERS([pthread.h fuse.h], [],
 		[AC_MSG_FAILURE([Cannot find fuse3 fuse2fs headers.])],
 [#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 35
+#define FUSE_USE_VERSION 314
 #ifdef __linux__
 #include <linux/fs.h>
 #include <linux/falloc.h>


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

* [PATCH 2/4] fuse2fs: wrap the fuse_set_feature_flag helper for older libfuse
  2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
  2025-11-06 22:42   ` [PATCH 1/4] fuse2fs: bump library version Darrick J. Wong
@ 2025-11-06 22:42   ` Darrick J. Wong
  2025-11-06 22:42   ` [PATCH 3/4] fuse2fs: disable nfs exports Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 4/4] fuse2fs: drop fuse 2.x support code Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:42 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Create a compatibility wrapper for fuse_set_feature_flag if the libfuse
version is older than the one where that function was introduced (3.17).

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


diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 51e6b3b1969d62..2468e7e1017d59 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1542,6 +1542,19 @@ static int fuse2fs_read_bitmaps(struct fuse2fs *ff)
 	return 0;
 }
 
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 17)
+static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,
+					 uint64_t flag)
+{
+	if (conn->capable & flag) {
+		conn->want |= flag;
+		return 1;
+	}
+
+	return 0;
+}
+#endif
+
 static void *op_init(struct fuse_conn_info *conn
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			, struct fuse_config *cfg EXT2FS_ATTR((unused))
@@ -1566,14 +1579,14 @@ static void *op_init(struct fuse_conn_info *conn
 	fs = ff->fs;
 	dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
 #ifdef FUSE_CAP_IOCTL_DIR
-	conn->want |= FUSE_CAP_IOCTL_DIR;
+	fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR);
 #endif
 #ifdef FUSE_CAP_POSIX_ACL
 	if (ff->acl)
-		conn->want |= FUSE_CAP_POSIX_ACL;
+		fuse_set_feature_flag(conn, FUSE_CAP_POSIX_ACL);
 #endif
 #ifdef FUSE_CAP_CACHE_SYMLINKS
-	conn->want |= FUSE_CAP_CACHE_SYMLINKS;
+	fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
 #endif
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	conn->time_gran = 1;
@@ -1593,6 +1606,19 @@ static void *op_init(struct fuse_conn_info *conn
 	 */
 	fuse2fs_mmp_start(ff);
 
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+	/*
+	 * THIS MUST GO LAST!
+	 *
+	 * fuse_set_feature_flag in 3.17.0 has a strange bug: it sets feature
+	 * flags in conn->want_ext, but not conn->want.  Upon return to
+	 * libfuse, the lower level library observes that want and want_ext
+	 * have gotten out of sync, and refuses to mount.  Therefore,
+	 * synchronize the two.  This bug went away in 3.17.3, but we're stuck
+	 * with this forever because Debian trixie released with 3.17.2.
+	 */
+	conn->want = conn->want_ext & 0xFFFFFFFF;
+#endif
 	return ff;
 }
 


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

* [PATCH 3/4] fuse2fs: disable nfs exports
  2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
  2025-11-06 22:42   ` [PATCH 1/4] fuse2fs: bump library version Darrick J. Wong
  2025-11-06 22:42   ` [PATCH 2/4] fuse2fs: wrap the fuse_set_feature_flag helper for older libfuse Darrick J. Wong
@ 2025-11-06 22:42   ` Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 4/4] fuse2fs: drop fuse 2.x support code Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:42 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

The kernel fuse driver can export its own handles, but it doesn't
actually talk to the fuse server about those handles.  Hence they don't
survive unmount/mount cycles like regular ext4.  Disable them, because
they cause fstests regressions and it's not clear that they're suitable
for NFS export, at least not as most people understand ext4 NFS exports.

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 2468e7e1017d59..8a22147d904c27 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -1588,6 +1588,9 @@ static void *op_init(struct fuse_conn_info *conn
 #ifdef FUSE_CAP_CACHE_SYMLINKS
 	fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
 #endif
+#ifdef FUSE_CAP_NO_EXPORT_SUPPORT
+	fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);
+#endif
 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	conn->time_gran = 1;
 	cfg->use_ino = 1;


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

* [PATCH 4/4] fuse2fs: drop fuse 2.x support code
  2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
                     ` (2 preceding siblings ...)
  2025-11-06 22:42   ` [PATCH 3/4] fuse2fs: disable nfs exports Darrick J. Wong
@ 2025-11-06 22:43   ` Darrick J. Wong
  3 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
  To: tytso; +Cc: amir73il, linux-ext4

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

We only enable fuse2fs if libfuse is from the 3.xx series and the lowlevel
libfuse API is present.  Drop support for 2.x.  This part is cribbed from Amir
who used an LLM aided conversion for fuse4fs, but the maintainer requested that
I apply it to fuse2fs as well.

Co-developed-by: Claude claude-4-sonnet
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 configure      |  314 +++++---------------------------------------------------
 configure.ac   |   81 +++++---------
 misc/fuse2fs.c |  219 ++++-----------------------------------
 3 files changed, 80 insertions(+), 534 deletions(-)


diff --git a/configure b/configure
index 71750b1a8ee972..86c9bc77321eee 100755
--- a/configure
+++ b/configure
@@ -1676,6 +1676,9 @@ Some influential environment variables:
               C compiler flags for ARCHIVE, overriding pkg-config
   ARCHIVE_LIBS
               linker flags for ARCHIVE, overriding pkg-config
+  fuse3_CFLAGS
+              C compiler flags for fuse3, overriding pkg-config
+  fuse3_LIBS  linker flags for fuse3, overriding pkg-config
   CXX         C++ compiler command
   CXXFLAGS    C++ compiler flags
   udev_CFLAGS C compiler flags for udev, overriding pkg-config
@@ -14054,19 +14057,20 @@ FUSE_LIB=
 # Check whether --enable-fuse2fs was given.
 if test ${enable_fuse2fs+y}
 then :
-  enableval=$enable_fuse2fs; if test "$enableval" = "no"
-then
-	FUSE_CMT="#"
-	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs" >&5
+  enableval=$enable_fuse2fs;
+	if test "$enableval" = "no"
+	then
+		FUSE_CMT="#"
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs" >&5
 printf "%s\n" "Disabling fuse2fs" >&6; }
-else
-	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+	else
+		cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #ifdef __linux__
-#include <linux/fs.h>
-#include <linux/falloc.h>
-#include <linux/xattr.h>
-#endif
+	#include <linux/fs.h>
+	#include <linux/falloc.h>
+	#include <linux/xattr.h>
+	#endif
 
 int
 main (void)
@@ -14087,9 +14091,6 @@ See \`config.log' for more details" "$LINENO" 5; }
 fi
 rm -f conftest.err conftest.i conftest.$ac_ext
 
-	  fuse3_CFLAGS
-              C compiler flags for fuse3, overriding pkg-config
-  fuse3_LIBS  linker flags for fuse3, overriding pkg-config
 
 pkg_failed=no
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3" >&5
@@ -14150,28 +14151,7 @@ fi
         echo "$fuse3_PKG_ERRORS" >&5
 
 
-		       for ac_header in pthread.h fuse.h
-do :
-  as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 29
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"
-then :
-  cat >>confdefs.h <<_ACEOF
-#define `printf "%s\n" "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
-
-else $as_nop
-  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse2fs headers.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-done
-
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
 printf %s "checking for fuse_main in -losxfuse... " >&6; }
 if test ${ac_cv_lib_osxfuse_fuse_main+y}
 then :
@@ -14209,45 +14189,6 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
 if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
 then :
   FUSE_LIB=-losxfuse
-else $as_nop
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -lfuse" >&5
-printf %s "checking for fuse_main in -lfuse... " >&6; }
-if test ${ac_cv_lib_fuse_fuse_main+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lfuse  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  ac_cv_lib_fuse_fuse_main=yes
-else $as_nop
-  ac_cv_lib_fuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_fuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_fuse_fuse_main" = xyes
-then :
-  FUSE_LIB=-lfuse
 else $as_nop
   { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
@@ -14255,35 +14196,12 @@ as_fn_error $? "Cannot find fuse library.
 See \`config.log' for more details" "$LINENO" 5; }
 fi
 
-fi
-
 
 elif test $pkg_failed = untried; then
         { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
 printf "%s\n" "no" >&6; }
 
-		       for ac_header in pthread.h fuse.h
-do :
-  as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 29
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"
-then :
-  cat >>confdefs.h <<_ACEOF
-#define `printf "%s\n" "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
-
-else $as_nop
-  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse2fs headers.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-done
-
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
 printf %s "checking for fuse_main in -losxfuse... " >&6; }
 if test ${ac_cv_lib_osxfuse_fuse_main+y}
 then :
@@ -14321,45 +14239,6 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
 if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
 then :
   FUSE_LIB=-losxfuse
-else $as_nop
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -lfuse" >&5
-printf %s "checking for fuse_main in -lfuse... " >&6; }
-if test ${ac_cv_lib_fuse_fuse_main+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lfuse  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  ac_cv_lib_fuse_fuse_main=yes
-else $as_nop
-  ac_cv_lib_fuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_fuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_fuse_fuse_main" = xyes
-then :
-  FUSE_LIB=-lfuse
 else $as_nop
   { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
@@ -14367,24 +14246,21 @@ as_fn_error $? "Cannot find fuse library.
 See \`config.log' for more details" "$LINENO" 5; }
 fi
 
-fi
-
 
 else
         fuse3_CFLAGS=$pkg_cv_fuse3_CFLAGS
         fuse3_LIBS=$pkg_cv_fuse3_LIBS
         { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 printf "%s\n" "yes" >&6; }
-
-		FUSE_LIB=-lfuse3
-
+        FUSE_LIB=-lfuse3
 fi
-	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs" >&5
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs" >&5
 printf "%s\n" "Enabling fuse2fs" >&6; }
-fi
+	fi
 
 else $as_nop
 
+
 pkg_failed=no
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3" >&5
 printf %s "checking for fuse3... " >&6; }
@@ -14444,30 +14320,6 @@ fi
         echo "$fuse3_PKG_ERRORS" >&5
 
 
-	       for ac_header in pthread.h fuse.h
-do :
-  as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 29
-#ifdef __linux__
-# include <linux/fs.h>
-# include <linux/falloc.h>
-# include <linux/xattr.h>
-#endif
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"
-then :
-  cat >>confdefs.h <<_ACEOF
-#define `printf "%s\n" "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
-
-else $as_nop
-  FUSE_CMT="#"
-fi
-
-done
-	if test -z "$FUSE_CMT"
-	then
 		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
 printf %s "checking for fuse_main in -losxfuse... " >&6; }
 if test ${ac_cv_lib_osxfuse_fuse_main+y}
@@ -14506,81 +14358,15 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
 if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
 then :
   FUSE_LIB=-losxfuse
-else $as_nop
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -lfuse" >&5
-printf %s "checking for fuse_main in -lfuse... " >&6; }
-if test ${ac_cv_lib_fuse_fuse_main+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lfuse  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  ac_cv_lib_fuse_fuse_main=yes
-else $as_nop
-  ac_cv_lib_fuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_fuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_fuse_fuse_main" = xyes
-then :
-  FUSE_LIB=-lfuse
 else $as_nop
   FUSE_CMT="#"
 fi
 
-fi
-
-	fi
 
 elif test $pkg_failed = untried; then
         { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
 printf "%s\n" "no" >&6; }
 
-	       for ac_header in pthread.h fuse.h
-do :
-  as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 29
-#ifdef __linux__
-# include <linux/fs.h>
-# include <linux/falloc.h>
-# include <linux/xattr.h>
-#endif
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"
-then :
-  cat >>confdefs.h <<_ACEOF
-#define `printf "%s\n" "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
-
-else $as_nop
-  FUSE_CMT="#"
-fi
-
-done
-	if test -z "$FUSE_CMT"
-	then
 		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
 printf %s "checking for fuse_main in -losxfuse... " >&6; }
 if test ${ac_cv_lib_osxfuse_fuse_main+y}
@@ -14619,73 +14405,30 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
 if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
 then :
   FUSE_LIB=-losxfuse
-else $as_nop
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -lfuse" >&5
-printf %s "checking for fuse_main in -lfuse... " >&6; }
-if test ${ac_cv_lib_fuse_fuse_main+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lfuse  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  ac_cv_lib_fuse_fuse_main=yes
-else $as_nop
-  ac_cv_lib_fuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_fuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_fuse_fuse_main" = xyes
-then :
-  FUSE_LIB=-lfuse
 else $as_nop
   FUSE_CMT="#"
 fi
 
-fi
-
-	fi
 
 else
         fuse3_CFLAGS=$pkg_cv_fuse3_CFLAGS
         fuse3_LIBS=$pkg_cv_fuse3_LIBS
         { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 printf "%s\n" "yes" >&6; }
-
-	FUSE_LIB=-lfuse3
-
+        FUSE_LIB=-lfuse3
 fi
-if test -z "$FUSE_CMT"
-then
-	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs by default." >&5
+	if test -z "$FUSE_CMT"
+	then
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs by default." >&5
 printf "%s\n" "Enabling fuse2fs by default." >&6; }
-fi
+	fi
+
 
 fi
 
 
 
-if test "$FUSE_LIB" = "-lfuse3"
+if test -n "$FUSE_LIB"
 then
 	FUSE_USE_VERSION=314
 	CFLAGS="$fuse3_CFLAGS $CFLAGS"
@@ -14715,9 +14458,6 @@ See \`config.log' for more details" "$LINENO" 5; }
 fi
 
 done
-elif test -n "$FUSE_LIB"
-then
-	FUSE_USE_VERSION=29
 fi
 if test -n "$FUSE_USE_VERSION"
 then
diff --git a/configure.ac b/configure.ac
index 0591999b52b019..bf1b57377cd848 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1367,69 +1367,49 @@ dnl
 AC_CHECK_LIB(rt, clock_gettime, [CLOCK_GETTIME_LIB=-lrt])
 AC_SUBST(CLOCK_GETTIME_LIB)
 dnl
-dnl Check to see if the FUSE library is -lfuse3, -losxfuse, or -lfuse
+dnl Check to see if the FUSE library is -lfuse3 or -losxfuse
 dnl
 FUSE_CMT=
 FUSE_LIB=
 dnl osxfuse.dylib supersedes fuselib.dylib
 AC_ARG_ENABLE([fuse2fs],
 AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
-if test "$enableval" = "no"
-then
-	FUSE_CMT="#"
-	AC_MSG_RESULT([Disabling fuse2fs])
-else
-	AC_PREPROC_IFELSE(
-[AC_LANG_PROGRAM([[#ifdef __linux__
-#include <linux/fs.h>
-#include <linux/falloc.h>
-#include <linux/xattr.h>
-#endif
-]], [])], [], [AC_MSG_FAILURE([Cannot find fuse2fs Linux headers.])])
-
-	PKG_CHECK_MODULES([fuse3], [fuse3],
-	  [
-		FUSE_LIB=-lfuse3
-	  ], [
-		AC_CHECK_HEADERS([pthread.h fuse.h], [],
-			[AC_MSG_FAILURE([Cannot find fuse2fs headers.])],
-[#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 29])
+[
+	if test "$enableval" = "no"
+	then
+		FUSE_CMT="#"
+		AC_MSG_RESULT([Disabling fuse2fs])
+	else
+		AC_PREPROC_IFELSE(
+	[AC_LANG_PROGRAM([[#ifdef __linux__
+	#include <linux/fs.h>
+	#include <linux/falloc.h>
+	#include <linux/xattr.h>
+	#endif
+	]], [])], [], [AC_MSG_FAILURE([Cannot find fuse2fs Linux headers.])])
 
+		PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
+		[
+			AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
+				[AC_MSG_FAILURE([Cannot find fuse library.])])
+		])
+		AC_MSG_RESULT([Enabling fuse2fs])
+	fi
+], [
+	PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
+	[
 		AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
-			[AC_CHECK_LIB(fuse, fuse_main, [FUSE_LIB=-lfuse],
-				[AC_MSG_FAILURE([Cannot find fuse library.])])])
-	  ])
-	AC_MSG_RESULT([Enabling fuse2fs])
-fi
-,
-PKG_CHECK_MODULES([fuse3], [fuse3],
-  [
-	FUSE_LIB=-lfuse3
-  ], [
-	AC_CHECK_HEADERS([pthread.h fuse.h], [], [FUSE_CMT="#"], 
-[#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 29
-#ifdef __linux__
-# include <linux/fs.h>
-# include <linux/falloc.h>
-# include <linux/xattr.h>
-#endif])
+			[FUSE_CMT="#"])
+	])
 	if test -z "$FUSE_CMT"
 	then
-		AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
-			[AC_CHECK_LIB(fuse, fuse_main, [FUSE_LIB=-lfuse],
-				[FUSE_CMT="#"])])
+		AC_MSG_RESULT([Enabling fuse2fs by default.])
 	fi
-  ])
-if test -z "$FUSE_CMT"
-then
-	AC_MSG_RESULT([Enabling fuse2fs by default.])
-fi
+]
 )
 AC_SUBST(FUSE_LIB)
 AC_SUBST(FUSE_CMT)
-if test "$FUSE_LIB" = "-lfuse3"
+if test -n "$FUSE_LIB"
 then
 	FUSE_USE_VERSION=314
 	CFLAGS="$fuse3_CFLAGS $CFLAGS"
@@ -1443,9 +1423,6 @@ then
 #include <linux/falloc.h>
 #include <linux/xattr.h>
 #endif])
-elif test -n "$FUSE_LIB"
-then
-	FUSE_USE_VERSION=29
 fi
 if test -n "$FUSE_USE_VERSION"
 then
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 8a22147d904c27..45ade06765d6d2 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -48,15 +48,6 @@
 #include "ext2fs/ext2_fs.h"
 #include "ext2fs/ext2fsP.h"
 #include "support/bthread.h"
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-# define FUSE_PLATFORM_OPTS	""
-#else
-# ifdef __linux__
-#  define FUSE_PLATFORM_OPTS	",use_ino,big_writes"
-# else
-#  define FUSE_PLATFORM_OPTS	",use_ino"
-# endif
-#endif
 
 #include "../version.h"
 #include "uuid/uuid.h"
@@ -171,11 +162,9 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)
 		break; \
 	}
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
-# ifdef _IOR
-#  ifdef _IOW
-#   define SUPPORT_I_FLAGS
-#  endif
+#ifdef _IOR
+# ifdef _IOW
+#  define SUPPORT_I_FLAGS
 # endif
 #endif
 
@@ -1555,11 +1544,8 @@ static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,
 }
 #endif
 
-static void *op_init(struct fuse_conn_info *conn
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_config *cfg EXT2FS_ATTR((unused))
-#endif
-			)
+static void *op_init(struct fuse_conn_info *conn,
+		     struct fuse_config *cfg EXT2FS_ATTR((unused)))
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
@@ -1591,13 +1577,11 @@ static void *op_init(struct fuse_conn_info *conn
 #ifdef FUSE_CAP_NO_EXPORT_SUPPORT
 	fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);
 #endif
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	conn->time_gran = 1;
 	cfg->use_ino = 1;
 	if (ff->debug)
 		cfg->debug = 1;
 	cfg->nullpath_ok = 1;
-#endif
 
 	if (ff->opstate == F2OP_WRITABLE)
 		fuse2fs_read_bitmaps(ff);
@@ -1678,9 +1662,7 @@ static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
 }
 
 static int __fuse2fs_file_ino(struct fuse2fs *ff, const char *path,
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			      struct fuse_file_info *fp EXT2FS_ATTR((unused)),
-#endif
 			      ext2_ino_t *inop,
 			      const char *func,
 			      int line)
@@ -1688,7 +1670,6 @@ static int __fuse2fs_file_ino(struct fuse2fs *ff, const char *path,
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	if (fp) {
 		struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
 
@@ -1699,7 +1680,7 @@ static int __fuse2fs_file_ino(struct fuse2fs *ff, const char *path,
 		dbg_printf(ff, "%s: get ino=%d\n", func, fh->ino);
 		return 0;
 	}
-#endif
+
 	dbg_printf(ff, "%s: get path=%s\n", func, path);
 	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, inop);
 	if (err)
@@ -1708,19 +1689,11 @@ static int __fuse2fs_file_ino(struct fuse2fs *ff, const char *path,
 	return 0;
 }
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 # define fuse2fs_file_ino(ff, path, fp, inop) \
 	__fuse2fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
-#else
-# define fuse2fs_file_ino(ff, path, fp, inop) \
-	__fuse2fs_file_ino((ff), (path), NULL, (inop), __func__, __LINE__)
-#endif
 
-static int op_getattr(const char *path, struct stat *statbuf
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi
-#endif
-			)
+static int op_getattr(const char *path, struct stat *statbuf,
+		      struct fuse_file_info *fi)
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
@@ -2752,11 +2725,8 @@ static int fuse2fs_check_from_dir_nlink(struct fuse2fs *ff, ext2_ino_t from_ino,
 	return 0;
 }
 
-static int op_rename(const char *from, const char *to
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, unsigned int flags EXT2FS_ATTR((unused))
-#endif
-			)
+static int op_rename(const char *from, const char *to,
+		     unsigned int flags EXT2FS_ATTR((unused)))
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	ext2_filsys fs;
@@ -2769,11 +2739,9 @@ static int op_rename(const char *from, const char *to
 	int flushed = 0;
 	int ret = 0;
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	/* renameat2 is not supported */
 	if (flags)
 		return -ENOSYS;
-#endif
 
 	FUSE2FS_CHECK_CONTEXT(ff);
 	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
@@ -3097,7 +3065,6 @@ 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)
 {
@@ -3176,19 +3143,8 @@ static int in_file_group(struct fuse_context *ctxt,
 	ext2fs_free_mem(&gids);
 	return ret;
 }
-#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
-#endif
-			)
+static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
 {
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = fuse2fs_get();
@@ -3255,11 +3211,8 @@ static int op_chmod(const char *path, mode_t mode
 	return ret;
 }
 
-static int op_chown(const char *path, uid_t owner, gid_t group
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi
-#endif
-			)
+static int op_chown(const char *path, uid_t owner, gid_t group,
+		    struct fuse_file_info *fi)
 {
 	struct fuse_context *ctxt = fuse_get_context();
 	struct fuse2fs *ff = fuse2fs_get();
@@ -3397,11 +3350,7 @@ static int fuse2fs_truncate(struct fuse2fs *ff, ext2_ino_t ino, off_t new_size)
 	return 0;
 }
 
-static int op_truncate(const char *path, off_t len
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi
-#endif
-			)
+static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	ext2_ino_t ino;
@@ -4131,9 +4080,7 @@ struct readdir_iter {
 	fuse_fill_dir_t func;
 
 	struct fuse2fs *ff;
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	enum fuse_readdir_flags flags;
-#endif
 	unsigned int nr;
 	off_t startpos;
 	off_t dirpos;
@@ -4185,44 +4132,29 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 		return 0;
 
 	dbg_printf(i->ff, "READDIR%s ino=%d %u offset=0x%llx\n",
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 			i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
-#else
-			"",
-#endif
 			dir,
 			i->nr++,
 			(unsigned long long)i->dirpos);
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 	if (i->flags == FUSE_READDIR_PLUS) {
 		ret = stat_inode(i->fs, dirent->inode, &stat);
 		if (ret)
 			return DIRENT_ABORT;
 	}
-#endif
 
 	memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
 	namebuf[dirent->name_len & 0xFF] = 0;
-	ret = i->func(i->buf, namebuf, &stat, i->dirpos
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, 0
-#endif
-			);
+	ret = i->func(i->buf, namebuf, &stat, i->dirpos , 0);
 	if (ret)
 		return DIRENT_ABORT;
 
 	return 0;
 }
 
-static int op_readdir(const char *path EXT2FS_ATTR((unused)),
-		      void *buf, fuse_fill_dir_t fill_func,
-		      off_t offset,
-		      struct fuse_file_info *fp
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, enum fuse_readdir_flags flags
-#endif
-			)
+static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
+		      fuse_fill_dir_t fill_func, off_t offset,
+		      struct fuse_file_info *fp, enum fuse_readdir_flags flags)
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
@@ -4231,9 +4163,7 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)),
 		.ff = ff,
 		.dirpos = 0,
 		.startpos = offset,
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 		.flags = flags,
-#endif
 	};
 	int ret = 0;
 
@@ -4416,82 +4346,8 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	return ret;
 }
 
-#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
-static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
-			off_t len, struct fuse_file_info *fp)
-{
-	struct fuse2fs *ff = fuse2fs_get();
-	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
-	ext2_filsys fs;
-	ext2_file_t efp;
-	errcode_t err;
-	int ret = 0;
-
-	FUSE2FS_CHECK_CONTEXT(ff);
-	FUSE2FS_CHECK_HANDLE(ff, fh);
-	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, fh->ino,
-		   (intmax_t) len);
-	fs = fuse2fs_start(ff);
-	if (!fuse2fs_is_writeable(ff)) {
-		ret = -EROFS;
-		goto out;
-	}
-
-	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
-	if (err) {
-		ret = translate_error(fs, fh->ino, err);
-		goto out;
-	}
-
-	err = ext2fs_file_set_size2(efp, len);
-	if (err) {
-		ret = translate_error(fs, fh->ino, err);
-		goto out2;
-	}
-
-out2:
-	err = ext2fs_file_close(efp);
-	if (ret)
-		goto out;
-	if (err) {
-		ret = translate_error(fs, fh->ino, err);
-		goto out;
-	}
-
-	ret = update_mtime(fs, fh->ino, NULL);
-	if (ret)
-		goto out;
-
-out:
-	fuse2fs_finish(ff, ret);
-	return ret;
-}
-
-static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
-		       struct stat *statbuf,
-		       struct fuse_file_info *fp)
-{
-	struct fuse2fs *ff = fuse2fs_get();
-	ext2_filsys fs;
-	struct fuse2fs_file_handle *fh = fuse2fs_get_handle(fp);
-	int ret = 0;
-
-	FUSE2FS_CHECK_CONTEXT(ff);
-	FUSE2FS_CHECK_HANDLE(ff, fh);
-	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
-	fs = fuse2fs_start(ff);
-	ret = stat_inode(fs, fh->ino, statbuf);
-	fuse2fs_finish(ff, ret);
-
-	return ret;
-}
-#endif /* FUSE_VERSION < FUSE_MAKE_VERSION(3, 0) */
-
-static int op_utimens(const char *path, const struct timespec ctv[2]
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
-			, struct fuse_file_info *fi
-#endif
-			)
+static int op_utimens(const char *path, const struct timespec ctv[2],
+		      struct fuse_file_info *fi)
 {
 	struct fuse2fs *ff = fuse2fs_get();
 	struct timespec tv[2];
@@ -4926,13 +4782,8 @@ static int ioctl_shutdown(struct fuse2fs *ff, struct fuse2fs_file_handle *fh,
 	return 0;
 }
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
 static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
 		    unsigned int cmd,
-#else
-		    int cmd,
-#endif
 		    void *arg EXT2FS_ATTR((unused)),
 		    struct fuse_file_info *fp,
 		    unsigned int flags EXT2FS_ATTR((unused)), void *data)
@@ -4983,7 +4834,6 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 
 	return ret;
 }
-#endif /* FUSE 28 */
 
 static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 		   uint64_t *idx)
@@ -5014,8 +4864,7 @@ static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 	return ret;
 }
 
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
-# ifdef SUPPORT_FALLOCATE
+#ifdef SUPPORT_FALLOCATE
 static int fuse2fs_allocate_range(struct fuse2fs *ff,
 				  struct fuse2fs_file_handle *fh, int mode,
 				  off_t offset, off_t len)
@@ -5291,8 +5140,7 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 
 	return ret;
 }
-# endif /* SUPPORT_FALLOCATE */
-#endif /* FUSE 29 */
+#endif /* SUPPORT_FALLOCATE */
 
 static struct fuse_operations fs_ops = {
 	.init = op_init,
@@ -5325,34 +5173,15 @@ static struct fuse_operations fs_ops = {
 	.fsyncdir = op_fsync,
 	.access = op_access,
 	.create = op_create,
-#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
-	.ftruncate = op_ftruncate,
-	.fgetattr = op_fgetattr,
-#endif
 	.utimens = op_utimens,
-#if (FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)) && (FUSE_VERSION < FUSE_MAKE_VERSION(3, 0))
-# if defined(UTIME_NOW) || defined(UTIME_OMIT)
-	.flag_utime_omit_ok = 1,
-# endif
-#endif
 	.bmap = op_bmap,
 #ifdef SUPERFLUOUS
 	.lock = op_lock,
 	.poll = op_poll,
 #endif
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
 	.ioctl = op_ioctl,
-#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
-	.flag_nullpath_ok = 1,
-#endif
-#endif
-#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
-#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 0)
-	.flag_nopath = 1,
-#endif
-# ifdef SUPPORT_FALLOCATE
+#ifdef SUPPORT_FALLOCATE
 	.fallocate = op_fallocate,
-# endif
 #endif
 };
 
@@ -5536,7 +5365,7 @@ static void fuse2fs_compute_libfuse_args(struct fuse2fs *ff,
 
 	/* Set up default fuse parameters */
 	snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
-		 "fsname=%s,attr_timeout=0" FUSE_PLATFORM_OPTS,
+		 "fsname=%s,attr_timeout=0",
 		 get_subtype(argv0),
 		 ff->device);
 	if (ff->no_default_opts == 0)


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

* [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
@ 2025-11-06 22:43   ` Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API Darrick J. Wong
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Separate the detection of libfuse and fuse2fs so that we can add another
fuse server (fuse4fs) without tangling it up in --disable-fuse2fs.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 configure        |  301 +++++++++++++-----------------------------------------
 configure.ac     |   79 ++++++++------
 misc/Makefile.in |    6 +
 3 files changed, 116 insertions(+), 270 deletions(-)


diff --git a/configure b/configure
index 86c9bc77321eee..22031343f078ab 100755
--- a/configure
+++ b/configure
@@ -701,7 +701,7 @@ gcc_ranlib
 gcc_ar
 UNI_DIFF_OPTS
 SEM_INIT_LIB
-FUSE_CMT
+FUSE2FS_CMT
 FUSE_LIB
 fuse3_LIBS
 fuse3_CFLAGS
@@ -14052,214 +14052,8 @@ then :
 fi
 
 
-FUSE_CMT=
+
 FUSE_LIB=
-# Check whether --enable-fuse2fs was given.
-if test ${enable_fuse2fs+y}
-then :
-  enableval=$enable_fuse2fs;
-	if test "$enableval" = "no"
-	then
-		FUSE_CMT="#"
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs" >&5
-printf "%s\n" "Disabling fuse2fs" >&6; }
-	else
-		cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#ifdef __linux__
-	#include <linux/fs.h>
-	#include <linux/falloc.h>
-	#include <linux/xattr.h>
-	#endif
-
-int
-main (void)
-{
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"
-then :
-
-else $as_nop
-  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse2fs Linux headers.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-
-pkg_failed=no
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3" >&5
-printf %s "checking for fuse3... " >&6; }
-
-if test -n "$fuse3_CFLAGS"; then
-    pkg_cv_fuse3_CFLAGS="$fuse3_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"fuse3\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "fuse3") 2>&5
-  ac_status=$?
-  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_fuse3_CFLAGS=`$PKG_CONFIG --cflags "fuse3" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$fuse3_LIBS"; then
-    pkg_cv_fuse3_LIBS="$fuse3_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"fuse3\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "fuse3") 2>&5
-  ac_status=$?
-  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_fuse3_LIBS=`$PKG_CONFIG --libs "fuse3" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-
-
-
-if test $pkg_failed = yes; then
-        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf "%s\n" "no" >&6; }
-
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-                fuse3_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "fuse3" 2>&1`
-        else
-                fuse3_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "fuse3" 2>&1`
-        fi
-        # Put the nasty error message in config.log where it belongs
-        echo "$fuse3_PKG_ERRORS" >&5
-
-
-			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
-printf %s "checking for fuse_main in -losxfuse... " >&6; }
-if test ${ac_cv_lib_osxfuse_fuse_main+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-losxfuse  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  ac_cv_lib_osxfuse_fuse_main=yes
-else $as_nop
-  ac_cv_lib_osxfuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_osxfuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
-then :
-  FUSE_LIB=-losxfuse
-else $as_nop
-  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse library.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-
-elif test $pkg_failed = untried; then
-        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
-printf "%s\n" "no" >&6; }
-
-			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
-printf %s "checking for fuse_main in -losxfuse... " >&6; }
-if test ${ac_cv_lib_osxfuse_fuse_main+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-losxfuse  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-char fuse_main ();
-int
-main (void)
-{
-return fuse_main ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  ac_cv_lib_osxfuse_fuse_main=yes
-else $as_nop
-  ac_cv_lib_osxfuse_fuse_main=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_osxfuse_fuse_main" >&5
-printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
-if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
-then :
-  FUSE_LIB=-losxfuse
-else $as_nop
-  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse library.
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-
-else
-        fuse3_CFLAGS=$pkg_cv_fuse3_CFLAGS
-        fuse3_LIBS=$pkg_cv_fuse3_LIBS
-        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-printf "%s\n" "yes" >&6; }
-        FUSE_LIB=-lfuse3
-fi
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs" >&5
-printf "%s\n" "Enabling fuse2fs" >&6; }
-	fi
-
-else $as_nop
-
 
 pkg_failed=no
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3" >&5
@@ -14320,7 +14114,7 @@ fi
         echo "$fuse3_PKG_ERRORS" >&5
 
 
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
 printf %s "checking for fuse_main in -losxfuse... " >&6; }
 if test ${ac_cv_lib_osxfuse_fuse_main+y}
 then :
@@ -14358,8 +14152,6 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
 if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
 then :
   FUSE_LIB=-losxfuse
-else $as_nop
-  FUSE_CMT="#"
 fi
 
 
@@ -14367,7 +14159,7 @@ elif test $pkg_failed = untried; then
         { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
 printf "%s\n" "no" >&6; }
 
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse_main in -losxfuse" >&5
 printf %s "checking for fuse_main in -losxfuse... " >&6; }
 if test ${ac_cv_lib_osxfuse_fuse_main+y}
 then :
@@ -14405,8 +14197,6 @@ printf "%s\n" "$ac_cv_lib_osxfuse_fuse_main" >&6; }
 if test "x$ac_cv_lib_osxfuse_fuse_main" = xyes
 then :
   FUSE_LIB=-losxfuse
-else $as_nop
-  FUSE_CMT="#"
 fi
 
 
@@ -14417,15 +14207,6 @@ else
 printf "%s\n" "yes" >&6; }
         FUSE_LIB=-lfuse3
 fi
-	if test -z "$FUSE_CMT"
-	then
-		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs by default." >&5
-printf "%s\n" "Enabling fuse2fs by default." >&6; }
-	fi
-
-
-fi
-
 
 
 if test -n "$FUSE_LIB"
@@ -14437,12 +14218,7 @@ then
 do :
   as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | $as_tr_sh`
 ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 314
-#ifdef __linux__
-#include <linux/fs.h>
-#include <linux/falloc.h>
-#include <linux/xattr.h>
-#endif
+#define FUSE_USE_VERSION	314
 "
 if eval test \"x\$"$as_ac_Header"\" = x"yes"
 then :
@@ -14453,7 +14229,7 @@ _ACEOF
 else $as_nop
   { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Cannot find fuse3 fuse2fs headers.
+as_fn_error $? "Cannot build against fuse3 headers
 See \`config.log' for more details" "$LINENO" 5; }
 fi
 
@@ -14466,6 +14242,71 @@ printf "%s\n" "#define FUSE_USE_VERSION $FUSE_USE_VERSION" >>confdefs.h
 
 fi
 
+FUSE2FS_CMT=
+# Check whether --enable-fuse2fs was given.
+if test ${enable_fuse2fs+y}
+then :
+  enableval=$enable_fuse2fs;
+	if test "$enableval" = "no"
+	then
+		FUSE2FS_CMT="#"
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs" >&5
+printf "%s\n" "Disabling fuse2fs" >&6; }
+	else
+		cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __linux__
+	#include <linux/fs.h>
+	#include <linux/falloc.h>
+	#include <linux/xattr.h>
+	#endif
+
+int
+main (void)
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse2fs Linux headers
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+		if test -z "$FUSE_USE_VERSION"
+		then
+			{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse library.
+See \`config.log' for more details" "$LINENO" 5; }
+		fi
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs" >&5
+printf "%s\n" "Enabling fuse2fs" >&6; }
+	fi
+
+else $as_nop
+
+	if test -n "$FUSE_USE_VERSION"
+	then
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse2fs by default" >&5
+printf "%s\n" "Enabling fuse2fs by default" >&6; }
+	else
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse2fs by default" >&5
+printf "%s\n" "Disabling fuse2fs by default" >&6; }
+	fi
+
+
+fi
+
+
+
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PR_SET_IO_FLUSHER" >&5
 printf %s "checking for PR_SET_IO_FLUSHER... " >&6; }
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
diff --git a/configure.ac b/configure.ac
index bf1b57377cd848..b40ed1456d1515 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1366,18 +1366,48 @@ dnl Check to see if librt is required for clock_gettime() (glibc < 2.17)
 dnl
 AC_CHECK_LIB(rt, clock_gettime, [CLOCK_GETTIME_LIB=-lrt])
 AC_SUBST(CLOCK_GETTIME_LIB)
+
 dnl
 dnl Check to see if the FUSE library is -lfuse3 or -losxfuse
 dnl
-FUSE_CMT=
 FUSE_LIB=
 dnl osxfuse.dylib supersedes fuselib.dylib
+PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
+[
+	AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse])
+])
+AC_SUBST(FUSE_LIB)
+
+dnl
+dnl Set FUSE_USE_VERSION, which is how fuse servers build against a particular
+dnl libfuse ABI.  Currently we link against the libfuse 3.14 ABI (hence 314)
+dnl
+if test -n "$FUSE_LIB"
+then
+	FUSE_USE_VERSION=314
+	CFLAGS="$fuse3_CFLAGS $CFLAGS"
+	FUSE_LIB="$fuse3_LIBS"
+	AC_CHECK_HEADERS([pthread.h fuse.h], [],
+		[AC_MSG_FAILURE([Cannot build against fuse3 headers])],
+[#define _FILE_OFFSET_BITS	64
+#define FUSE_USE_VERSION	314])
+fi
+if test -n "$FUSE_USE_VERSION"
+then
+	AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
+		[Define to the version of FUSE to use])
+fi
+
+dnl
+dnl Check if fuse2fs is actually built.
+dnl
+FUSE2FS_CMT=
 AC_ARG_ENABLE([fuse2fs],
 AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
 [
 	if test "$enableval" = "no"
 	then
-		FUSE_CMT="#"
+		FUSE2FS_CMT="#"
 		AC_MSG_RESULT([Disabling fuse2fs])
 	else
 		AC_PREPROC_IFELSE(
@@ -1386,49 +1416,24 @@ AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
 	#include <linux/falloc.h>
 	#include <linux/xattr.h>
 	#endif
-	]], [])], [], [AC_MSG_FAILURE([Cannot find fuse2fs Linux headers.])])
+	]], [])], [], [AC_MSG_FAILURE([Cannot find fuse2fs Linux headers])])
 
-		PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
-		[
-			AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
-				[AC_MSG_FAILURE([Cannot find fuse library.])])
-		])
+		if test -z "$FUSE_USE_VERSION"
+		then
+			AC_MSG_FAILURE([Cannot find fuse library.])
+		fi
 		AC_MSG_RESULT([Enabling fuse2fs])
 	fi
 ], [
-	PKG_CHECK_MODULES([fuse3], [fuse3], [FUSE_LIB=-lfuse3],
-	[
-		AC_CHECK_LIB(osxfuse, fuse_main, [FUSE_LIB=-losxfuse],
-			[FUSE_CMT="#"])
-	])
-	if test -z "$FUSE_CMT"
+	if test -n "$FUSE_USE_VERSION"
 	then
-		AC_MSG_RESULT([Enabling fuse2fs by default.])
+		AC_MSG_RESULT([Enabling fuse2fs by default])
+	else
+		AC_MSG_RESULT([Disabling fuse2fs by default])
 	fi
 ]
 )
-AC_SUBST(FUSE_LIB)
-AC_SUBST(FUSE_CMT)
-if test -n "$FUSE_LIB"
-then
-	FUSE_USE_VERSION=314
-	CFLAGS="$fuse3_CFLAGS $CFLAGS"
-	FUSE_LIB="$fuse3_LIBS"
-	AC_CHECK_HEADERS([pthread.h fuse.h], [],
-		[AC_MSG_FAILURE([Cannot find fuse3 fuse2fs headers.])],
-[#define _FILE_OFFSET_BITS	64
-#define FUSE_USE_VERSION 314
-#ifdef __linux__
-#include <linux/fs.h>
-#include <linux/falloc.h>
-#include <linux/xattr.h>
-#endif])
-fi
-if test -n "$FUSE_USE_VERSION"
-then
-	AC_DEFINE_UNQUOTED(FUSE_USE_VERSION, $FUSE_USE_VERSION,
-		[Define to the version of FUSE to use])
-fi
+AC_SUBST(FUSE2FS_CMT)
 
 dnl
 dnl see if PR_SET_IO_FLUSHER exists
diff --git a/misc/Makefile.in b/misc/Makefile.in
index 0e3bed66dcb63d..b63a0424b19fec 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -34,7 +34,7 @@ MKDIR_P = @MKDIR_P@
 @BLKID_CMT@FINDFS_LINK= findfs
 @BLKID_CMT@FINDFS_MAN= findfs.8
 
-@FUSE_CMT@FUSE_PROG= fuse2fs
+@FUSE2FS_CMT@FUSE2FS_PROG= fuse2fs
 
 SPROGS=		mke2fs badblocks tune2fs dumpe2fs $(BLKID_PROG) logsave \
 			$(E2IMAGE_PROG) @FSCK_PROG@ e2undo
@@ -47,9 +47,9 @@ SMANPAGES=	tune2fs.8 mklost+found.8 mke2fs.8 dumpe2fs.8 badblocks.8 \
 			e2mmpstatus.8
 FMANPAGES=	mke2fs.conf.5 ext4.5
 
-UPROGS=		chattr lsattr $(FUSE_PROG) @UUID_CMT@ uuidgen
+UPROGS=		chattr lsattr $(FUSE2FS_PROG) @UUID_CMT@ uuidgen
 UMANPAGES=	chattr.1 lsattr.1 @UUID_CMT@ uuidgen.1
-UMANPAGES+=	@FUSE_CMT@ fuse2fs.1
+UMANPAGES+=	@FUSE2FS_CMT@ fuse2fs.1
 
 LPROGS=		@E2INITRD_PROG@
 


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

* [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure Darrick J. Wong
@ 2025-11-06 22:43   ` Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 03/23] debian: create new package for fuse4fs Darrick J. Wong
                     ` (20 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Copy fuse2fs.c to fuse4fs.c.  This will become our testbed for trying
out lowlevel fuse server support in the next few patches.

Namespacing conversions performed via:
sed -e 's/fuse2fs/fuse4fs/g' -e 's/FUSE2FS/FUSE4FS/g' -e 's/F2OP_/F4OP_/g' -e 's/FUSE server/FUSE low-level server/g'

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 Makefile.in          |    3 
 configure            |  113 +
 configure.ac         |   65 +
 fuse4fs/Makefile.in  |  192 ++
 fuse4fs/fuse4fs.1.in |  118 +
 fuse4fs/fuse4fs.c    | 5781 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/config.h.in      |    3 
 7 files changed, 6272 insertions(+), 3 deletions(-)
 create mode 100644 fuse4fs/Makefile.in
 create mode 100644 fuse4fs/fuse4fs.1.in
 create mode 100644 fuse4fs/fuse4fs.c


diff --git a/Makefile.in b/Makefile.in
index 277b500bbc9acc..d000f94bc88f0f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -18,12 +18,13 @@ MKDIR_P = @MKDIR_P@
 @ALL_CMT@SUPPORT_LIB_SUBDIR= lib/support
 @ALL_CMT@E2P_LIB_SUBDIR= lib/e2p
 @ALL_CMT@EXT2FS_LIB_SUBDIR= lib/ext2fs
+@FUSE4FS_CMT@FUSE4FS_DIR=fuse4fs
 
 LIB_SUBDIRS=lib/et lib/ss $(E2P_LIB_SUBDIR) $(UUID_LIB_SUBDIR) \
 	$(BLKID_LIB_SUBDIR) $(SUPPORT_LIB_SUBDIR) $(EXT2FS_LIB_SUBDIR)
 
 PROG_SUBDIRS=e2fsck $(DEBUGFS_DIR) misc $(RESIZE_DIR) tests/progs \
-	tests/fuzz po $(E2SCRUB_DIR)
+	tests/fuzz po $(E2SCRUB_DIR) $(FUSE4FS_DIR)
 
 SUBDIRS=util $(LIB_SUBDIRS) $(PROG_SUBDIRS) tests
 
diff --git a/configure b/configure
index 22031343f078ab..7f5fb7c1a62084 100755
--- a/configure
+++ b/configure
@@ -701,6 +701,7 @@ gcc_ranlib
 gcc_ar
 UNI_DIFF_OPTS
 SEM_INIT_LIB
+FUSE4FS_CMT
 FUSE2FS_CMT
 FUSE_LIB
 fuse3_LIBS
@@ -933,6 +934,7 @@ with_libintl_prefix
 enable_largefile
 with_libarchive
 enable_fuse2fs
+enable_fuse4fs
 enable_lto
 enable_ubsan
 enable_addrsan
@@ -1628,6 +1630,7 @@ Optional Features:
   --disable-rpath         do not hardcode runtime library paths
   --disable-largefile     omit support for large files
   --disable-fuse2fs       do not build fuse2fs
+  --disable-fuse4fs       do not build fuse4fs
   --enable-lto            enable link time optimization
   --enable-ubsan          enable undefined behavior sanitizer
   --enable-addrsan        enable address sanitizer
@@ -14242,6 +14245,49 @@ printf "%s\n" "#define FUSE_USE_VERSION $FUSE_USE_VERSION" >>confdefs.h
 
 fi
 
+have_fuse_lowlevel=
+if test -n "$FUSE_USE_VERSION"
+then
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for lowlevel interface in libfuse" >&5
+printf %s "checking for lowlevel interface in libfuse... " >&6; }
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+	#define _GNU_SOURCE
+	#define _FILE_OFFSET_BITS	64
+	#define FUSE_USE_VERSION	314
+	#include <fuse_lowlevel.h>
+
+int
+main (void)
+{
+
+	struct fuse_lowlevel_ops fs_ops = { };
+
+  ;
+  return 0;
+}
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  have_fuse_lowlevel=yes
+	   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+if test -n "$have_fuse_lowlevel"
+then
+
+printf "%s\n" "#define HAVE_FUSE_LOWLEVEL 1" >>confdefs.h
+
+fi
+
 FUSE2FS_CMT=
 # Check whether --enable-fuse2fs was given.
 if test ${enable_fuse2fs+y}
@@ -14307,6 +14353,71 @@ fi
 
 
 
+FUSE4FS_CMT=
+# Check whether --enable-fuse4fs was given.
+if test ${enable_fuse4fs+y}
+then :
+  enableval=$enable_fuse4fs;
+	if test "$enableval" = "no"
+	then
+		FUSE4FS_CMT="#"
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse4fs" >&5
+printf "%s\n" "Disabling fuse4fs" >&6; }
+	else
+		cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __linux__
+	#include <linux/fs.h>
+	#include <linux/falloc.h>
+	#include <linux/xattr.h>
+	#endif
+
+int
+main (void)
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse4fs Linux headers
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+		if test -z "$have_fuse_lowlevel"
+		then
+			{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Cannot find fuse lowlevel library.
+See \`config.log' for more details" "$LINENO" 5; }
+		fi
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse4fs" >&5
+printf "%s\n" "Enabling fuse4fs" >&6; }
+	fi
+
+else $as_nop
+
+	if test -n "$have_fuse_lowlevel"
+	then
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Enabling fuse4fs by default" >&5
+printf "%s\n" "Enabling fuse4fs by default" >&6; }
+	else
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling fuse4fs by default" >&5
+printf "%s\n" "Disabling fuse4fs by default" >&6; }
+	fi
+
+
+fi
+
+
+
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PR_SET_IO_FLUSHER" >&5
 printf %s "checking for PR_SET_IO_FLUSHER... " >&6; }
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -15984,7 +16095,7 @@ for i in MCONFIG Makefile \
 	misc/Makefile ext2ed/Makefile e2fsck/Makefile \
 	debugfs/Makefile tests/Makefile tests/progs/Makefile \
 	tests/fuzz/Makefile resize/Makefile doc/Makefile \
-	po/Makefile.in scrub/Makefile; do
+	po/Makefile.in scrub/Makefile fuse4fs/Makefile; do
 	if test -d `dirname ${srcdir}/$i` ; then
 		outlist="$outlist $i"
 	fi
diff --git a/configure.ac b/configure.ac
index b40ed1456d1515..2eb11873ea0e50 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1398,6 +1398,32 @@ then
 		[Define to the version of FUSE to use])
 fi
 
+dnl
+dnl Check if the FUSE lowlevel library is supported
+dnl
+have_fuse_lowlevel=
+if test -n "$FUSE_USE_VERSION"
+then
+	AC_MSG_CHECKING(for lowlevel interface in libfuse)
+	AC_LINK_IFELSE(
+	[	AC_LANG_PROGRAM([[
+	#define _GNU_SOURCE
+	#define _FILE_OFFSET_BITS	64
+	#define FUSE_USE_VERSION	314
+	#include <fuse_lowlevel.h>
+		]], [[
+	struct fuse_lowlevel_ops fs_ops = { };
+		]])
+	], have_fuse_lowlevel=yes
+	   AC_MSG_RESULT(yes),
+	   AC_MSG_RESULT(no))
+fi
+if test -n "$have_fuse_lowlevel"
+then
+	AC_DEFINE(HAVE_FUSE_LOWLEVEL, 1,
+		  [Define to 1 if fuse supports lowlevel API])
+fi
+
 dnl
 dnl Check if fuse2fs is actually built.
 dnl
@@ -1435,6 +1461,43 @@ AS_HELP_STRING([--disable-fuse2fs],[do not build fuse2fs]),
 )
 AC_SUBST(FUSE2FS_CMT)
 
+dnl
+dnl Check if fuse4fs is actually built.
+dnl
+FUSE4FS_CMT=
+AC_ARG_ENABLE([fuse4fs],
+AS_HELP_STRING([--disable-fuse4fs],[do not build fuse4fs]),
+[
+	if test "$enableval" = "no"
+	then
+		FUSE4FS_CMT="#"
+		AC_MSG_RESULT([Disabling fuse4fs])
+	else
+		AC_PREPROC_IFELSE(
+	[AC_LANG_PROGRAM([[#ifdef __linux__
+	#include <linux/fs.h>
+	#include <linux/falloc.h>
+	#include <linux/xattr.h>
+	#endif
+	]], [])], [], [AC_MSG_FAILURE([Cannot find fuse4fs Linux headers])])
+
+		if test -z "$have_fuse_lowlevel"
+		then
+			AC_MSG_FAILURE([Cannot find fuse lowlevel library.])
+		fi
+		AC_MSG_RESULT([Enabling fuse4fs])
+	fi
+], [
+	if test -n "$have_fuse_lowlevel"
+	then
+		AC_MSG_RESULT([Enabling fuse4fs by default])
+	else
+		AC_MSG_RESULT([Disabling fuse4fs by default])
+	fi
+]
+)
+AC_SUBST(FUSE4FS_CMT)
+
 dnl
 dnl see if PR_SET_IO_FLUSHER exists
 dnl
@@ -2042,7 +2105,7 @@ for i in MCONFIG Makefile \
 	misc/Makefile ext2ed/Makefile e2fsck/Makefile \
 	debugfs/Makefile tests/Makefile tests/progs/Makefile \
 	tests/fuzz/Makefile resize/Makefile doc/Makefile \
-	po/Makefile.in scrub/Makefile; do
+	po/Makefile.in scrub/Makefile fuse4fs/Makefile; do
 	if test -d `dirname ${srcdir}/$i` ; then
 		outlist="$outlist $i"
 	fi
diff --git a/fuse4fs/Makefile.in b/fuse4fs/Makefile.in
new file mode 100644
index 00000000000000..bc137a765ee2b7
--- /dev/null
+++ b/fuse4fs/Makefile.in
@@ -0,0 +1,192 @@
+#
+# Standard e2fsprogs prologue....
+#
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+top_builddir = ..
+my_dir = misc
+INSTALL = @INSTALL@
+MKDIR_P = @MKDIR_P@
+
+@MCONFIG@
+
+UPROGS=
+UMANPAGES=
+@FUSE4FS_CMT@UPROGS+=fuse4fs
+@FUSE4FS_CMT@UMANPAGES+=fuse4fs.1
+
+FUSE4FS_OBJS=	fuse4fs.o journal.o recovery.o revoke.o
+
+PROFILED_FUSE4FS_OJBS=	profiled/fuse4fs.o profiled/journal.o \
+			profiled/recovery.o profiled/revoke.o
+
+SRCS=\
+	$(srcdir)/fuse4fs.c \
+	$(srcdir)/../debugfs/journal.c \
+	$(srcdir)/../e2fsck/revoke.c \
+	$(srcdir)/../e2fsck/recovery.c
+
+LIBS= $(LIBEXT2FS) $(LIBCOM_ERR) $(LIBSUPPORT)
+DEPLIBS= $(LIBEXT2FS) $(DEPLIBCOM_ERR) $(DEPLIBSUPPORT)
+PROFILED_LIBS= $(LIBSUPPORT) $(PROFILED_LIBEXT2FS) $(PROFILED_LIBCOM_ERR)
+PROFILED_DEPLIBS= $(DEPLIBSUPPORT) $(PROFILED_LIBEXT2FS) $(DEPPROFILED_LIBCOM_ERR)
+
+STATIC_LIBS= $(LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR)
+STATIC_DEPLIBS= $(DEPLIBSUPPORT) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBCOM_ERR)
+
+LIBS_E2P= $(LIBE2P) $(LIBCOM_ERR)
+DEPLIBS_E2P= $(LIBE2P) $(DEPLIBCOM_ERR)
+
+COMPILE_ET=	_ET_DIR_OVERRIDE=$(srcdir)/../lib/et/et ../lib/et/compile_et
+
+# This nastiness is needed because of jfs_user.h hackery; when we finally
+# clean up this mess, we should be able to drop it
+JOURNAL_CFLAGS = -I$(srcdir)/../e2fsck $(ALL_CFLAGS) -DDEBUGFS
+DEPEND_CFLAGS = -I$(top_srcdir)/e2fsck
+
+.c.o:
+	$(E) "	CC $<"
+	$(Q) $(CC) -c $(ALL_CFLAGS) $< -o $@
+	$(Q) $(CHECK_CMD) $(ALL_CFLAGS) $<
+	$(Q) $(CPPCHECK_CMD) $(CPPFLAGS) $<
+@PROFILE_CMT@	$(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+all:: profiled $(SPROGS) $(UPROGS) $(USPROGS) $(SMANPAGES) $(UMANPAGES) \
+	$(FMANPAGES) $(LPROGS)
+
+all-static::
+
+@PROFILE_CMT@all:: fuse4fs.profiled
+
+profiled:
+@PROFILE_CMT@	$(E) "	MKDIR $@"
+@PROFILE_CMT@	$(Q) mkdir profiled
+
+fuse4fs: $(FUSE4FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \
+		$(LIBEXT2FS) $(DEPLIBS_E2P)
+	$(E) "	LD $@"
+	$(Q) $(CC) $(ALL_LDFLAGS) -o fuse4fs $(FUSE4FS_OBJS) $(LIBS) \
+		$(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \
+		$(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P)
+
+journal.o: $(srcdir)/../debugfs/journal.c
+	$(E) "	CC $<"
+	$(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+		$(srcdir)/../debugfs/journal.c -o $@
+@PROFILE_CMT@	$(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+recovery.o: $(srcdir)/../e2fsck/recovery.c
+	$(E) "	CC $<"
+	$(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+		$(srcdir)/../e2fsck/recovery.c -o $@
+@PROFILE_CMT@	$(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+revoke.o: $(srcdir)/../e2fsck/revoke.c
+	$(E) "	CC $<"
+	$(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+		$(srcdir)/../e2fsck/revoke.c -o $@
+@PROFILE_CMT@	$(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+fuse4fs.1: $(DEP_SUBSTITUTE) $(srcdir)/fuse4fs.1.in
+	$(E) "	SUBST $@"
+	$(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/fuse4fs.1.in fuse4fs.1
+
+installdirs:
+	$(E) "	MKDIR_P $(bindir) $(man1dir)"
+	$(Q) $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
+
+install: all $(UMANPAGES) installdirs
+	$(Q) for i in $(UPROGS); do \
+		$(ES) "	INSTALL $(bindir)/$$i"; \
+		$(INSTALL_PROGRAM) $$i $(DESTDIR)$(bindir)/$$i; \
+	done
+	$(Q) for i in $(UMANPAGES); do \
+		for j in $(COMPRESS_EXT); do \
+			$(RM) -f $(DESTDIR)$(man1dir)/$$i.$$j; \
+		done; \
+		$(ES) "	INSTALL_DATA $(man1dir)/$$i"; \
+		$(INSTALL_DATA) $$i $(DESTDIR)$(man1dir)/$$i; \
+	done
+
+install-strip: install
+	$(Q) for i in $(UPROGS); do \
+		$(E) "	STRIP $(bindir)/$$i"; \
+		$(STRIP) $(DESTDIR)$(bindir)/$$i; \
+	done
+
+uninstall:
+	for i in $(UPROGS); do \
+		$(RM) -f $(DESTDIR)$(bindir)/$$i; \
+	done
+	for i in $(UMANPAGES); do \
+		$(RM) -f $(DESTDIR)$(man1dir)/$$i; \
+	done
+
+clean::
+	$(RM) -f $(UPROGS) $(UMANPAGES) profile.h \
+		fuse4fs.profiled \
+		profiled/*.o \#* *.s *.o *.a *~ core gmon.out
+
+mostlyclean: clean
+distclean: clean
+	$(RM) -f .depend Makefile $(srcdir)/TAGS $(srcdir)/Makefile.in.old
+
+# +++ Dependency line eater +++
+#
+# Makefile dependencies follow.  This must be the last section in
+# the Makefile.in file
+#
+fuse4fs.o: $(srcdir)/fuse4fs.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/ext2fsP.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/version.h \
+ $(top_srcdir)/lib/e2p/e2p.h
+journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/../debugfs/journal.h \
+ $(top_srcdir)/e2fsck/jfs_user.h $(top_srcdir)/e2fsck/e2fsck.h \
+ $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
+revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
+ $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
+ $(srcdir)/../e2fsck/e2fsck.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
+recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
+ $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
+ $(srcdir)/../e2fsck/e2fsck.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/fuse4fs/fuse4fs.1.in b/fuse4fs/fuse4fs.1.in
new file mode 100644
index 00000000000000..8bef5f48802385
--- /dev/null
+++ b/fuse4fs/fuse4fs.1.in
@@ -0,0 +1,118 @@
+.\" -*- nroff -*-
+.\" Copyright 2025 Oracle.  All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH FUSE4FS 1 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+fuse4fs \- FUSE file system client for ext2/ext3/ext4 file systems
+.SH SYNOPSIS
+.B fuse4fs
+[
+.B device/image
+]
+[
+.B mountpoint
+]
+[
+.I options
+]
+.SH DESCRIPTION
+.B fuse4fs
+is a FUSE file system client that supports reading and writing from
+devices or image files containing ext2, ext3, and ext4 file systems.
+.SH OPTIONS
+.SS "general options:"
+.TP
+\fB\-o\fR opt,[opt...]
+mount options
+.TP
+\fB\-h\fR   \fB\-\-help\fR
+print help
+.TP
+\fB\-V\fR   \fB\-\-version\fR
+print version
+.SS "fuse4fs options:"
+.TP
+\fB-o\fR ro
+read-only mount
+.TP
+\fB-o\fR rw
+read-write mount (default)
+.TP
+\fB-o\fR bsddf
+bsd-style df (default)
+.TP
+\fB-o\fR minixdf
+minix-style df
+.TP
+\fB-o\fR acl
+enable file access control lists
+.TP
+\fB-o\fR cache_size
+Set the disk cache size to this quantity.
+The quantity may contain the suffixes k, m, or g.
+By default, the size is 32MB.
+The size may not be larger than 2GB.
+.TP
+\fB-o\fR direct
+Use O_DIRECT to access the block device.
+.TP
+\fB-o\fR dirsync
+Flush dirty metadata to disk after every directory update.
+.TP
+\fB-o\fR errors=continue
+ignore errors
+.TP
+\fB-o\fR errors=remount-ro
+stop allowing writes after errors
+.TP
+\fB-o\fR errors=panic
+dump core on error
+.TP
+\fB-o\fR fakeroot
+pretend to be root for permission checks
+.TP
+\fB-o\fR fuse4fs_debug
+enable fuse4fs debugging
+.TP
+\fB-o\fR kernel
+Behave more like the kernel ext4 driver in the following ways:
+Allows processes owned by other users to access the filesystem.
+Uses the kernel's permissions checking logic instead of fuse4fs's.
+Enables setuid and device files.
+Note that these options can still be overridden (e.g.
+.I nosuid
+) later.
+.TP
+\fB-o\fR lockfile=path
+use this file to control access to the filesystem
+.TP
+\fB-o\fR no_default_opts
+do not include default fuse options
+.TP
+\fB-o\fR norecovery
+do not replay the journal and mount the file system read-only
+.SS "FUSE options:"
+.TP
+\fB-d -o\fR debug
+enable debug output (implies -f)
+.TP
+\fB-f\fR
+foreground operation
+.TP
+\fB-s\fR
+disable multi-threaded operation
+.P
+For other FUSE options please see
+.BR mount.fuse (8)
+or see the output of
+.I fuse4fs \-\-helpfull
+.SH AVAILABILITY
+.B fuse4fs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR ext4 (5)
+.BR e2fsck (8),
+.BR mount.fuse (8)
+
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
new file mode 100644
index 00000000000000..daf22e0fe7fde5
--- /dev/null
+++ b/fuse4fs/fuse4fs.c
@@ -0,0 +1,5781 @@
+/*
+ * fuse4fs.c - FUSE low-level server for e2fsprogs.
+ *
+ * Copyright (C) 2014-2025 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include "config.h"
+#include <pthread.h>
+#ifdef __linux__
+# include <linux/fs.h>
+# include <linux/falloc.h>
+# include <linux/xattr.h>
+# include <sys/prctl.h>
+#endif
+#ifdef HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+#endif
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <ctype.h>
+#define FUSE_DARWIN_ENABLE_EXTENSIONS 0
+#ifdef __SET_FOB_FOR_FUSE
+# error Do not set magic value __SET_FOB_FOR_FUSE!!!!
+#endif
+#ifndef _FILE_OFFSET_BITS
+/*
+ * Old versions of libfuse (e.g. Debian 2.9.9 package) required that the build
+ * system set _FILE_OFFSET_BITS explicitly, even if doing so isn't required to
+ * get a 64-bit off_t.  AC_SYS_LARGEFILE doesn't set any _FILE_OFFSET_BITS if
+ * it's not required (such as on aarch64), so we must inject it here.
+ */
+# define __SET_FOB_FOR_FUSE
+# define _FILE_OFFSET_BITS 64
+#endif /* _FILE_OFFSET_BITS */
+#include <fuse.h>
+#ifdef __SET_FOB_FOR_FUSE
+# undef _FILE_OFFSET_BITS
+#endif /* __SET_FOB_FOR_FUSE */
+#include <inttypes.h>
+#include "ext2fs/ext2fs.h"
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fsP.h"
+#include "support/bthread.h"
+
+#include "../version.h"
+#include "uuid/uuid.h"
+#include "e2p/e2p.h"
+
+#ifdef ENABLE_NLS
+#include <libintl.h>
+#include <locale.h>
+#define _(a) (gettext(a))
+#ifdef gettext_noop
+#define N_(a) gettext_noop(a)
+#else
+#define N_(a) (a)
+#endif
+#define P_(singular, plural, n) (ngettext(singular, plural, n))
+#ifndef NLS_CAT_NAME
+#define NLS_CAT_NAME "e2fsprogs"
+#endif
+#ifndef LOCALEDIR
+#define LOCALEDIR "/usr/share/locale"
+#endif
+#else
+#define _(a) (a)
+#define N_(a) a
+#define P_(singular, plural, n) ((n) == 1 ? (singular) : (plural))
+#endif
+
+#ifndef XATTR_NAME_POSIX_ACL_DEFAULT
+#define XATTR_NAME_POSIX_ACL_DEFAULT "posix_acl_default"
+#endif
+#ifndef XATTR_SECURITY_PREFIX
+#define XATTR_SECURITY_PREFIX "security."
+#define XATTR_SECURITY_PREFIX_LEN (sizeof (XATTR_SECURITY_PREFIX) - 1)
+#endif
+
+/*
+ * Linux and MacOS implement the setxattr(2) interface, which defines
+ * XATTR_CREATE and XATTR_REPLACE.  However, FreeBSD uses
+ * extattr_set_file(2), which does not have a flags or options
+ * parameter, and does not define XATTR_CREATE and XATTR_REPLACE.
+ */
+#ifndef XATTR_CREATE
+#define XATTR_CREATE 0
+#endif
+
+#ifndef XATTR_REPLACE
+#define XATTR_REPLACE 0
+#endif
+
+#if !defined(EUCLEAN)
+#if !defined(EBADMSG)
+#define EUCLEAN EBADMSG
+#elif !defined(EPROTO)
+#define EUCLEAN EPROTO
+#else
+#define EUCLEAN EIO
+#endif
+#endif /* !defined(EUCLEAN) */
+
+#if !defined(ENODATA)
+#ifdef ENOATTR
+#define ENODATA ENOATTR
+#else
+#define ENODATA ENOENT
+#endif
+#endif /* !defined(ENODATA) */
+
+static inline uint64_t round_up(uint64_t b, unsigned int align)
+{
+	unsigned int m;
+
+	if (align == 0)
+		return b;
+	m = b % align;
+	if (m)
+		b += align - m;
+	return b;
+}
+
+static inline uint64_t round_down(uint64_t b, unsigned int align)
+{
+	unsigned int m;
+
+	if (align == 0)
+		return b;
+	m = b % align;
+	return b - m;
+}
+
+#define dbg_printf(fuse4fs, format, ...) \
+	while ((fuse4fs)->debug) { \
+		printf("FUSE4FS (%s): tid=%d " format, (fuse4fs)->shortdev, gettid(), ##__VA_ARGS__); \
+		fflush(stdout); \
+		break; \
+	}
+
+#define log_printf(fuse4fs, format, ...) \
+	do { \
+		printf("FUSE4FS (%s): " format, (fuse4fs)->shortdev, ##__VA_ARGS__); \
+		fflush(stdout); \
+	} while (0)
+
+#define err_printf(fuse4fs, format, ...) \
+	do { \
+		fprintf(stderr, "FUSE4FS (%s): " format, (fuse4fs)->shortdev, ##__VA_ARGS__); \
+		fflush(stderr); \
+	} while (0)
+
+#define timing_printf(fuse4fs, format, ...) \
+	while ((fuse4fs)->timing) { \
+		printf("FUSE4FS (%s): " format, (fuse4fs)->shortdev, ##__VA_ARGS__); \
+		break; \
+	}
+
+#ifdef _IOR
+# ifdef _IOW
+#  define SUPPORT_I_FLAGS
+# endif
+#endif
+
+#ifdef FALLOC_FL_KEEP_SIZE
+# define FL_KEEP_SIZE_FLAG FALLOC_FL_KEEP_SIZE
+# define SUPPORT_FALLOCATE
+#else
+# define FL_KEEP_SIZE_FLAG (0)
+#endif
+
+#ifdef FALLOC_FL_PUNCH_HOLE
+# define FL_PUNCH_HOLE_FLAG FALLOC_FL_PUNCH_HOLE
+#else
+# define FL_PUNCH_HOLE_FLAG (0)
+#endif
+
+#ifdef FALLOC_FL_ZERO_RANGE
+# define FL_ZERO_RANGE_FLAG FALLOC_FL_ZERO_RANGE
+#else
+# define FL_ZERO_RANGE_FLAG (0)
+#endif
+
+errcode_t ext2fs_check_ext3_journal(ext2_filsys fs);
+errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs);
+
+const char *err_shortdev;
+
+#ifdef CONFIG_JBD_DEBUG		/* Enabled by configure --enable-jbd-debug */
+int journal_enable_debug = -1;
+#endif
+
+/*
+ * ext2_file_t contains a struct inode, so we can't leave files open.
+ * Use this as a proxy instead.
+ */
+#define FUSE4FS_FILE_MAGIC	(0xEF53DEAFUL)
+struct fuse4fs_file_handle {
+	unsigned long magic;
+	ext2_ino_t ino;
+	int open_flags;
+	int check_flags;
+};
+
+enum fuse4fs_opstate {
+	F4OP_READONLY,
+	F4OP_WRITABLE,
+	F4OP_SHUTDOWN,
+};
+
+/* Main program context */
+#define FUSE4FS_MAGIC		(0xEF53DEADUL)
+struct fuse4fs {
+	unsigned long magic;
+	ext2_filsys fs;
+	pthread_mutex_t bfl;
+	char *device;
+	char *shortdev;
+
+	/* options set by fuse_opt_parse must be of type int */
+	int ro;
+	int debug;
+	int no_default_opts;
+	int errors_behavior; /* actually an enum */
+	int minixdf;
+	int fakeroot;
+	int alloc_all_blocks;
+	int norecovery;
+	int kernel;
+	int directio;
+	int acl;
+	int dirsync;
+
+	enum fuse4fs_opstate opstate;
+	int logfd;
+	int blocklog;
+	int oom_score_adj;
+	unsigned int blockmask;
+	unsigned long offset;
+	unsigned int next_generation;
+	unsigned long long cache_size;
+	char *lockfile;
+#ifdef CONFIG_MMP
+	struct bthread *mmp_thread;
+	unsigned int mmp_update_interval;
+#endif
+#ifdef HAVE_CLOCK_MONOTONIC
+	double lock_start_time;
+	double op_start_time;
+
+	/* options set by fuse_opt_parse must be of type int */
+	int timing;
+#endif
+};
+
+#define FUSE4FS_CHECK_HANDLE(ff, fh) \
+	do { \
+		if ((fh) == NULL || (fh)->magic != FUSE4FS_FILE_MAGIC) { \
+			fprintf(stderr, \
+				"FUSE4FS: Corrupt in-memory file handle at %s:%d!\n", \
+				__func__, __LINE__); \
+			fflush(stderr); \
+			return -EUCLEAN; \
+		} \
+	} while (0)
+
+#define __FUSE4FS_CHECK_CONTEXT(ff, retcode, shutcode) \
+	do { \
+		if ((ff) == NULL || (ff)->magic != FUSE4FS_MAGIC) { \
+			fprintf(stderr, \
+				"FUSE4FS: Corrupt in-memory data at %s:%d!\n", \
+				__func__, __LINE__); \
+			fflush(stderr); \
+			retcode; \
+		} \
+		if ((ff)->opstate == F4OP_SHUTDOWN) { \
+			shutcode; \
+		} \
+	} while (0)
+
+#define FUSE4FS_CHECK_CONTEXT(ff) \
+	__FUSE4FS_CHECK_CONTEXT((ff), return -EUCLEAN, return -EIO)
+#define FUSE4FS_CHECK_CONTEXT_DESTROY(ff) \
+	__FUSE4FS_CHECK_CONTEXT((ff), return, /* do not return */)
+#define FUSE4FS_CHECK_CONTEXT_INIT(ff) \
+	__FUSE4FS_CHECK_CONTEXT((ff), abort(), abort())
+
+static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
+			     const char *func, int line);
+#define translate_error(fs, ino, err) __translate_error((fs), (ino), (err), \
+			__func__, __LINE__)
+
+/* for macosx */
+#ifndef W_OK
+#  define W_OK 2
+#endif
+
+#ifndef R_OK
+#  define R_OK 4
+#endif
+
+static inline int u_log2(unsigned int arg)
+{
+	int	l = 0;
+
+	arg >>= 1;
+	while (arg) {
+		l++;
+		arg >>= 1;
+	}
+	return l;
+}
+
+static inline blk64_t FUSE4FS_B_TO_FSBT(const struct fuse4fs *ff, off_t pos)
+{
+	return pos >> ff->blocklog;
+}
+
+static inline blk64_t FUSE4FS_B_TO_FSB(const struct fuse4fs *ff, off_t pos)
+{
+	return (pos + ff->blockmask) >> ff->blocklog;
+}
+
+static inline unsigned int FUSE4FS_OFF_IN_FSB(const struct fuse4fs *ff,
+					      off_t pos)
+{
+	return pos & ff->blockmask;
+}
+
+static inline off_t FUSE4FS_FSB_TO_B(const struct fuse4fs *ff, blk64_t bno)
+{
+	return bno << ff->blocklog;
+}
+
+static double gettime_monotonic(void)
+{
+#ifdef CLOCK_MONOTONIC
+	struct timespec ts;
+#endif
+	struct timeval tv;
+	static pthread_mutex_t fake_lock = PTHREAD_MUTEX_INITIALIZER;
+	static double fake_time = 0;
+	double dret;
+	int ret;
+
+#ifdef CLOCK_MONOTONIC
+	ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+	if (ret == 0)
+		return (double)ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+#endif
+	ret = gettimeofday(&tv, NULL);
+	if (ret == 0)
+		return (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+
+	/* If we have no clock sources at all, fake it */
+	pthread_mutex_lock(&fake_lock);
+	fake_time += 1.0;
+	dret = fake_time;
+	pthread_mutex_unlock(&fake_lock);
+
+	return dret;
+}
+
+static double init_deadline(double timeout)
+{
+	return gettime_monotonic() + timeout;
+}
+
+static int retry_before_deadline(double deadline)
+{
+	double now = gettime_monotonic();
+
+	if (now >= deadline)
+		return 0;
+
+	/* sleep for 0.1s before trying again */
+	usleep(100000);
+	return 1;
+}
+
+/* Wait this many seconds to acquire the filesystem device */
+#define FUSE4FS_OPEN_TIMEOUT	(15.0)
+
+#define EXT4_EPOCH_BITS 2
+#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
+#define EXT4_NSEC_MASK  (~0UL << EXT4_EPOCH_BITS)
+
+/*
+ * Extended fields will fit into an inode if the filesystem was formatted
+ * with large inodes (-I 256 or larger) and there are not currently any EAs
+ * consuming all of the available space. For new inodes we always reserve
+ * enough space for the kernel's known extended fields, but for inodes
+ * created with an old kernel this might not have been the case. None of
+ * the extended inode fields is critical for correct filesystem operation.
+ * This macro checks if a certain field fits in the inode. Note that
+ * inode-size = GOOD_OLD_INODE_SIZE + i_extra_isize
+ */
+#define EXT4_FITS_IN_INODE(ext4_inode, field)		\
+	((offsetof(typeof(*ext4_inode), field) +	\
+	  sizeof((ext4_inode)->field))			\
+	 <= ((size_t) EXT2_GOOD_OLD_INODE_SIZE +		\
+	    (ext4_inode)->i_extra_isize))		\
+
+static inline __u32 ext4_encode_extra_time(const struct timespec *time)
+{
+	__u32 extra = sizeof(time->tv_sec) > 4 ?
+			((time->tv_sec - (__s32)time->tv_sec) >> 32) &
+			EXT4_EPOCH_MASK : 0;
+	return extra | (time->tv_nsec << EXT4_EPOCH_BITS);
+}
+
+static inline void ext4_decode_extra_time(struct timespec *time, __u32 extra)
+{
+	if (sizeof(time->tv_sec) > 4 && (extra & EXT4_EPOCH_MASK)) {
+		__u64 extra_bits = extra & EXT4_EPOCH_MASK;
+		/*
+		 * Prior to kernel 3.14?, we had a broken decode function,
+		 * wherein we effectively did this:
+		 * if (extra_bits == 3)
+		 *     extra_bits = 0;
+		 */
+		time->tv_sec += extra_bits << 32;
+	}
+	time->tv_nsec = ((extra) & EXT4_NSEC_MASK) >> EXT4_EPOCH_BITS;
+}
+
+#define EXT4_CLAMP_TIMESTAMP(xtime, timespec, raw_inode)		       \
+do {									       \
+	if ((timespec)->tv_sec < EXT4_TIMESTAMP_MIN)			       \
+		(timespec)->tv_sec = EXT4_TIMESTAMP_MIN;		       \
+	if ((timespec)->tv_sec < EXT4_TIMESTAMP_MIN)			       \
+		(timespec)->tv_sec = EXT4_TIMESTAMP_MIN;		       \
+									       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) {		       \
+		if ((timespec)->tv_sec > EXT4_EXTRA_TIMESTAMP_MAX)	       \
+			(timespec)->tv_sec = EXT4_EXTRA_TIMESTAMP_MAX;	       \
+	} else {							       \
+		if ((timespec)->tv_sec > EXT4_NON_EXTRA_TIMESTAMP_MAX)	       \
+			(timespec)->tv_sec = EXT4_NON_EXTRA_TIMESTAMP_MAX;     \
+	}								       \
+} while (0)
+
+#define EXT4_INODE_SET_XTIME(xtime, timespec, raw_inode)		       \
+do {									       \
+	typeof(*(timespec)) _ts = *(timespec);				       \
+									       \
+	EXT4_CLAMP_TIMESTAMP(xtime, &_ts, raw_inode);			       \
+	(raw_inode)->xtime = _ts.tv_sec;				       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra))		       \
+		(raw_inode)->xtime ## _extra =				       \
+				ext4_encode_extra_time(&_ts);		       \
+} while (0)
+
+#define EXT4_EINODE_SET_XTIME(xtime, timespec, raw_inode)		       \
+do {									       \
+	typeof(*(timespec)) _ts = *(timespec);				       \
+									       \
+	EXT4_CLAMP_TIMESTAMP(xtime, &_ts, raw_inode);			       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime))			       \
+		(raw_inode)->xtime = _ts.tv_sec;			       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra))		       \
+		(raw_inode)->xtime ## _extra =				       \
+				ext4_encode_extra_time(&_ts);		       \
+} while (0)
+
+#define EXT4_INODE_GET_XTIME(xtime, timespec, raw_inode)		       \
+do {									       \
+	(timespec)->tv_sec = (signed)((raw_inode)->xtime);		       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra))		       \
+		ext4_decode_extra_time((timespec),			       \
+				       (raw_inode)->xtime ## _extra);	       \
+	else								       \
+		(timespec)->tv_nsec = 0;				       \
+} while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, timespec, raw_inode)		       \
+do {									       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime))			       \
+		(timespec)->tv_sec =					       \
+			(signed)((raw_inode)->xtime);			       \
+	if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra))		       \
+		ext4_decode_extra_time((timespec),			       \
+				       raw_inode->xtime ## _extra);	       \
+	else								       \
+		(timespec)->tv_nsec = 0;				       \
+} while (0)
+
+static inline errcode_t fuse4fs_read_inode(ext2_filsys fs, ext2_ino_t ino,
+					   struct ext2_inode_large *inode)
+{
+	memset(inode, 0, sizeof(*inode));
+	return ext2fs_read_inode_full(fs, ino, EXT2_INODE(inode),
+				      sizeof(*inode));
+}
+
+static inline errcode_t fuse4fs_write_inode(ext2_filsys fs, ext2_ino_t ino,
+					    struct ext2_inode_large *inode)
+{
+	return ext2fs_write_inode_full(fs, ino, EXT2_INODE(inode),
+				       sizeof(*inode));
+}
+
+static inline ext2_filsys fuse4fs_start(struct fuse4fs *ff)
+{
+	if (ff->timing) {
+		double lock_time = gettime_monotonic();
+
+		pthread_mutex_lock(&ff->bfl);
+
+		ff->op_start_time = gettime_monotonic();
+		ff->lock_start_time = lock_time;
+	} else {
+		pthread_mutex_lock(&ff->bfl);
+	}
+
+	return ff->fs;
+}
+
+static inline void fuse4fs_finish_timing(struct fuse4fs *ff, const char *func)
+{
+	double now;
+
+	if (!ff->timing)
+		return;
+
+	now = gettime_monotonic();
+
+	timing_printf(ff, "%s: lock=%.2fms elapsed=%.2fms\n", func,
+		      (ff->op_start_time - ff->lock_start_time) * 1000.0,
+		      (now - ff->op_start_time) * 1000.0);
+}
+
+static inline void __fuse4fs_finish(struct fuse4fs *ff, int ret,
+				    const char *func)
+{
+	fuse4fs_finish_timing(ff, func);
+	if (ret)
+		dbg_printf(ff, "%s: libfuse ret=%d\n", func, ret);
+	pthread_mutex_unlock(&ff->bfl);
+}
+#define fuse4fs_finish(ff, ret) __fuse4fs_finish((ff), (ret), __func__)
+
+#ifdef CONFIG_MMP
+static bool fuse4fs_mmp_wanted(const struct fuse4fs *ff)
+{
+	ext2_filsys fs = ff->fs;
+
+	if (!fs || !ext2fs_has_feature_mmp(fs->super) ||
+	    ff->opstate != F4OP_WRITABLE || (fs->flags & EXT2_FLAG_SKIP_MMP))
+		return false;
+	return true;
+}
+
+static int fuse4fs_mmp_touch(struct fuse4fs *ff, bool immediate)
+{
+	ext2_filsys fs = ff->fs;
+	struct mmp_struct *mmp = fs->mmp_buf;
+	struct mmp_struct *mmp_cmp = fs->mmp_cmp;
+	struct timeval tv;
+	errcode_t retval = 0;
+
+	gettimeofday(&tv, 0);
+	if (!immediate &&
+	    tv.tv_sec - fs->mmp_last_written < ff->mmp_update_interval)
+		return 0;
+
+	retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, NULL);
+	if (retval)
+		return translate_error(fs, 0, retval);
+
+	if (memcmp(mmp, mmp_cmp, sizeof(*mmp_cmp)))
+		return translate_error(fs, 0, EXT2_ET_MMP_CHANGE_ABORT);
+
+	/*
+	 * Believe it or not, ext2fs_mmp_read actually overwrites fs->mmp_cmp
+	 * and leaves fs->mmp_buf untouched.  Hence we copy mmp_cmp into
+	 * mmp_buf, update mmp_buf, and write mmp_buf out to disk.
+	 */
+	memcpy(mmp, mmp_cmp, sizeof(*mmp_cmp));
+	mmp->mmp_time = tv.tv_sec;
+	mmp->mmp_seq = ext2fs_mmp_new_seq();
+
+	retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_buf);
+	if (retval)
+		return translate_error(fs, 0, retval);
+
+	return 0;
+}
+
+static void fuse4fs_mmp_bthread(void *data)
+{
+	struct fuse4fs *ff = data;
+
+	fuse4fs_start(ff);
+	if (fuse4fs_mmp_wanted(ff) && !bthread_cancelled(ff->mmp_thread))
+		fuse4fs_mmp_touch(ff, false);
+	fuse4fs_finish(ff, 0);
+}
+
+static void fuse4fs_mmp_start(struct fuse4fs *ff)
+{
+	int ret;
+
+	if (!fuse4fs_mmp_wanted(ff))
+		return;
+
+	ret = bthread_create("fuse4fs_mmp", fuse4fs_mmp_bthread, ff,
+			     ff->mmp_update_interval, &ff->mmp_thread);
+	if (ret) {
+		err_printf(ff, "MMP: %s.\n", error_message(ret));
+		return;
+	}
+
+	ret = bthread_start(ff->mmp_thread);
+	if (ret)
+		err_printf(ff, "MMP: %s.\n", error_message(ret));
+}
+
+static void fuse4fs_mmp_cancel(struct fuse4fs *ff)
+{
+	if (ff->mmp_thread)
+		bthread_cancel(ff->mmp_thread);
+}
+
+static void fuse4fs_mmp_config(struct fuse4fs *ff)
+{
+	ext2_filsys fs = ff->fs;
+	struct mmp_struct *mmp_s = fs->mmp_buf;
+	unsigned int mmp_update_interval = fs->super->s_mmp_update_interval;
+
+	if (!ext2fs_has_feature_mmp(fs->super) ||
+	    !(fs->flags & EXT2_FLAG_RW) ||
+	    (fs->flags & EXT2_FLAG_SKIP_MMP))
+		return;
+
+	/*
+	 * If update_interval in MMP block is larger, use that instead of
+	 * update_interval from the superblock.
+	 */
+	if (mmp_s->mmp_check_interval > mmp_update_interval)
+		mmp_update_interval = mmp_s->mmp_check_interval;
+
+	/* Clamp to the relevant(?) interval values */
+	if (mmp_update_interval < EXT4_MMP_MIN_CHECK_INTERVAL)
+		mmp_update_interval = EXT4_MMP_MIN_CHECK_INTERVAL;
+	if (mmp_update_interval > EXT4_MMP_MAX_UPDATE_INTERVAL)
+		mmp_update_interval = EXT4_MMP_MAX_UPDATE_INTERVAL;
+
+	ff->mmp_update_interval = mmp_update_interval;
+
+	/*
+	 * libext2fs writes EXT4_MMP_SEQ_FSCK after mounting, so we need to
+	 * update it immediately so that it doesn't look like another node is
+	 * actually running fsck.
+	 */
+	fuse4fs_mmp_touch(ff, true);
+}
+
+static void fuse4fs_mmp_destroy(struct fuse4fs *ff)
+{
+	bthread_destroy(&ff->mmp_thread);
+}
+#else
+# define fuse4fs_mmp_start(...)		((void)0)
+# define fuse4fs_mmp_cancel(...)	((void)0)
+# define fuse4fs_mmp_config(...)	((void)0)
+# define fuse4fs_mmp_destroy(...)	((void)0)
+#endif
+
+static inline struct fuse4fs *fuse4fs_get(void)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+
+	return ctxt->private_data;
+}
+
+static inline struct fuse4fs_file_handle *
+fuse4fs_get_handle(const struct fuse_file_info *fp)
+{
+	return (struct fuse4fs_file_handle *)(uintptr_t)fp->fh;
+}
+
+static inline void
+fuse4fs_set_handle(struct fuse_file_info *fp, struct fuse4fs_file_handle *fh)
+{
+	fp->fh = (uintptr_t)fh;
+}
+
+static void get_now(struct timespec *now)
+{
+#ifdef CLOCK_REALTIME
+	if (!clock_gettime(CLOCK_REALTIME, now))
+		return;
+#endif
+
+	now->tv_sec = time(NULL);
+	now->tv_nsec = 0;
+}
+
+static void increment_version(struct ext2_inode_large *inode)
+{
+	__u64 ver;
+
+	ver = inode->osd1.linux1.l_i_version;
+	if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+		ver |= (__u64)inode->i_version_hi << 32;
+	ver++;
+	inode->osd1.linux1.l_i_version = ver;
+	if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+		inode->i_version_hi = ver >> 32;
+}
+
+static void init_times(struct ext2_inode_large *inode)
+{
+	struct timespec now;
+
+	get_now(&now);
+	EXT4_INODE_SET_XTIME(i_atime, &now, inode);
+	EXT4_INODE_SET_XTIME(i_ctime, &now, inode);
+	EXT4_INODE_SET_XTIME(i_mtime, &now, inode);
+	EXT4_EINODE_SET_XTIME(i_crtime, &now, inode);
+	increment_version(inode);
+}
+
+static int update_ctime(ext2_filsys fs, ext2_ino_t ino,
+			struct ext2_inode_large *pinode)
+{
+	errcode_t err;
+	struct timespec now;
+	struct ext2_inode_large inode;
+
+	get_now(&now);
+
+	/* If user already has a inode buffer, just update that */
+	if (pinode) {
+		increment_version(pinode);
+		EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+		return 0;
+	}
+
+	/* Otherwise we have to read-modify-write the inode */
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	increment_version(&inode);
+	EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int update_atime(ext2_filsys fs, ext2_ino_t ino)
+{
+	errcode_t err;
+	struct ext2_inode_large inode, *pinode;
+	struct timespec atime, mtime, now;
+	double datime, dmtime, dnow;
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	pinode = &inode;
+	EXT4_INODE_GET_XTIME(i_atime, &atime, pinode);
+	EXT4_INODE_GET_XTIME(i_mtime, &mtime, pinode);
+	get_now(&now);
+
+	datime = atime.tv_sec + ((double)atime.tv_nsec / 1000000000);
+	dmtime = mtime.tv_sec + ((double)mtime.tv_nsec / 1000000000);
+	dnow = now.tv_sec + ((double)now.tv_nsec / 1000000000);
+
+	/*
+	 * If atime is newer than mtime and atime hasn't been updated in thirty
+	 * seconds, skip the atime update.  Same idea as Linux "relatime".  Use
+	 * doubles to account for nanosecond resolution.
+	 */
+	if (datime >= dmtime && datime >= dnow - 30)
+		return 0;
+	EXT4_INODE_SET_XTIME(i_atime, &now, &inode);
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int update_mtime(ext2_filsys fs, ext2_ino_t ino,
+			struct ext2_inode_large *pinode)
+{
+	errcode_t err;
+	struct ext2_inode_large inode;
+	struct timespec now;
+
+	if (pinode) {
+		get_now(&now);
+		EXT4_INODE_SET_XTIME(i_mtime, &now, pinode);
+		EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+		increment_version(pinode);
+		return 0;
+	}
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	get_now(&now);
+	EXT4_INODE_SET_XTIME(i_mtime, &now, &inode);
+	EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+	increment_version(&inode);
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int ext2_file_type(unsigned int mode)
+{
+	if (LINUX_S_ISREG(mode))
+		return EXT2_FT_REG_FILE;
+
+	if (LINUX_S_ISDIR(mode))
+		return EXT2_FT_DIR;
+
+	if (LINUX_S_ISCHR(mode))
+		return EXT2_FT_CHRDEV;
+
+	if (LINUX_S_ISBLK(mode))
+		return EXT2_FT_BLKDEV;
+
+	if (LINUX_S_ISLNK(mode))
+		return EXT2_FT_SYMLINK;
+
+	if (LINUX_S_ISFIFO(mode))
+		return EXT2_FT_FIFO;
+
+	if (LINUX_S_ISSOCK(mode))
+		return EXT2_FT_SOCK;
+
+	return 0;
+}
+
+static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
+{
+	ext2_filsys fs = ff->fs;
+	blk64_t reserved;
+
+	dbg_printf(ff, "%s: Asking for %llu; alloc_all=%d total=%llu free=%llu "
+		   "rsvd=%llu\n", __func__, num, ff->alloc_all_blocks,
+		   ext2fs_blocks_count(fs->super),
+		   ext2fs_free_blocks_count(fs->super),
+		   ext2fs_r_blocks_count(fs->super));
+	if (num > ext2fs_blocks_count(fs->super))
+		return 0;
+
+	if (ff->alloc_all_blocks)
+		return 1;
+
+	/*
+	 * Different meaning for r_blocks -- libext2fs has bugs where the FS
+	 * can get corrupted if it totally runs out of blocks.  Avoid this
+	 * by refusing to allocate any of the reserve blocks to anybody.
+	 */
+	reserved = ext2fs_r_blocks_count(fs->super);
+	if (reserved == 0)
+		reserved = ext2fs_blocks_count(fs->super) / 10;
+	return ext2fs_free_blocks_count(fs->super) > reserved + num;
+}
+
+static int fuse4fs_is_writeable(struct fuse4fs *ff)
+{
+	return ff->opstate == F4OP_WRITABLE &&
+		(ff->fs->super->s_error_count == 0);
+}
+
+static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
+{
+	if (ff->fakeroot)
+		return 1;
+	return ctxt->uid == 0;
+}
+
+static inline int want_check_owner(struct fuse4fs *ff,
+				   struct fuse_context *ctxt)
+{
+	/*
+	 * The kernel is responsible for access control, so we allow anything
+	 * that the superuser can do.
+	 */
+	if (ff->kernel)
+		return 0;
+	return !is_superuser(ff, ctxt);
+}
+
+/* Test for append permission */
+#define A_OK	16
+
+static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
+			       const struct ext2_inode *inode, int mask)
+{
+	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)) && !fuse4fs_is_writeable(ff))
+		return -EROFS;
+
+	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? */
+	if ((mask & W_OK) &&
+	    (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;
+}
+
+static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	ext2_filsys fs = ff->fs;
+	struct ext2_inode inode;
+	mode_t perms;
+	errcode_t err;
+	int ret;
+
+	/* no writing to read-only or broken fs */
+	if ((mask & (W_OK | A_OK)) && !fuse4fs_is_writeable(ff))
+		return -EROFS;
+
+	err = ext2fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+	perms = inode.i_mode & 0777;
+
+	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);
+
+	/* existence check */
+	if (mask == 0)
+		return 0;
+
+	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)
+		return 0;
+
+	/* Figure out what root's allowed to do */
+	if (is_superuser(ff, ctxt)) {
+		/* Non-file access always ok */
+		if (!LINUX_S_ISREG(inode.i_mode))
+			return 0;
+
+		/* R/W access to a file always ok */
+		if (!(mask & X_OK))
+			return 0;
+
+		/* X access to a file ok if a user/group/other can X */
+		if (perms & 0111)
+			return 0;
+
+		/* Trying to execute a file that's not executable. BZZT! */
+		return -EACCES;
+	}
+
+	/* Remove the O_APPEND flag before testing permissions */
+	mask &= ~A_OK;
+
+	/* allow owner, if perms match */
+	if (inode_uid(inode) == ctxt->uid) {
+		if ((mask & (perms >> 6)) == mask)
+			return 0;
+		return -EACCES;
+	}
+
+	/* allow group, if perms match */
+	if (inode_gid(inode) == ctxt->gid) {
+		if ((mask & (perms >> 3)) == mask)
+			return 0;
+		return -EACCES;
+	}
+
+	/* otherwise check other */
+	if ((mask & perms) == mask)
+		return 0;
+	return -EACCES;
+}
+
+static errcode_t fuse4fs_check_support(struct fuse4fs *ff)
+{
+	ext2_filsys fs = ff->fs;
+
+	if (ext2fs_has_feature_quota(fs->super)) {
+		err_printf(ff, "%s\n", _("quotas not supported."));
+		return EXT2_ET_UNSUPP_FEATURE;
+	}
+	if (ext2fs_has_feature_verity(fs->super)) {
+		err_printf(ff, "%s\n", _("verity not supported."));
+		return EXT2_ET_UNSUPP_FEATURE;
+	}
+	if (ext2fs_has_feature_encrypt(fs->super)) {
+		err_printf(ff, "%s\n", _("encryption not supported."));
+		return EXT2_ET_UNSUPP_FEATURE;
+	}
+	if (ext2fs_has_feature_casefold(fs->super)) {
+		err_printf(ff, "%s\n", _("casefolding not supported."));
+		return EXT2_ET_UNSUPP_FEATURE;
+	}
+
+	if (fs->super->s_state & EXT2_ERROR_FS) {
+		err_printf(ff, "%s\n",
+ _("Errors detected; running e2fsck is required."));
+		return EXT2_ET_FILESYSTEM_CORRUPTED;
+	}
+
+	return 0;
+}
+
+static errcode_t fuse4fs_acquire_lockfile(struct fuse4fs *ff)
+{
+	char *resolved;
+	int lockfd;
+	errcode_t err;
+
+	lockfd = open(ff->lockfile, O_RDWR | O_CREAT | O_EXCL, 0400);
+	if (lockfd < 0) {
+		if (errno == EEXIST)
+			err = EWOULDBLOCK;
+		else
+			err = errno;
+		err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+			   _("opening lockfile failed"),
+			   strerror(err));
+		ff->lockfile = NULL;
+		return err;
+	}
+	close(lockfd);
+
+	resolved = realpath(ff->lockfile, NULL);
+	if (!resolved) {
+		err = errno;
+		err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+			   _("resolving lockfile failed"),
+			   strerror(err));
+		unlink(ff->lockfile);
+		ff->lockfile = NULL;
+		return err;
+	}
+	free(ff->lockfile);
+	ff->lockfile = resolved;
+
+	return 0;
+}
+
+static void fuse4fs_release_lockfile(struct fuse4fs *ff)
+{
+	if (unlink(ff->lockfile)) {
+		errcode_t err = errno;
+
+		err_printf(ff, "%s: %s: %s\n", ff->lockfile,
+			   _("removing lockfile failed"),
+			   strerror(err));
+	}
+	free(ff->lockfile);
+}
+
+static void fuse4fs_unmount(struct fuse4fs *ff)
+{
+	char uuid[UUID_STR_SIZE];
+	errcode_t err;
+
+	if (ff->fs) {
+		uuid_unparse(ff->fs->super->s_uuid, uuid);
+		err = ext2fs_close_free(&ff->fs);
+		if (err)
+			err_printf(ff, "%s: %s\n", _("while closing fs"),
+				   error_message(err));
+
+		if (ff->kernel)
+			log_printf(ff, "%s %s.\n", _("unmounted filesystem"),
+				   uuid);
+	}
+
+	if (ff->lockfile)
+		fuse4fs_release_lockfile(ff);
+}
+
+static errcode_t fuse4fs_open(struct fuse4fs *ff)
+{
+	char options[128];
+	double deadline;
+	int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_RW |
+		    EXT2_FLAG_EXCLUSIVE;
+	errcode_t err;
+
+	if (ff->lockfile) {
+		err = fuse4fs_acquire_lockfile(ff);
+		if (err)
+			return err;
+	}
+
+	snprintf(options, sizeof(options) - 1, "offset=%lu", ff->offset);
+	ff->opstate = F4OP_READONLY;
+
+	if (ff->directio)
+		flags |= EXT2_FLAG_DIRECT_IO;
+
+	/*
+	 * If the filesystem is stored on a block device, the _EXCLUSIVE flag
+	 * causes libext2fs to try to open the block device with O_EXCL.  If
+	 * the block device is already opened O_EXCL by something else, the
+	 * open call returns EBUSY.
+	 *
+	 * Unfortunately, there's a nasty race between fuse4fs going through
+	 * its startup sequence (open fs, parse superblock, daemonize, create
+	 * mount, respond to FUSE_INIT) in response to a mount(8) invocation
+	 * and another process that calls umount(2) on the same mount.
+	 *
+	 * If fuse4fs is being run as a mount(8) helper and has daemonized, the
+	 * original fuse4fs subprocess exits and so will mount(8).  This can
+	 * occur before the kernel issues a FUSE_INIT request to fuse4fs.  If
+	 * a process then umount(2)'s the mount, the kernel will abort the
+	 * fuse connection.  If the FUSE_INIT request hasn't been issued, now
+	 * it won't ever be issued.  The kernel tears down the mount and
+	 * returns from umount(2), but fuse4fs has no idea that any of this has
+	 * happened because it receives no requests.
+	 *
+	 * At this point, the original fuse4fs server holds the block device
+	 * open O_EXCL.  If mount(8) is invoked again on the same device, the
+	 * new fuse4fs server will try to open the block device O_EXCL and
+	 * fail.  A crappy solution here is to retry for 5 seconds, hoping that
+	 * the first fuse4fs server will wake up and exit.
+	 *
+	 * If the filesystem is in a regular file, O_EXCL (without O_CREAT) has
+	 * no defined behavior, but it never returns EBUSY.
+	 */
+	deadline = init_deadline(FUSE4FS_OPEN_TIMEOUT);
+	do {
+		err = ext2fs_open2(ff->device, options, flags, 0, 0,
+				   unix_io_manager, &ff->fs);
+		if ((err == EPERM || err == EACCES) &&
+		    (!ff->ro || (flags & EXT2_FLAG_RW))) {
+			/*
+			 * Source device cannot be opened for write.  Under
+			 * these circumstances, mount(8) will try again with a
+			 * ro mount, and the kernel will open the block device
+			 * readonly.
+			 */
+			log_printf(ff, "%s\n",
+ _("WARNING: source write-protected, mounted read-only."));
+			flags &= ~EXT2_FLAG_RW;
+			ff->ro = 1;
+
+			/* Force the loop to run once more */
+			err = -1;
+		}
+	} while (err == -1 ||
+		 (err == EBUSY && retry_before_deadline(deadline)));
+	if (err == EBUSY) {
+		err_printf(ff, "%s: %s.\n",
+ _("Could not lock filesystem block device"), error_message(err));
+		return err;
+	}
+	if (err) {
+		err_printf(ff, "%s.\n", error_message(err));
+		err_printf(ff, "%s\n", _("Please run e2fsck -fy."));
+		return err;
+	}
+
+	/*
+	 * If the filesystem is stored in a regular file, take an (advisory)
+	 * exclusive lock to prevent other instances of e2fsprogs from writing
+	 * to the filesystem image.  On Linux we don't want to do this for
+	 * block devices because udev will spin forever trying to settle a
+	 * uevent and cause weird userspace stalls, and block devices have
+	 * O_EXCL so we don't need this there.
+	 */
+	if (!(ff->fs->io->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
+		unsigned int lock_flags = IO_CHANNEL_FLOCK_TRYLOCK;
+
+		if (ff->fs->flags & IO_FLAG_RW)
+			lock_flags |= IO_CHANNEL_FLOCK_EXCLUSIVE;
+		else
+			lock_flags |= IO_CHANNEL_FLOCK_SHARED;
+
+		deadline = init_deadline(FUSE4FS_OPEN_TIMEOUT);
+		do {
+			err = io_channel_flock(ff->fs->io, lock_flags);
+		} while (err == EWOULDBLOCK && retry_before_deadline(deadline));
+		if (err) {
+			err_printf(ff, "%s: %s\n",
+ _("Could not lock filesystem image"), error_message(err));
+			return err;
+		}
+	}
+
+	if (ff->kernel) {
+		char uuid[UUID_STR_SIZE];
+
+		uuid_unparse(ff->fs->super->s_uuid, uuid);
+		log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
+	}
+
+	ff->fs->priv_data = ff;
+	ff->blocklog = u_log2(ff->fs->blocksize);
+	ff->blockmask = ff->fs->blocksize - 1;
+
+	fuse4fs_mmp_config(ff);
+	return 0;
+}
+
+/* Figure out a reasonable default size for the disk cache */
+static unsigned long long default_cache_size(void)
+{
+	long pages = 0, pagesize = 0;
+	unsigned long long max_cache;
+	unsigned long long ret = 32ULL << 20; /* 32 MB */
+
+#ifdef _SC_PHYS_PAGES
+	pages = sysconf(_SC_PHYS_PAGES);
+#endif
+#ifdef _SC_PAGESIZE
+	pagesize = sysconf(_SC_PAGESIZE);
+#endif
+	if (pages > 0 && pagesize > 0) {
+		max_cache = (unsigned long long)pagesize * pages / 20;
+
+		if (max_cache > 0 && ret > max_cache)
+			ret = max_cache;
+	}
+	return ret;
+}
+
+static errcode_t fuse4fs_config_cache(struct fuse4fs *ff)
+{
+	char buf[128];
+	errcode_t err;
+
+	if (!ff->cache_size)
+		ff->cache_size = default_cache_size();
+	if (!ff->cache_size)
+		return 0;
+
+	snprintf(buf, sizeof(buf), "cache_blocks=%llu",
+		 FUSE4FS_B_TO_FSBT(ff, ff->cache_size));
+	err = io_channel_set_options(ff->fs->io, buf);
+	if (err) {
+		err_printf(ff, "%s %lluk: %s\n",
+			   _("cannot set disk cache size to"),
+			   ff->cache_size >> 10,
+			   error_message(err));
+		return err;
+	}
+
+	return 0;
+}
+
+static int fuse4fs_mount(struct fuse4fs *ff)
+{
+	struct ext2_inode_large inode;
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+
+	if (ext2fs_has_feature_journal_needs_recovery(fs->super)) {
+		if (ff->norecovery) {
+			log_printf(ff, "%s\n",
+ _("Mounting read-only without recovering journal."));
+			ff->ro = 1;
+			ff->fs->flags &= ~EXT2_FLAG_RW;
+		} else if (!(fs->flags & EXT2_FLAG_RW)) {
+			err_printf(ff, "%s\n",
+ _("Cannot replay journal on read-only device."));
+			return -1;
+		} else {
+			log_printf(ff, "%s\n", _("Recovering journal."));
+			err = ext2fs_run_ext3_journal(&ff->fs);
+			if (err) {
+				err_printf(ff, "%s.\n", error_message(err));
+				err_printf(ff, "%s\n",
+						_("Please run e2fsck -fy."));
+				return translate_error(fs, 0, err);
+			}
+			fs = ff->fs;
+
+			err = fuse4fs_check_support(ff);
+			if (err)
+				return err;
+		}
+	} else if (ext2fs_has_feature_journal(fs->super)) {
+		err = ext2fs_check_ext3_journal(fs);
+		if (err)
+			return translate_error(fs, 0, err);
+	}
+
+	/* Make sure the root directory is readable. */
+	err = fuse4fs_read_inode(fs, EXT2_ROOT_INO, &inode);
+	if (err)
+		return translate_error(fs, EXT2_ROOT_INO, err);
+
+	if (fs->flags & EXT2_FLAG_RW) {
+		if (ext2fs_has_feature_journal(fs->super))
+			log_printf(ff, "%s",
+ _("Warning: fuse4fs 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"));
+		ff->opstate = F4OP_WRITABLE;
+	}
+
+	if (!(fs->super->s_state & EXT2_VALID_FS))
+		err_printf(ff, "%s\n",
+ _("Warning: Mounting unchecked fs, running e2fsck is recommended."));
+	if (fs->super->s_max_mnt_count > 0 &&
+	    fs->super->s_mnt_count >= fs->super->s_max_mnt_count)
+		err_printf(ff, "%s\n",
+ _("Warning: Maximal mount count reached, running e2fsck is recommended."));
+	if (fs->super->s_checkinterval > 0 &&
+	    (time_t) (fs->super->s_lastcheck +
+		      fs->super->s_checkinterval) <= time(0))
+		err_printf(ff, "%s\n",
+ _("Warning: Check time reached; running e2fsck is recommended."));
+	if (fs->super->s_last_orphan)
+		err_printf(ff, "%s\n",
+ _("Orphans detected; running e2fsck is recommended."));
+
+	if (!ff->errors_behavior)
+		ff->errors_behavior = fs->super->s_errors;
+
+	/* Clear the valid flag so that an unclean shutdown forces a fsck */
+	if (ff->opstate == F4OP_WRITABLE) {
+		fs->super->s_mnt_count++;
+		ext2fs_set_tstamp(fs->super, s_mtime, time(NULL));
+		fs->super->s_state &= ~EXT2_VALID_FS;
+		ext2fs_mark_super_dirty(fs);
+		err = ext2fs_flush2(fs, 0);
+		if (err)
+			return translate_error(fs, 0, err);
+	}
+
+	return 0;
+}
+
+static void op_destroy(void *p EXT2FS_ATTR((unused)))
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	errcode_t err;
+
+	FUSE4FS_CHECK_CONTEXT_DESTROY(ff);
+
+	fs = fuse4fs_start(ff);
+
+	dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
+	if (ff->opstate == F4OP_WRITABLE) {
+		fs->super->s_state |= EXT2_VALID_FS;
+		if (fs->super->s_error_count)
+			fs->super->s_state |= EXT2_ERROR_FS;
+		ext2fs_mark_super_dirty(fs);
+		err = ext2fs_set_gdt_csum(fs);
+		if (err)
+			translate_error(fs, 0, err);
+
+		err = ext2fs_flush2(fs, 0);
+		if (err)
+			translate_error(fs, 0, err);
+	}
+
+	if (ff->debug && fs->io->manager->get_stats) {
+		io_stats stats = NULL;
+
+		fs->io->manager->get_stats(fs->io, &stats);
+		dbg_printf(ff, "read: %lluk\n",  stats->bytes_read >> 10);
+		dbg_printf(ff, "write: %lluk\n", stats->bytes_written >> 10);
+		dbg_printf(ff, "hits: %llu\n",   stats->cache_hits);
+		dbg_printf(ff, "misses: %llu\n", stats->cache_misses);
+		dbg_printf(ff, "hit_ratio: %.1f%%\n",
+				(100.0 * stats->cache_hits) /
+				(stats->cache_hits + stats->cache_misses));
+	}
+
+	fuse4fs_finish(ff, 0);
+}
+
+/* Reopen @stream with @fileno */
+static int fuse4fs_freopen_stream(const char *path, int fileno, FILE *stream)
+{
+	char _fdpath[256];
+	const char *fdpath;
+	FILE *fp;
+	int ret;
+
+	ret = snprintf(_fdpath, sizeof(_fdpath), "/dev/fd/%d", fileno);
+	if (ret >= sizeof(_fdpath))
+		fdpath = path;
+	else
+		fdpath = _fdpath;
+
+	/*
+	 * C23 defines std{out,err} as an expression of type FILE* that need
+	 * not be an lvalue.  What this means is that we can't just assign to
+	 * stdout: we have to use freopen, which takes a path.
+	 *
+	 * There's no guarantee that the OS provides a /dev/fd/X alias for open
+	 * file descriptors, so if that fails, fall back to the original log
+	 * file path.  We'd rather not do a path-based reopen because that
+	 * exposes us to rename race attacks.
+	 */
+	fp = freopen(fdpath, "a", stream);
+	if (!fp && errno == ENOENT && fdpath == _fdpath)
+		fp = freopen(path, "a", stream);
+	if (!fp) {
+		perror(fdpath);
+		return -1;
+	}
+
+	return 0;
+}
+
+/* Redirect stdout/stderr to a file, or return a mount-compatible error. */
+static int fuse4fs_capture_output(struct fuse4fs *ff, const char *path)
+{
+	int ret;
+	int fd;
+
+	/*
+	 * First, open the log file path with system calls so that we can
+	 * redirect the stdout/stderr file numbers (typically 1 and 2) to our
+	 * logfile descriptor.  We'd like to avoid allocating extra file
+	 * objects in the kernel if we can because pos will be the same between
+	 * stdout and stderr.
+	 */
+	if (ff->logfd < 0) {
+		fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600);
+		if (fd < 0) {
+			perror(path);
+			return -1;
+		}
+
+		/*
+		 * Save the newly opened fd in case we have to do this again in
+		 * op_init.
+		 */
+		ff->logfd = fd;
+	}
+
+	ret = dup2(ff->logfd, STDOUT_FILENO);
+	if (ret < 0) {
+		perror(path);
+		return -1;
+	}
+
+	ret = dup2(ff->logfd, STDERR_FILENO);
+	if (ret < 0) {
+		perror(path);
+		return -1;
+	}
+
+	/*
+	 * Now that we've changed STD{OUT,ERR}_FILENO to be the log file, use
+	 * freopen to make sure that std{out,err} (the C library abstractions)
+	 * point to the STDXXX_FILENO because any of our library dependencies
+	 * might decide to printf to one of those streams and we want to
+	 * capture all output in the log.
+	 */
+	ret = fuse4fs_freopen_stream(path, STDOUT_FILENO, stdout);
+	if (ret)
+		return ret;
+	ret = fuse4fs_freopen_stream(path, STDERR_FILENO, stderr);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* Set up debug and error logging files */
+static int fuse4fs_setup_logging(struct fuse4fs *ff)
+{
+	char *logfile = getenv("FUSE4FS_LOGFILE");
+	if (logfile)
+		return fuse4fs_capture_output(ff, logfile);
+
+	/* in kernel mode, try to log errors to the kernel log */
+	if (ff->kernel)
+		fuse4fs_capture_output(ff, "/dev/ttyprintk");
+
+	return 0;
+}
+
+static int fuse4fs_read_bitmaps(struct fuse4fs *ff)
+{
+	errcode_t err;
+
+	err = ext2fs_read_inode_bitmap(ff->fs);
+	if (err)
+		return translate_error(ff->fs, 0, err);
+
+	err = ext2fs_read_block_bitmap(ff->fs);
+	if (err)
+		return translate_error(ff->fs, 0, err);
+
+	return 0;
+}
+
+#if FUSE_VERSION < FUSE_MAKE_VERSION(3, 17)
+static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,
+					 uint64_t flag)
+{
+	if (conn->capable & flag) {
+		conn->want |= flag;
+		return 1;
+	}
+
+	return 0;
+}
+#endif
+
+static void *op_init(struct fuse_conn_info *conn,
+		     struct fuse_config *cfg EXT2FS_ATTR((unused)))
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+
+	FUSE4FS_CHECK_CONTEXT_INIT(ff);
+
+	/*
+	 * Configure logging a second time, because libfuse might have
+	 * redirected std{out,err} as part of daemonization.  If this fails,
+	 * give up and move on.
+	 */
+	fuse4fs_setup_logging(ff);
+	if (ff->logfd >= 0)
+		close(ff->logfd);
+	ff->logfd = -1;
+
+	fs = ff->fs;
+	dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
+#ifdef FUSE_CAP_IOCTL_DIR
+	fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR);
+#endif
+#ifdef FUSE_CAP_POSIX_ACL
+	if (ff->acl)
+		fuse_set_feature_flag(conn, FUSE_CAP_POSIX_ACL);
+#endif
+#ifdef FUSE_CAP_CACHE_SYMLINKS
+	fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS);
+#endif
+#ifdef FUSE_CAP_NO_EXPORT_SUPPORT
+	fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);
+#endif
+	conn->time_gran = 1;
+	cfg->use_ino = 1;
+	if (ff->debug)
+		cfg->debug = 1;
+	cfg->nullpath_ok = 1;
+
+	if (ff->opstate == F4OP_WRITABLE)
+		fuse4fs_read_bitmaps(ff);
+
+	/*
+	 * Background threads must be started from op_init because libfuse
+	 * might daemonize us in fuse_main() by forking, and threads are not
+	 * conveyed to the new child process.
+	 */
+	fuse4fs_mmp_start(ff);
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+	/*
+	 * THIS MUST GO LAST!
+	 *
+	 * fuse_set_feature_flag in 3.17.0 has a strange bug: it sets feature
+	 * flags in conn->want_ext, but not conn->want.  Upon return to
+	 * libfuse, the lower level library observes that want and want_ext
+	 * have gotten out of sync, and refuses to mount.  Therefore,
+	 * synchronize the two.  This bug went away in 3.17.3, but we're stuck
+	 * with this forever because Debian trixie released with 3.17.2.
+	 */
+	conn->want = conn->want_ext & 0xFFFFFFFF;
+#endif
+	return ff;
+}
+
+static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
+{
+	struct ext2_inode_large inode;
+	dev_t fakedev = 0;
+	errcode_t err;
+	int ret = 0;
+	struct timespec tv;
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	memcpy(&fakedev, fs->super->s_uuid, sizeof(fakedev));
+	statbuf->st_dev = fakedev;
+	statbuf->st_ino = ino;
+	statbuf->st_mode = inode.i_mode;
+	statbuf->st_nlink = inode.i_links_count;
+	statbuf->st_uid = inode_uid(inode);
+	statbuf->st_gid = inode_gid(inode);
+	statbuf->st_size = EXT2_I_SIZE(&inode);
+	statbuf->st_blksize = fs->blocksize;
+	statbuf->st_blocks = ext2fs_get_stat_i_blocks(fs,
+						EXT2_INODE(&inode));
+	EXT4_INODE_GET_XTIME(i_atime, &tv, &inode);
+#if HAVE_STRUCT_STAT_ST_ATIM
+	statbuf->st_atim = tv;
+#else
+	statbuf->st_atime = tv.tv_sec;
+#endif
+	EXT4_INODE_GET_XTIME(i_mtime, &tv, &inode);
+#if HAVE_STRUCT_STAT_ST_ATIM
+	statbuf->st_mtim = tv;
+#else
+	statbuf->st_mtime = tv.tv_sec;
+#endif
+	EXT4_INODE_GET_XTIME(i_ctime, &tv, &inode);
+#if HAVE_STRUCT_STAT_ST_ATIM
+	statbuf->st_ctim = tv;
+#else
+	statbuf->st_ctime = tv.tv_sec;
+#endif
+	if (LINUX_S_ISCHR(inode.i_mode) ||
+	    LINUX_S_ISBLK(inode.i_mode)) {
+		if (inode.i_block[0])
+			statbuf->st_rdev = inode.i_block[0];
+		else
+			statbuf->st_rdev = inode.i_block[1];
+	}
+
+	return ret;
+}
+
+static int __fuse4fs_file_ino(struct fuse4fs *ff, const char *path,
+			      struct fuse_file_info *fp EXT2FS_ATTR((unused)),
+			      ext2_ino_t *inop,
+			      const char *func,
+			      int line)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+
+	if (fp) {
+		struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+
+		if (fh->ino == 0)
+			return -ESTALE;
+
+		*inop = fh->ino;
+		dbg_printf(ff, "%s: get ino=%d\n", func, fh->ino);
+		return 0;
+	}
+
+	dbg_printf(ff, "%s: get path=%s\n", func, path);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, inop);
+	if (err)
+		return __translate_error(fs, 0, err, func, line);
+
+	return 0;
+}
+
+# define fuse4fs_file_ino(ff, path, fp, inop) \
+	__fuse4fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
+
+static int op_getattr(const char *path, struct stat *statbuf,
+		      struct fuse_file_info *fi)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	ext2_ino_t ino;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	ret = fuse4fs_file_ino(ff, path, fi, &ino);
+	if (ret)
+		goto out;
+	ret = stat_inode(fs, ino, statbuf);
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_readlink(const char *path, char *buf, size_t len)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	errcode_t err;
+	ext2_ino_t ino;
+	struct ext2_inode inode;
+	unsigned int got;
+	ext2_file_t file;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: path=%s\n", __func__, path);
+	fs = fuse4fs_start(ff);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err || ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	err = ext2fs_read_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+	if (!LINUX_S_ISLNK(inode.i_mode)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	len--;
+	if (inode.i_size < len)
+		len = inode.i_size;
+	if (ext2fs_is_fast_symlink(&inode))
+		memcpy(buf, (char *)inode.i_block, len);
+	else {
+		/* big/inline symlink */
+
+		err = ext2fs_file_open(fs, ino, 0, &file);
+		if (err) {
+			ret = translate_error(fs, ino, err);
+			goto out;
+		}
+
+		err = ext2fs_file_read(file, buf, len, &got);
+		if (err)
+			ret = translate_error(fs, ino, err);
+		else if (got != len)
+			ret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+
+		err = ext2fs_file_close(file);
+		if (ret)
+			goto out;
+		if (err) {
+			ret = translate_error(fs, ino, err);
+			goto out;
+		}
+	}
+	buf[len] = 0;
+
+	if (fuse4fs_is_writeable(ff)) {
+		ret = update_atime(fs, ino);
+		if (ret)
+			goto out;
+	}
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int __getxattr(struct fuse4fs *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 fuse4fs *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 fuse4fs *ff, ext2_ino_t parent,
+				  ext2_ino_t child, mode_t mode)
+{
+	void *def;
+	size_t deflen;
+	int ret;
+
+	if (!ff->acl || S_ISDIR(mode))
+		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 inline void fuse4fs_set_uid(struct ext2_inode_large *inode, uid_t uid)
+{
+	inode->i_uid = uid;
+	ext2fs_set_i_uid_high(*inode, uid >> 16);
+}
+
+static inline void fuse4fs_set_gid(struct ext2_inode_large *inode, gid_t gid)
+{
+	inode->i_gid = gid;
+	ext2fs_set_i_gid_high(*inode, gid >> 16);
+}
+
+static int fuse4fs_new_child_gid(struct fuse4fs *ff, ext2_ino_t parent,
+				 gid_t *gid, int *parent_sgid)
+{
+	struct ext2_inode_large inode;
+	struct fuse_context *ctxt = fuse_get_context();
+	errcode_t err;
+
+	err = fuse4fs_read_inode(ff->fs, parent, &inode);
+	if (err)
+		return translate_error(ff->fs, parent, err);
+
+	if (inode.i_mode & S_ISGID) {
+		if (parent_sgid)
+			*parent_sgid = 1;
+		*gid = inode.i_gid;
+	} else {
+		if (parent_sgid)
+			*parent_sgid = 0;
+		*gid = ctxt->gid;
+	}
+
+	return 0;
+}
+
+/*
+ * Flush dirty data to disk if we're running in dirsync mode.  If @flushed is a
+ * non-null pointer, this function sets @flushed to 1 if we decided to flush
+ * data, or 0 if not.
+ */
+static inline int fuse4fs_dirsync_flush(struct fuse4fs *ff, ext2_ino_t ino,
+					int *flushed)
+{
+	struct ext2_inode_large inode;
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+
+	if (ff->dirsync)
+		goto flush;
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, 0, err);
+
+	if (inode.i_flags & EXT2_DIRSYNC_FL)
+		goto flush;
+
+	if (flushed)
+		*flushed = 0;
+	return 0;
+flush:
+	err = ext2fs_flush2(fs, 0);
+	if (err)
+		return translate_error(fs, 0, err);
+
+	if (flushed)
+		*flushed = 1;
+	return 0;
+}
+
+static void fuse4fs_set_extra_isize(struct fuse4fs *ff, ext2_ino_t ino,
+				    struct ext2_inode_large *inode)
+{
+	ext2_filsys fs = ff->fs;
+	size_t extra = sizeof(struct ext2_inode_large) -
+		EXT2_GOOD_OLD_INODE_SIZE;
+
+	if (ext2fs_has_feature_extra_isize(fs->super)) {
+		dbg_printf(ff, "%s: ino=%u extra=%zu want=%u min=%u\n",
+			   __func__, ino, extra, fs->super->s_want_extra_isize,
+			   fs->super->s_min_extra_isize);
+
+		if (fs->super->s_want_extra_isize > extra)
+			extra = fs->super->s_want_extra_isize;
+		if (fs->super->s_min_extra_isize > extra)
+			extra = fs->super->s_min_extra_isize;
+	}
+
+	inode->i_extra_isize = extra;
+}
+
+static int op_mknod(const char *path, mode_t mode, dev_t dev)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	char *temp_path;
+	errcode_t err;
+	char *node_name, a;
+	int filetype;
+	struct ext2_inode_large inode;
+	gid_t gid;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
+		   (unsigned int)dev);
+	temp_path = strdup(path);
+	if (!temp_path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name = strrchr(temp_path, '/');
+	if (!node_name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name++;
+	a = *node_name;
+	*node_name = 0;
+
+	fs = fuse4fs_start(ff);
+	if (!fs_can_allocate(ff, 2)) {
+		ret = -ENOSPC;
+		goto out2;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &parent);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	if (ret)
+		goto out2;
+
+	*node_name = a;
+
+	if (LINUX_S_ISCHR(mode))
+		filetype = EXT2_FT_CHRDEV;
+	else if (LINUX_S_ISBLK(mode))
+		filetype = EXT2_FT_BLKDEV;
+	else if (LINUX_S_ISFIFO(mode))
+		filetype = EXT2_FT_FIFO;
+	else if (LINUX_S_ISSOCK(mode))
+		filetype = EXT2_FT_SOCK;
+	else {
+		ret = -EINVAL;
+		goto out2;
+	}
+
+	err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+	if (err)
+		goto out2;
+
+	err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+
+	dbg_printf(ff, "%s: create ino=%d/name=%s in dir=%d\n", __func__, child,
+		   node_name, parent);
+	err = ext2fs_link(fs, parent, node_name, child,
+			  filetype | EXT2FS_LINK_EXPAND);
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out2;
+	}
+
+	ret = update_mtime(fs, parent, NULL);
+	if (ret)
+		goto out2;
+
+	memset(&inode, 0, sizeof(inode));
+	inode.i_mode = mode;
+
+	if (dev & ~0xFFFF)
+		inode.i_block[1] = dev;
+	else
+		inode.i_block[0] = dev;
+	inode.i_links_count = 1;
+	fuse4fs_set_extra_isize(ff, child, &inode);
+	fuse4fs_set_uid(&inode, ctxt->uid);
+	fuse4fs_set_gid(&inode, gid);
+
+	err = ext2fs_write_new_inode(fs, child, EXT2_INODE(&inode));
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	inode.i_generation = ff->next_generation++;
+	init_times(&inode);
+	err = fuse4fs_write_inode(fs, child, &inode);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+	if (ret)
+		goto out2;
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
+out2:
+	fuse4fs_finish(ff, ret);
+out:
+	free(temp_path);
+	return ret;
+}
+
+static int op_mkdir(const char *path, mode_t mode)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	char *temp_path;
+	errcode_t err;
+	char *node_name, a;
+	struct ext2_inode_large inode;
+	char *block;
+	blk64_t blk;
+	int ret = 0;
+	gid_t gid;
+	int parent_sgid;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
+	temp_path = strdup(path);
+	if (!temp_path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name = strrchr(temp_path, '/');
+	if (!node_name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name++;
+	a = *node_name;
+	*node_name = 0;
+
+	fs = fuse4fs_start(ff);
+	if (!fs_can_allocate(ff, 1)) {
+		ret = -ENOSPC;
+		goto out2;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &parent);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	if (ret)
+		goto out2;
+
+	err = fuse4fs_new_child_gid(ff, parent, &gid, &parent_sgid);
+	if (err)
+		goto out2;
+
+	*node_name = a;
+
+	err = ext2fs_mkdir2(fs, parent, 0, 0, EXT2FS_LINK_EXPAND,
+			    node_name, NULL);
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out2;
+	}
+
+	ret = update_mtime(fs, parent, NULL);
+	if (ret)
+		goto out2;
+
+	/* Still have to update the uid/gid of the dir */
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &child);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+	dbg_printf(ff, "%s: created ino=%d/path=%s in dir=%d\n", __func__, child,
+		   node_name, parent);
+
+	err = fuse4fs_read_inode(fs, child, &inode);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	fuse4fs_set_extra_isize(ff, child, &inode);
+	fuse4fs_set_uid(&inode, ctxt->uid);
+	fuse4fs_set_gid(&inode, gid);
+	inode.i_mode = LINUX_S_IFDIR | (mode & ~S_ISUID);
+	if (parent_sgid)
+		inode.i_mode |= S_ISGID;
+	inode.i_generation = ff->next_generation++;
+	init_times(&inode);
+
+	err = fuse4fs_write_inode(fs, child, &inode);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	/* Rewrite the directory block checksum, having set i_generation */
+	if ((inode.i_flags & EXT4_INLINE_DATA_FL) ||
+	    !ext2fs_has_feature_metadata_csum(fs->super))
+		goto out2;
+	err = ext2fs_new_dir_block(fs, child, parent, &block);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+	err = ext2fs_bmap2(fs, child, EXT2_INODE(&inode), NULL, 0, 0,
+			   NULL, &blk);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out3;
+	}
+	err = ext2fs_write_dir_block4(fs, blk, block, 0, child);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out3;
+	}
+
+	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+	if (ret)
+		goto out3;
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out3;
+
+out3:
+	ext2fs_free_mem(&block);
+out2:
+	fuse4fs_finish(ff, ret);
+out:
+	free(temp_path);
+	return ret;
+}
+
+static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
+			  ext2_ino_t *parent)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	ext2_ino_t dir;
+	char *filename = strdup(path);
+	char *base_name;
+	int ret;
+
+	base_name = strrchr(filename, '/');
+	if (base_name) {
+		*base_name++ = '\0';
+		err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, filename,
+				   &dir);
+		if (err) {
+			free(filename);
+			return translate_error(fs, 0, err);
+		}
+	} else {
+		dir = EXT2_ROOT_INO;
+		base_name = filename;
+	}
+
+	ret = check_inum_access(ff, dir, W_OK);
+	if (ret) {
+		free(filename);
+		return ret;
+	}
+
+	dbg_printf(ff, "%s: unlinking name=%s from dir=%d\n", __func__,
+		   base_name, dir);
+	err = ext2fs_unlink(fs, dir, base_name, 0, 0);
+	free(filename);
+	if (err)
+		return translate_error(fs, dir, err);
+
+	ret = update_mtime(fs, dir, NULL);
+	if (ret)
+		return ret;
+
+	if (parent)
+		*parent = dir;
+	return 0;
+}
+
+static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
+			    struct ext2_inode_large *inode)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_xattr_handle *h;
+	errcode_t err;
+	int ret = 0;
+
+	/*
+	 * The xattr handle maintains its own private copy of the inode, so
+	 * write ours to disk so that we can read it.
+	 */
+	err = fuse4fs_write_inode(fs, ino, inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	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_remove_all(h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out_close;
+	}
+
+out_close:
+	ext2fs_xattrs_close(&h);
+	if (ret)
+		return ret;
+
+	/* Now read the inode back in. */
+	err = fuse4fs_read_inode(fs, ino, inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+	int ret = 0;
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	dbg_printf(ff, "%s: put ino=%d links=%d\n", __func__, ino,
+		   inode.i_links_count);
+
+	if (S_ISDIR(inode.i_mode)) {
+		/*
+		 * Caller should have checked that this is an empty directory
+		 * before starting the unlink process.  nlink is usually 2, but
+		 * it could be 1 if this dir ever had more than 65000 subdirs.
+		 * Zero the link count.
+		 */
+		if (!ext2fs_dir_link_empty(EXT2_INODE(&inode)))
+			return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+		inode.i_links_count = 0;
+		ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+	} else {
+		/*
+		 * Any other file type can be hardlinked, so all we need to do
+		 * is decrement the nlink.
+		 */
+		if (inode.i_links_count == 0)
+			return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+		inode.i_links_count--;
+		if (!inode.i_links_count)
+			ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+	}
+
+	ret = update_ctime(fs, ino, &inode);
+	if (ret)
+		return ret;
+
+	/* Still linked?  Leave it be. */
+	if (inode.i_links_count)
+		goto write_out;
+
+	if (ext2fs_has_feature_ea_inode(fs->super)) {
+		ret = remove_ea_inodes(ff, ino, &inode);
+		if (ret)
+			return ret;
+	}
+
+	/* Nobody holds this file; free its blocks! */
+	err = ext2fs_free_ext_attr(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(&inode))) {
+		err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), NULL,
+				   0, ~0ULL);
+		if (err)
+			return translate_error(fs, ino, err);
+	}
+
+	ext2fs_inode_alloc_stats2(fs, ino, -1,
+				  LINUX_S_ISDIR(inode.i_mode));
+
+write_out:
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int __op_unlink(struct fuse4fs *ff, const char *path)
+{
+	ext2_filsys fs = ff->fs;
+	ext2_ino_t parent, ino;
+	errcode_t err;
+	int ret = 0;
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	ret = check_inum_access(ff, ino, W_OK);
+	if (ret)
+		goto out;
+
+	ret = fuse4fs_unlink(ff, path, &parent);
+	if (ret)
+		goto out;
+
+	ret = remove_inode(ff, ino);
+	if (ret)
+		goto out;
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out;
+
+out:
+	return ret;
+}
+
+static int op_unlink(const char *path)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	int ret;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fuse4fs_start(ff);
+	ret = __op_unlink(ff, path);
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+struct rd_struct {
+	ext2_ino_t	parent;
+	int		empty;
+};
+
+static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
+		      int	entry EXT2FS_ATTR((unused)),
+		      struct ext2_dir_entry *dirent,
+		      int	offset EXT2FS_ATTR((unused)),
+		      int	blocksize EXT2FS_ATTR((unused)),
+		      char	*buf EXT2FS_ATTR((unused)),
+		      void	*private)
+{
+	struct rd_struct *rds = (struct rd_struct *) private;
+
+	if (dirent->inode == 0)
+		return 0;
+	if (((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.'))
+		return 0;
+	if (((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') &&
+	    (dirent->name[1] == '.')) {
+		rds->parent = dirent->inode;
+		return 0;
+	}
+	rds->empty = 0;
+	return 0;
+}
+
+static int __op_rmdir(struct fuse4fs *ff, const char *path)
+{
+	ext2_filsys fs = ff->fs;
+	ext2_ino_t parent, child;
+	errcode_t err;
+	struct ext2_inode_large inode;
+	struct rd_struct rds;
+	int ret = 0;
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &child);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	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;
+
+	err = ext2fs_dir_iterate2(fs, child, 0, 0, rmdir_proc, &rds);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		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;
+	}
+
+	ret = fuse4fs_unlink(ff, path, &parent);
+	if (ret)
+		goto out;
+	ret = remove_inode(ff, child);
+	if (ret)
+		goto out;
+
+	if (rds.parent) {
+		dbg_printf(ff, "%s: decr dir=%d link count\n", __func__,
+			   rds.parent);
+		err = fuse4fs_read_inode(fs, rds.parent, &inode);
+		if (err) {
+			ret = translate_error(fs, rds.parent, err);
+			goto out;
+		}
+		ext2fs_dec_nlink(EXT2_INODE(&inode));
+		ret = update_mtime(fs, rds.parent, &inode);
+		if (ret)
+			goto out;
+		err = fuse4fs_write_inode(fs, rds.parent, &inode);
+		if (err) {
+			ret = translate_error(fs, rds.parent, err);
+			goto out;
+		}
+	}
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out;
+
+out:
+	return ret;
+}
+
+static int op_rmdir(const char *path)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	int ret;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fuse4fs_start(ff);
+	ret = __op_rmdir(ff, path);
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_symlink(const char *src, const char *dest)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	char *temp_path;
+	errcode_t err;
+	char *node_name, a;
+	struct ext2_inode_large inode;
+	gid_t gid;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: symlink %s to %s\n", __func__, src, dest);
+	temp_path = strdup(dest);
+	if (!temp_path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name = strrchr(temp_path, '/');
+	if (!node_name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name++;
+	a = *node_name;
+	*node_name = 0;
+
+	fs = fuse4fs_start(ff);
+	if (!fs_can_allocate(ff, 1)) {
+		ret = -ENOSPC;
+		goto out2;
+	}
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &parent);
+	*node_name = a;
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	if (ret)
+		goto out2;
+
+	err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+	if (err)
+		goto out2;
+
+	/* Create symlink */
+	err = ext2fs_symlink(fs, parent, 0, node_name, src);
+	if (err == EXT2_ET_DIR_NO_SPACE) {
+		err = ext2fs_expand_dir(fs, parent);
+		if (err) {
+			ret = translate_error(fs, parent, err);
+			goto out2;
+		}
+
+		err = ext2fs_symlink(fs, parent, 0, node_name, src);
+	}
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out2;
+	}
+
+	/* Update parent dir's mtime */
+	ret = update_mtime(fs, parent, NULL);
+	if (ret)
+		goto out2;
+
+	/* Still have to update the uid/gid of the symlink */
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &child);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+	dbg_printf(ff, "%s: symlinking ino=%d/name=%s to dir=%d\n", __func__,
+		   child, node_name, parent);
+
+	err = fuse4fs_read_inode(fs, child, &inode);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	fuse4fs_set_extra_isize(ff, child, &inode);
+	fuse4fs_set_uid(&inode, ctxt->uid);
+	fuse4fs_set_gid(&inode, gid);
+	inode.i_generation = ff->next_generation++;
+	init_times(&inode);
+
+	err = fuse4fs_write_inode(fs, child, &inode);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
+out2:
+	fuse4fs_finish(ff, ret);
+out:
+	free(temp_path);
+	return ret;
+}
+
+struct update_dotdot {
+	ext2_ino_t new_dotdot;
+};
+
+static int update_dotdot_helper(ext2_ino_t dir EXT2FS_ATTR((unused)),
+				int entry EXT2FS_ATTR((unused)),
+				struct ext2_dir_entry *dirent,
+				int offset EXT2FS_ATTR((unused)),
+				int blocksize EXT2FS_ATTR((unused)),
+				char *buf EXT2FS_ATTR((unused)),
+				void *priv_data)
+{
+	struct update_dotdot *ud = priv_data;
+
+	if (ext2fs_dirent_name_len(dirent) == 2 &&
+	    dirent->name[0] == '.' && dirent->name[1] == '.') {
+		dirent->inode = ud->new_dotdot;
+		return DIRENT_CHANGED | DIRENT_ABORT;
+	}
+
+	return 0;
+}
+
+/*
+ * If we're moving a directory, make sure that the new parent of that directory
+ * can handle the nlink bump.
+ */
+static int fuse4fs_check_from_dir_nlink(struct fuse4fs *ff, ext2_ino_t from_ino,
+					ext2_ino_t to_ino,
+					ext2_ino_t from_dir_ino,
+					ext2_ino_t to_dir_ino)
+{
+	struct ext2_inode_large inode;
+	errcode_t err;
+
+	err = fuse4fs_read_inode(ff->fs, from_ino, &inode);
+	if (err)
+		return translate_error(ff->fs, from_ino, err);
+
+	if (!S_ISDIR(inode.i_mode))
+		return 0;
+
+	if (to_ino != 0)
+		return 0;
+
+	if (to_dir_ino == from_dir_ino)
+		return 0;
+
+	err = fuse4fs_read_inode(ff->fs, to_dir_ino, &inode);
+	if (err)
+		return translate_error(ff->fs, from_ino, err);
+
+	if (ext2fs_dir_link_max(ff->fs, &inode))
+		return -EMLINK;
+
+	return 0;
+}
+
+static int op_rename(const char *from, const char *to,
+		     unsigned int flags EXT2FS_ATTR((unused)))
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	errcode_t err;
+	ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
+	char *temp_to = NULL, *temp_from = NULL;
+	char *cp, a;
+	struct ext2_inode inode;
+	struct update_dotdot ud;
+	int flushed = 0;
+	int ret = 0;
+
+	/* renameat2 is not supported */
+	if (flags)
+		return -ENOSYS;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
+	fs = fuse4fs_start(ff);
+	if (!fs_can_allocate(ff, 5)) {
+		ret = -ENOSPC;
+		goto out;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, from, &from_ino);
+	if (err || from_ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, to, &to_ino);
+	if (err && err != EXT2_ET_FILE_NOT_FOUND) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	if (err == EXT2_ET_FILE_NOT_FOUND)
+		to_ino = 0;
+
+	/* Already the same file? */
+	if (to_ino != 0 && to_ino == from_ino) {
+		ret = 0;
+		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;
+		goto out;
+	}
+
+	temp_from = strdup(from);
+	if (!temp_from) {
+		ret = -ENOMEM;
+		goto out2;
+	}
+
+	/* Find parent dir of the source and check write access */
+	cp = strrchr(temp_from, '/');
+	if (!cp) {
+		ret = -EINVAL;
+		goto out2;
+	}
+
+	a = *(cp + 1);
+	*(cp + 1) = 0;
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_from,
+			   &from_dir_ino);
+	*(cp + 1) = a;
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+	if (from_dir_ino == 0) {
+		ret = -ENOENT;
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, from_dir_ino, W_OK);
+	if (ret)
+		goto out2;
+
+	/* Find parent dir of the destination and check write access */
+	cp = strrchr(temp_to, '/');
+	if (!cp) {
+		ret = -EINVAL;
+		goto out2;
+	}
+
+	a = *(cp + 1);
+	*(cp + 1) = 0;
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_to,
+			   &to_dir_ino);
+	*(cp + 1) = a;
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+	if (to_dir_ino == 0) {
+		ret = -ENOENT;
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, to_dir_ino, W_OK);
+	if (ret)
+		goto out2;
+
+	ret = fuse4fs_check_from_dir_nlink(ff, from_ino, to_ino, from_dir_ino,
+					   to_dir_ino);
+	if (ret)
+		goto out2;
+
+	/* If the target exists, unlink it first */
+	if (to_ino != 0) {
+		err = ext2fs_read_inode(fs, to_ino, &inode);
+		if (err) {
+			ret = translate_error(fs, to_ino, err);
+			goto out2;
+		}
+
+		dbg_printf(ff, "%s: unlinking %s ino=%d\n", __func__,
+			   LINUX_S_ISDIR(inode.i_mode) ? "dir" : "file",
+			   to_ino);
+		if (LINUX_S_ISDIR(inode.i_mode))
+			ret = __op_rmdir(ff, to);
+		else
+			ret = __op_unlink(ff, to);
+		if (ret)
+			goto out2;
+	}
+
+	/* Get ready to do the move */
+	err = ext2fs_read_inode(fs, from_ino, &inode);
+	if (err) {
+		ret = translate_error(fs, from_ino, err);
+		goto out2;
+	}
+
+	/* Link in the new file */
+	dbg_printf(ff, "%s: linking ino=%d/path=%s to dir=%d\n", __func__,
+		   from_ino, cp + 1, to_dir_ino);
+	err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+			  ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
+	if (err) {
+		ret = translate_error(fs, to_dir_ino, err);
+		goto out2;
+	}
+
+	/* Update '..' pointer if dir */
+	err = ext2fs_read_inode(fs, from_ino, &inode);
+	if (err) {
+		ret = translate_error(fs, from_ino, err);
+		goto out2;
+	}
+
+	if (LINUX_S_ISDIR(inode.i_mode)) {
+		ud.new_dotdot = to_dir_ino;
+		dbg_printf(ff, "%s: updating .. entry for dir=%d\n", __func__,
+			   to_dir_ino);
+		err = ext2fs_dir_iterate2(fs, from_ino, 0, NULL,
+					  update_dotdot_helper, &ud);
+		if (err) {
+			ret = translate_error(fs, from_ino, err);
+			goto out2;
+		}
+
+		/* Decrease from_dir_ino's links_count */
+		dbg_printf(ff, "%s: moving linkcount from dir=%d to dir=%d\n",
+			   __func__, from_dir_ino, to_dir_ino);
+		err = ext2fs_read_inode(fs, from_dir_ino, &inode);
+		if (err) {
+			ret = translate_error(fs, from_dir_ino, err);
+			goto out2;
+		}
+		ext2fs_dec_nlink(&inode);
+		err = ext2fs_write_inode(fs, from_dir_ino, &inode);
+		if (err) {
+			ret = translate_error(fs, from_dir_ino, err);
+			goto out2;
+		}
+
+		/* Increase to_dir_ino's links_count */
+		err = ext2fs_read_inode(fs, to_dir_ino, &inode);
+		if (err) {
+			ret = translate_error(fs, to_dir_ino, err);
+			goto out2;
+		}
+		ext2fs_inc_nlink(fs, &inode);
+		err = ext2fs_write_inode(fs, to_dir_ino, &inode);
+		if (err) {
+			ret = translate_error(fs, to_dir_ino, err);
+			goto out2;
+		}
+	}
+
+	/* Update timestamps */
+	ret = update_ctime(fs, from_ino, NULL);
+	if (ret)
+		goto out2;
+
+	ret = update_mtime(fs, to_dir_ino, NULL);
+	if (ret)
+		goto out2;
+
+	/* Remove the old file */
+	ret = fuse4fs_unlink(ff, from, NULL);
+	if (ret)
+		goto out2;
+
+	ret = fuse4fs_dirsync_flush(ff, from_dir_ino, &flushed);
+	if (ret)
+		goto out2;
+
+	if (from_dir_ino != to_dir_ino && !flushed) {
+		ret = fuse4fs_dirsync_flush(ff, to_dir_ino, NULL);
+		if (ret)
+			goto out2;
+	}
+
+out2:
+	free(temp_from);
+	free(temp_to);
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_link(const char *src, const char *dest)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	char *temp_path;
+	errcode_t err;
+	char *node_name, a;
+	ext2_ino_t parent, ino;
+	struct ext2_inode_large inode;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: src=%s dest=%s\n", __func__, src, dest);
+	temp_path = strdup(dest);
+	if (!temp_path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name = strrchr(temp_path, '/');
+	if (!node_name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name++;
+	a = *node_name;
+	*node_name = 0;
+
+	fs = fuse4fs_start(ff);
+	if (!fs_can_allocate(ff, 2)) {
+		ret = -ENOSPC;
+		goto out2;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &parent);
+	*node_name = a;
+	if (err) {
+		err = -ENOENT;
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	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);
+		goto out2;
+	}
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out2;
+	}
+
+	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	if (ret)
+		goto out2;
+
+	if (ext2fs_dir_link_max(ff->fs, &inode)) {
+		ret = -EMLINK;
+		goto out2;
+	}
+
+	ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
+	ret = update_ctime(fs, ino, &inode);
+	if (ret)
+		goto out2;
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out2;
+	}
+
+	dbg_printf(ff, "%s: linking ino=%d/name=%s to dir=%d\n", __func__, ino,
+		   node_name, parent);
+	err = ext2fs_link(fs, parent, node_name, ino,
+			  ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out2;
+	}
+
+	ret = update_mtime(fs, parent, NULL);
+	if (ret)
+		goto out2;
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
+out2:
+	fuse4fs_finish(ff, ret);
+out:
+	free(temp_path);
+	return ret;
+}
+
+/* Obtain group ids of the process that sent us a command(?) */
+static int get_req_groups(struct fuse4fs *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) {
+			/*
+			 * If there's an error, we failed to find the group
+			 * membership of the process that initiated the file
+			 * change, either because the process went away or
+			 * because there's no Linux procfs.  Regardless of the
+			 * cause, we return -ENOENT.
+			 */
+			ext2fs_free_mem(&array);
+			return -ENOENT;
+		}
+
+		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 fuse4fs *ff = fuse4fs_get();
+	gid_t *gids = NULL;
+	size_t i, nr_gids = 0;
+	gid_t gid = inode_gid(*inode);
+	int ret;
+
+	/* If the inode gid matches the process' primary group, we're done. */
+	if (ctxt->gid == gid)
+		return 1;
+
+	ret = get_req_groups(ff, &gids, &nr_gids);
+	if (ret == -ENOENT) {
+		/* magic return code for "could not get caller group info" */
+		return 0;
+	}
+	if (ret < 0)
+		return ret;
+
+	ret = 0;
+	for (i = 0; i < nr_gids; i++) {
+		if (gids[i] == gid) {
+			ret = 1;
+			break;
+		}
+	}
+
+	ext2fs_free_mem(&gids);
+	return ret;
+}
+
+static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	errcode_t err;
+	ext2_ino_t ino;
+	struct ext2_inode_large inode;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	ret = fuse4fs_file_ino(ff, path, fi, &ino);
+	if (ret)
+		goto out;
+	dbg_printf(ff, "%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		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;
+	}
+
+	/*
+	 * XXX: We should really check that the inode gid is not in /any/
+	 * of the user's groups, but FUSE only tells us about the primary
+	 * group.
+	 */
+	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;
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_chown(const char *path, uid_t owner, gid_t group,
+		    struct fuse_file_info *fi)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	errcode_t err;
+	ext2_ino_t ino;
+	struct ext2_inode_large inode;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	ret = fuse4fs_file_ino(ff, path, fi, &ino);
+	if (ret)
+		goto out;
+	dbg_printf(ff, "%s: path=%s owner=%d group=%d ino=%d\n", __func__,
+		   path, owner, group, ino);
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		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. */
+		if (want_check_owner(ff, ctxt) &&
+		    !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
+			ret = -EPERM;
+			goto out;
+		}
+		fuse4fs_set_uid(&inode, owner);
+	}
+
+	if (group != (gid_t) ~0) {
+		/* Only root or the owner get to change GID. */
+		if (want_check_owner(ff, ctxt) &&
+		    inode_uid(inode) != ctxt->uid) {
+			ret = -EPERM;
+			goto out;
+		}
+
+		/* XXX: We /should/ check group membership but FUSE */
+		fuse4fs_set_gid(&inode, group);
+	}
+
+	ret = update_ctime(fs, ino, &inode);
+	if (ret)
+		goto out;
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int fuse4fs_punch_posteof(struct fuse4fs *ff, ext2_ino_t ino,
+				 off_t new_size)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_inode_large inode;
+	blk64_t truncate_block = FUSE4FS_B_TO_FSB(ff, new_size);
+	errcode_t err;
+
+	err = fuse4fs_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);
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
+static int fuse4fs_truncate(struct fuse4fs *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;
+
+	err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file);
+	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);
+
+	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 fuse4fs_punch_posteof(ff, ino, new_size);
+
+	return 0;
+}
+
+static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_ino_t ino;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fuse4fs_start(ff);
+	ret = fuse4fs_file_ino(ff, path, fi, &ino);
+	if (ret)
+		goto out;
+	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
+
+	ret = check_inum_access(ff, ino, W_OK);
+	if (ret)
+		goto out;
+
+	ret = fuse4fs_truncate(ff, ino, len);
+	if (ret)
+		goto out;
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+#ifdef __linux__
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+				  int *e2fs_open_flags)
+{
+	/*
+	 * On Linux, execve will bleed __FMODE_EXEC into the file mode flags,
+	 * and FUSE is more than happy to let that slip through.
+	 */
+	if (kernel_flags & 0x20) {
+		*access_check = X_OK;
+		*e2fs_open_flags &= ~EXT2_FILE_WRITE;
+	}
+}
+#else
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+				  int *e2fs_open_flags)
+{
+	/* empty */
+}
+#endif /* __linux__ */
+
+static int __op_open(struct fuse4fs *ff, const char *path,
+		     struct fuse_file_info *fp)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct fuse4fs_file_handle *file;
+	int check = 0, ret = 0;
+
+	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);
+	file->magic = FUSE4FS_FILE_MAGIC;
+
+	file->open_flags = 0;
+	switch (fp->flags & O_ACCMODE) {
+	case O_RDONLY:
+		check = R_OK;
+		break;
+	case O_WRONLY:
+		check = W_OK;
+		file->open_flags |= EXT2_FILE_WRITE;
+		break;
+	case O_RDWR:
+		check = R_OK | W_OK;
+		file->open_flags |= EXT2_FILE_WRITE;
+		break;
+	}
+
+	/*
+	 * If the caller wants to truncate the file, we need to ask for full
+	 * write access even if the caller claims to be appending.
+	 */
+	if ((fp->flags & O_APPEND) && !(fp->flags & O_TRUNC))
+		check |= A_OK;
+
+	detect_linux_executable_open(fp->flags, &check, &file->open_flags);
+
+	if (fp->flags & O_CREAT)
+		file->open_flags |= EXT2_FILE_CREATE;
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &file->ino);
+	if (err || file->ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
+
+	ret = check_inum_access(ff, file->ino, check);
+	if (ret) {
+		/*
+		 * In a regular (Linux) fs driver, the kernel will open
+		 * binaries for reading if the user has --x privileges (i.e.
+		 * execute without read).  Since the kernel doesn't have any
+		 * way to tell us if it's opening a file via execve, we'll
+		 * just assume that allowing access is ok if asking for ro mode
+		 * fails but asking for x mode succeeds.  Of course we can
+		 * also employ undocumented hacks (see above).
+		 */
+		if (check == R_OK) {
+			ret = check_inum_access(ff, file->ino, X_OK);
+			if (ret)
+				goto out;
+			check = X_OK;
+		} else
+			goto out;
+	}
+
+	if (fp->flags & O_TRUNC) {
+		ret = fuse4fs_truncate(ff, file->ino, 0);
+		if (ret)
+			goto out;
+	}
+
+	file->check_flags = check;
+	fuse4fs_set_handle(fp, file);
+
+out:
+	if (ret)
+		ext2fs_free_mem(&file);
+	return ret;
+}
+
+static int op_open(const char *path, struct fuse_file_info *fp)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	int ret;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fuse4fs_start(ff);
+	ret = __op_open(ff, path, fp);
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
+		   size_t len, off_t offset,
+		   struct fuse_file_info *fp)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	ext2_filsys fs;
+	ext2_file_t efp;
+	errcode_t err;
+	unsigned int got = 0;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
+		   (unsigned long long)offset, len);
+	fs = fuse4fs_start(ff);
+	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out;
+	}
+
+	err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out2;
+	}
+
+	err = ext2fs_file_read(efp, buf, len, &got);
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out2;
+	}
+
+out2:
+	err = ext2fs_file_close(efp);
+	if (ret)
+		goto out;
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out;
+	}
+
+	if (fh->check_flags != X_OK && fuse4fs_is_writeable(ff)) {
+		ret = update_atime(fs, fh->ino);
+		if (ret)
+			goto out;
+	}
+out:
+	fuse4fs_finish(ff, ret);
+	return got ? (int) got : ret;
+}
+
+static int op_write(const char *path EXT2FS_ATTR((unused)),
+		    const char *buf, size_t len, off_t offset,
+		    struct fuse_file_info *fp)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	ext2_filsys fs;
+	ext2_file_t efp;
+	errcode_t err;
+	unsigned int got = 0;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
+		   (unsigned long long) offset, len);
+	fs = fuse4fs_start(ff);
+	if (!fuse4fs_is_writeable(ff)) {
+		ret = -EROFS;
+		goto out;
+	}
+
+	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
+		ret = -ENOSPC;
+		goto out;
+	}
+
+	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out;
+	}
+
+	err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out2;
+	}
+
+	err = ext2fs_file_write(efp, buf, len, &got);
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out2;
+	}
+
+	err = ext2fs_file_flush(efp);
+	if (err) {
+		got = 0;
+		ret = translate_error(fs, fh->ino, err);
+		goto out2;
+	}
+
+out2:
+	err = ext2fs_file_close(efp);
+	if (ret)
+		goto out;
+	if (err) {
+		ret = translate_error(fs, fh->ino, err);
+		goto out;
+	}
+
+	ret = update_mtime(fs, fh->ino, NULL);
+	if (ret)
+		goto out;
+
+out:
+	fuse4fs_finish(ff, ret);
+	return got ? (int) got : ret;
+}
+
+static int op_release(const char *path EXT2FS_ATTR((unused)),
+		      struct fuse_file_info *fp)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	ext2_filsys fs;
+	errcode_t err;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	fs = fuse4fs_start(ff);
+
+	if ((fp->flags & O_SYNC) &&
+	    fuse4fs_is_writeable(ff) &&
+	    (fh->open_flags & EXT2_FILE_WRITE)) {
+		err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
+		if (err)
+			ret = translate_error(fs, fh->ino, err);
+	}
+
+	fp->fh = 0;
+	fuse4fs_finish(ff, ret);
+
+	ext2fs_free_mem(&fh);
+
+	return ret;
+}
+
+static int op_fsync(const char *path EXT2FS_ATTR((unused)),
+		    int datasync EXT2FS_ATTR((unused)),
+		    struct fuse_file_info *fp)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	ext2_filsys fs;
+	errcode_t err;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	fs = fuse4fs_start(ff);
+	/* For now, flush everything, even if it's slow */
+	if (fuse4fs_is_writeable(ff) && fh->open_flags & EXT2_FILE_WRITE) {
+		err = ext2fs_flush2(fs, 0);
+		if (err)
+			ret = translate_error(fs, fh->ino, err);
+	}
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+
+static int op_statfs(const char *path EXT2FS_ATTR((unused)),
+		     struct statvfs *buf)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	uint64_t fsid, *f;
+	blk64_t overhead, reserved, free;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: path=%s\n", __func__, path);
+	fs = fuse4fs_start(ff);
+	buf->f_bsize = fs->blocksize;
+	buf->f_frsize = 0;
+
+	if (ff->minixdf)
+		overhead = 0;
+	else
+		overhead = fs->desc_blocks +
+			   (blk64_t)fs->group_desc_count *
+			   (fs->inode_blocks_per_group + 2);
+	reserved = ext2fs_r_blocks_count(fs->super);
+	if (!reserved)
+		reserved = ext2fs_blocks_count(fs->super) / 10;
+	free = ext2fs_free_blocks_count(fs->super);
+
+	buf->f_blocks = ext2fs_blocks_count(fs->super) - overhead;
+	buf->f_bfree = free;
+	if (free < reserved)
+		buf->f_bavail = 0;
+	else
+		buf->f_bavail = free - reserved;
+	buf->f_files = fs->super->s_inodes_count;
+	buf->f_ffree = fs->super->s_free_inodes_count;
+	buf->f_favail = fs->super->s_free_inodes_count;
+	f = (uint64_t *)fs->super->s_uuid;
+	fsid = *f;
+	f++;
+	fsid ^= *f;
+	buf->f_fsid = fsid;
+	buf->f_flag = 0;
+	if (ff->opstate != F4OP_WRITABLE)
+		buf->f_flag |= ST_RDONLY;
+	buf->f_namemax = EXT2_NAME_LEN;
+	fuse4fs_finish(ff, 0);
+
+	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)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	void *ptr;
+	size_t plen;
+	ext2_ino_t ino;
+	errcode_t err;
+	int ret = 0;
+
+	if (!validate_xattr_name(key))
+		return -ENODATA;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	if (!ext2fs_has_feature_xattr(fs->super)) {
+		ret = -ENOTSUP;
+		goto out;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err || ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+
+	ret = check_inum_access(ff, ino, R_OK);
+	if (ret)
+		goto out;
+
+	ret = __getxattr(ff, ino, key, &ptr, &plen);
+	if (ret)
+		goto out;
+
+	if (!len) {
+		ret = plen;
+	} else if (len < plen) {
+		ret = -ERANGE;
+	} else {
+		memcpy(value, ptr, plen);
+		ret = plen;
+	}
+
+	ext2fs_free_mem(&ptr);
+out:
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+
+static int count_buffer_space(char *name, char *value EXT2FS_ATTR((unused)),
+			      size_t value_len EXT2FS_ATTR((unused)),
+			      void *data)
+{
+	unsigned int *x = data;
+
+	*x = *x + strlen(name) + 1;
+	return 0;
+}
+
+static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
+		      size_t value_len EXT2FS_ATTR((unused)), void *data)
+{
+	char **b = data;
+	size_t name_len = strlen(name);
+
+	memcpy(*b, name, name_len + 1);
+	*b = *b + name_len + 1;
+
+	return 0;
+}
+
+static int op_listxattr(const char *path, char *names, size_t len)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	struct ext2_xattr_handle *h;
+	unsigned int bufsz;
+	ext2_ino_t ino;
+	errcode_t err;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	if (!ext2fs_has_feature_xattr(fs->super)) {
+		ret = -ENOTSUP;
+		goto out;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err || ino == 0) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
+
+	ret = check_inum_access(ff, ino, R_OK);
+	if (ret)
+		goto out;
+
+	err = ext2fs_xattrs_open(fs, ino, &h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+	err = ext2fs_xattrs_read(h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out2;
+	}
+
+	/* Count buffer space needed for names */
+	bufsz = 0;
+	err = ext2fs_xattrs_iterate(h, count_buffer_space, &bufsz);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out2;
+	}
+
+	if (len == 0) {
+		ret = bufsz;
+		goto out2;
+	} else if (len < bufsz) {
+		ret = -ERANGE;
+		goto out2;
+	}
+
+	/* Copy names out */
+	memset(names, 0, len);
+	err = ext2fs_xattrs_iterate(h, copy_names, &names);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out2;
+	}
+	ret = bufsz;
+out2:
+	err = ext2fs_xattrs_close(&h);
+	if (err && !ret)
+		ret = translate_error(fs, ino, err);
+out:
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+
+static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
+		       const char *key, const char *value,
+		       size_t len, int flags)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	struct ext2_xattr_handle *h;
+	ext2_ino_t ino;
+	errcode_t err;
+	int ret = 0;
+
+	if (flags & ~(XATTR_CREATE | XATTR_REPLACE))
+		return -EOPNOTSUPP;
+
+	if (!validate_xattr_name(key))
+		return -EINVAL;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	if (!ext2fs_has_feature_xattr(fs->super)) {
+		ret = -ENOTSUP;
+		goto out;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err || ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+
+	ret = check_inum_access(ff, ino, W_OK);
+	if (ret == -EACCES) {
+		ret = -EPERM;
+		goto out;
+	} else if (ret)
+		goto out;
+
+	err = ext2fs_xattrs_open(fs, ino, &h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+	err = ext2fs_xattrs_read(h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		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);
+		goto out2;
+	}
+
+	ret = update_ctime(fs, ino, NULL);
+out2:
+	err = ext2fs_xattrs_close(&h);
+	if (!ret && err)
+		ret = translate_error(fs, ino, err);
+out:
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+
+static int op_removexattr(const char *path, const char *key)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	struct ext2_xattr_handle *h;
+	void *buf;
+	size_t buflen;
+	ext2_ino_t ino;
+	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;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	if (!ext2fs_has_feature_xattr(fs->super)) {
+		ret = -ENOTSUP;
+		goto out;
+	}
+
+	if (!fs_can_allocate(ff, 1)) {
+		ret = -ENOSPC;
+		goto out;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err || ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+
+	ret = check_inum_access(ff, ino, W_OK);
+	if (ret)
+		goto out;
+
+	err = ext2fs_xattrs_open(fs, ino, &h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+	err = ext2fs_xattrs_read(h);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		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);
+		goto out2;
+	}
+
+	ret = update_ctime(fs, ino, NULL);
+out2:
+	err = ext2fs_xattrs_close(&h);
+	if (err && !ret)
+		ret = translate_error(fs, ino, err);
+out:
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+
+struct readdir_iter {
+	void *buf;
+	ext2_filsys fs;
+	fuse_fill_dir_t func;
+
+	struct fuse4fs *ff;
+	enum fuse_readdir_flags flags;
+	unsigned int nr;
+	off_t startpos;
+	off_t dirpos;
+};
+
+static inline mode_t dirent_fmode(ext2_filsys fs,
+				   const struct ext2_dir_entry *dirent)
+{
+	if (!ext2fs_has_feature_filetype(fs->super))
+		return 0;
+
+	switch (ext2fs_dirent_file_type(dirent)) {
+	case EXT2_FT_REG_FILE:
+		return S_IFREG;
+	case EXT2_FT_DIR:
+		return S_IFDIR;
+	case EXT2_FT_CHRDEV:
+		return S_IFCHR;
+	case EXT2_FT_BLKDEV:
+		return S_IFBLK;
+	case EXT2_FT_FIFO:
+		return S_IFIFO;
+	case EXT2_FT_SOCK:
+		return S_IFSOCK;
+	case EXT2_FT_SYMLINK:
+		return S_IFLNK;
+	}
+
+	return 0;
+}
+
+static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
+			   int entry EXT2FS_ATTR((unused)),
+			   struct ext2_dir_entry *dirent,
+			   int offset EXT2FS_ATTR((unused)),
+			   int blocksize EXT2FS_ATTR((unused)),
+			   char *buf EXT2FS_ATTR((unused)), void *data)
+{
+	struct readdir_iter *i = data;
+	char namebuf[EXT2_NAME_LEN + 1];
+	struct stat stat = {
+		.st_ino = dirent->inode,
+		.st_mode = dirent_fmode(i->fs, dirent),
+	};
+	int ret;
+
+	i->dirpos++;
+	if (i->startpos >= i->dirpos)
+		return 0;
+
+	dbg_printf(i->ff, "READDIR%s ino=%d %u offset=0x%llx\n",
+			i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
+			dir,
+			i->nr++,
+			(unsigned long long)i->dirpos);
+
+	if (i->flags == FUSE_READDIR_PLUS) {
+		ret = stat_inode(i->fs, dirent->inode, &stat);
+		if (ret)
+			return DIRENT_ABORT;
+	}
+
+	memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
+	namebuf[dirent->name_len & 0xFF] = 0;
+	ret = i->func(i->buf, namebuf, &stat, i->dirpos , 0);
+	if (ret)
+		return DIRENT_ABORT;
+
+	return 0;
+}
+
+static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
+		      fuse_fill_dir_t fill_func, off_t offset,
+		      struct fuse_file_info *fp, enum fuse_readdir_flags flags)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	errcode_t err;
+	struct readdir_iter i = {
+		.ff = ff,
+		.dirpos = 0,
+		.startpos = offset,
+		.flags = flags,
+	};
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	dbg_printf(ff, "%s: ino=%d offset=0x%llx\n", __func__, fh->ino,
+			(unsigned long long)offset);
+	i.fs = fuse4fs_start(ff);
+	i.buf = buf;
+	i.func = fill_func;
+	err = ext2fs_dir_iterate2(i.fs, fh->ino, 0, NULL, op_readdir_iter, &i);
+	if (err) {
+		ret = translate_error(i.fs, fh->ino, err);
+		goto out;
+	}
+
+	if (fuse4fs_is_writeable(ff)) {
+		ret = update_atime(i.fs, fh->ino);
+		if (ret)
+			goto out;
+	}
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_access(const char *path, int mask)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	errcode_t err;
+	ext2_ino_t ino;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: path=%s mask=0x%x\n", __func__, path, mask);
+	fs = fuse4fs_start(ff);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err || ino == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	ret = check_inum_access(ff, ino, mask);
+	if (ret)
+		goto out;
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	char *temp_path;
+	errcode_t err;
+	char *node_name, a;
+	int filetype;
+	struct ext2_inode_large inode;
+	gid_t gid;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
+	temp_path = strdup(path);
+	if (!temp_path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name = strrchr(temp_path, '/');
+	if (!node_name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	node_name++;
+	a = *node_name;
+	*node_name = 0;
+
+	fs = fuse4fs_start(ff);
+	if (!fs_can_allocate(ff, 1)) {
+		ret = -ENOSPC;
+		goto out2;
+	}
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+			   &parent);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out2;
+	}
+
+	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	if (ret)
+		goto out2;
+
+	err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+	if (err)
+		goto out2;
+
+	*node_name = a;
+
+	filetype = ext2_file_type(mode);
+
+	err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out2;
+	}
+
+	dbg_printf(ff, "%s: creating ino=%d/name=%s in dir=%d\n", __func__, child,
+		   node_name, parent);
+	err = ext2fs_link(fs, parent, node_name, child,
+			  filetype | EXT2FS_LINK_EXPAND);
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out2;
+	}
+
+	ret = update_mtime(fs, parent, NULL);
+	if (ret)
+		goto out2;
+
+	memset(&inode, 0, sizeof(inode));
+	inode.i_mode = mode;
+	inode.i_links_count = 1;
+	fuse4fs_set_extra_isize(ff, child, &inode);
+	fuse4fs_set_uid(&inode, ctxt->uid);
+	fuse4fs_set_gid(&inode, gid);
+	if (ext2fs_has_feature_extents(fs->super)) {
+		ext2_extent_handle_t handle;
+
+		inode.i_flags &= ~EXT4_EXTENTS_FL;
+		ret = ext2fs_extent_open2(fs, child,
+					  EXT2_INODE(&inode), &handle);
+		if (ret) {
+			ret = translate_error(fs, child, err);
+			goto out2;
+		}
+
+		ext2fs_extent_free(handle);
+	}
+
+	err = ext2fs_write_new_inode(fs, child, EXT2_INODE(&inode));
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	inode.i_generation = ff->next_generation++;
+	init_times(&inode);
+	err = fuse4fs_write_inode(fs, child, &inode);
+	if (err) {
+		ret = translate_error(fs, child, err);
+		goto out2;
+	}
+
+	ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+	if (ret)
+		goto out2;
+
+	fp->flags &= ~O_TRUNC;
+	ret = __op_open(ff, path, fp);
+	if (ret)
+		goto out2;
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out2;
+
+out2:
+	fuse4fs_finish(ff, ret);
+out:
+	free(temp_path);
+	return ret;
+}
+
+static int op_utimens(const char *path, const struct timespec ctv[2],
+		      struct fuse_file_info *fi)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct timespec tv[2];
+	ext2_filsys fs;
+	errcode_t err;
+	ext2_ino_t ino;
+	struct ext2_inode_large inode;
+	int access = W_OK;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	ret = fuse4fs_file_ino(ff, path, fi, &ino);
+	if (ret)
+		goto out;
+	dbg_printf(ff, "%s: ino=%d atime=%lld.%ld mtime=%lld.%ld\n", __func__,
+			ino,
+			(long long int)ctv[0].tv_sec, ctv[0].tv_nsec,
+			(long long int)ctv[1].tv_sec, ctv[1].tv_nsec);
+
+	/*
+	 * 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;
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+	tv[0] = ctv[0];
+	tv[1] = ctv[1];
+#ifdef UTIME_NOW
+	if (tv[0].tv_nsec == UTIME_NOW)
+		get_now(tv);
+	if (tv[1].tv_nsec == UTIME_NOW)
+		get_now(tv + 1);
+#endif /* UTIME_NOW */
+#ifdef UTIME_OMIT
+	if (tv[0].tv_nsec != UTIME_OMIT)
+		EXT4_INODE_SET_XTIME(i_atime, &tv[0], &inode);
+	if (tv[1].tv_nsec != UTIME_OMIT)
+		EXT4_INODE_SET_XTIME(i_mtime, &tv[1], &inode);
+#endif /* UTIME_OMIT */
+	ret = update_ctime(fs, ino, &inode);
+	if (ret)
+		goto out;
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+#define FUSE4FS_MODIFIABLE_IFLAGS \
+	(EXT2_FL_USER_MODIFIABLE & ~(EXT4_EXTENTS_FL | EXT4_CASEFOLD_FL | \
+				     EXT3_JOURNAL_DATA_FL))
+
+static inline int set_iflags(struct ext2_inode_large *inode, __u32 iflags)
+{
+	if ((inode->i_flags ^ iflags) & ~FUSE4FS_MODIFIABLE_IFLAGS)
+		return -EINVAL;
+
+	inode->i_flags = (inode->i_flags & ~FUSE4FS_MODIFIABLE_IFLAGS) |
+			 (iflags & FUSE4FS_MODIFIABLE_IFLAGS);
+	return 0;
+}
+
+#ifdef SUPPORT_I_FLAGS
+static int ioctl_getflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			  void *data)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	*(__u32 *)data = inode.i_flags & EXT2_FL_USER_VISIBLE;
+	return 0;
+}
+
+static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			  void *data)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+	int ret;
+	__u32 flags = *(__u32 *)data;
+	struct fuse_context *ctxt = fuse_get_context();
+
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+		return -EPERM;
+
+	ret = set_iflags(&inode, flags);
+	if (ret)
+		return ret;
+
+	ret = update_ctime(fs, fh->ino, &inode);
+	if (ret)
+		return ret;
+
+	err = fuse4fs_write_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	return 0;
+}
+
+static int ioctl_getversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			    void *data)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	*(__u32 *)data = inode.i_generation;
+	return 0;
+}
+
+static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			    void *data)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+	int ret;
+	__u32 generation = *(__u32 *)data;
+	struct fuse_context *ctxt = fuse_get_context();
+
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+		return -EPERM;
+
+	inode.i_generation = generation;
+
+	ret = update_ctime(fs, fh->ino, &inode);
+	if (ret)
+		return ret;
+
+	err = fuse4fs_write_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	return 0;
+}
+#endif /* SUPPORT_I_FLAGS */
+
+#ifdef FS_IOC_FSGETXATTR
+static __u32 iflags_to_fsxflags(__u32 iflags)
+{
+	__u32 xflags = 0;
+
+	if (iflags & FS_SYNC_FL)
+		xflags |= FS_XFLAG_SYNC;
+	if (iflags & FS_IMMUTABLE_FL)
+		xflags |= FS_XFLAG_IMMUTABLE;
+	if (iflags & FS_APPEND_FL)
+		xflags |= FS_XFLAG_APPEND;
+	if (iflags & FS_NODUMP_FL)
+		xflags |= FS_XFLAG_NODUMP;
+	if (iflags & FS_NOATIME_FL)
+		xflags |= FS_XFLAG_NOATIME;
+	if (iflags & FS_DAX_FL)
+		xflags |= FS_XFLAG_DAX;
+	if (iflags & FS_PROJINHERIT_FL)
+		xflags |= FS_XFLAG_PROJINHERIT;
+	return xflags;
+}
+
+static int ioctl_fsgetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			    void *data)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+	struct fsxattr *fsx = data;
+	unsigned int inode_size;
+
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	memset(fsx, 0, sizeof(*fsx));
+	inode_size = EXT2_GOOD_OLD_INODE_SIZE + inode.i_extra_isize;
+	if (ext2fs_inode_includes(inode_size, i_projid))
+		fsx->fsx_projid = inode_projid(inode);
+	fsx->fsx_xflags = iflags_to_fsxflags(inode.i_flags);
+	return 0;
+}
+
+static __u32 fsxflags_to_iflags(__u32 xflags)
+{
+	__u32 iflags = 0;
+
+	if (xflags & FS_XFLAG_IMMUTABLE)
+		iflags |= FS_IMMUTABLE_FL;
+	if (xflags & FS_XFLAG_APPEND)
+		iflags |= FS_APPEND_FL;
+	if (xflags & FS_XFLAG_SYNC)
+		iflags |= FS_SYNC_FL;
+	if (xflags & FS_XFLAG_NOATIME)
+		iflags |= FS_NOATIME_FL;
+	if (xflags & FS_XFLAG_NODUMP)
+		iflags |= FS_NODUMP_FL;
+	if (xflags & FS_XFLAG_DAX)
+		iflags |= FS_DAX_FL;
+	if (xflags & FS_XFLAG_PROJINHERIT)
+		iflags |= FS_PROJINHERIT_FL;
+	return iflags;
+}
+
+#define FUSE4FS_MODIFIABLE_XFLAGS (FS_XFLAG_IMMUTABLE | \
+				   FS_XFLAG_APPEND | \
+				   FS_XFLAG_SYNC | \
+				   FS_XFLAG_NOATIME | \
+				   FS_XFLAG_NODUMP | \
+				   FS_XFLAG_PROJINHERIT)
+
+#define FUSE4FS_MODIFIABLE_IXFLAGS (FS_IMMUTABLE_FL | \
+				    FS_APPEND_FL | \
+				    FS_SYNC_FL | \
+				    FS_NOATIME_FL | \
+				    FS_NODUMP_FL | \
+				    FS_PROJINHERIT_FL)
+
+static inline int set_xflags(struct ext2_inode_large *inode, __u32 xflags)
+{
+	__u32 iflags;
+
+	if (xflags & ~FUSE4FS_MODIFIABLE_XFLAGS)
+		return -EINVAL;
+
+	iflags = fsxflags_to_iflags(xflags);
+	inode->i_flags = (inode->i_flags & ~FUSE4FS_MODIFIABLE_IXFLAGS) |
+			 (iflags & FUSE4FS_MODIFIABLE_IXFLAGS);
+	return 0;
+}
+
+static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			    void *data)
+{
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	struct ext2_inode_large inode;
+	int ret;
+	struct fuse_context *ctxt = fuse_get_context();
+	struct fsxattr *fsx = data;
+	unsigned int inode_size;
+
+	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+		return -EPERM;
+
+	ret = set_xflags(&inode, fsx->fsx_xflags);
+	if (ret)
+		return ret;
+
+	inode_size = EXT2_GOOD_OLD_INODE_SIZE + inode.i_extra_isize;
+	if (ext2fs_inode_includes(inode_size, i_projid))
+		inode.i_projid = fsx->fsx_projid;
+
+	ret = update_ctime(fs, fh->ino, &inode);
+	if (ret)
+		return ret;
+
+	err = fuse4fs_write_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	return 0;
+}
+#endif /* FS_IOC_FSGETXATTR */
+
+#ifdef FITRIM
+static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			void *data)
+{
+	ext2_filsys fs = ff->fs;
+	struct fstrim_range *fr = data;
+	blk64_t start, end, max_blocks, b, cleared, minlen;
+	blk64_t max_blks = ext2fs_blocks_count(fs->super);
+	errcode_t err = 0;
+
+	if (!fuse4fs_is_writeable(ff))
+		return -EROFS;
+
+	start = FUSE4FS_B_TO_FSBT(ff, fr->start);
+	if (fr->len == -1ULL)
+		end = -1ULL;
+	else
+		end = FUSE4FS_B_TO_FSBT(ff, fr->start + fr->len - 1);
+	minlen = FUSE4FS_B_TO_FSBT(ff, fr->minlen);
+
+	if (EXT2FS_NUM_B2C(fs, minlen) > EXT2_CLUSTERS_PER_GROUP(fs->super) ||
+	    start >= max_blks ||
+	    fr->len < fs->blocksize)
+		return -EINVAL;
+
+	dbg_printf(ff, "%s: start=0x%llx end=0x%llx minlen=0x%llx\n", __func__,
+		   start, end, minlen);
+
+	if (start < fs->super->s_first_data_block)
+		start = fs->super->s_first_data_block;
+
+	if (end < fs->super->s_first_data_block)
+		end = fs->super->s_first_data_block;
+	if (end >= ext2fs_blocks_count(fs->super))
+		end = ext2fs_blocks_count(fs->super) - 1;
+
+	cleared = 0;
+	max_blocks = FUSE4FS_B_TO_FSBT(ff, 2048ULL * 1024 * 1024);
+
+	fr->len = 0;
+	while (start <= end) {
+		err = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+							   start, end, &start);
+		switch (err) {
+		case 0:
+			break;
+		case ENOENT:
+			/* no free blocks found, so we're done */
+			err = 0;
+			goto out;
+		default:
+			return translate_error(fs, fh->ino, err);
+		}
+
+		b = start + max_blocks < end ? start + max_blocks : end;
+		err =  ext2fs_find_first_set_block_bitmap2(fs->block_map,
+							   start, b, &b);
+		switch (err) {
+		case 0:
+			break;
+		case ENOENT:
+			/*
+			 * No free blocks found between start and b; discard
+			 * the entire range.
+			 */
+			err = 0;
+			break;
+		default:
+			return translate_error(fs, fh->ino, err);
+		}
+
+		if (b - start >= minlen) {
+			err = io_channel_discard(fs->io, start, b - start);
+			if (err == EBUSY) {
+				/*
+				 * Apparently dm-thinp can return EBUSY when
+				 * it's too busy deallocating thinp units to
+				 * deallocate more.  Swallow these errors.
+				 */
+				err = 0;
+			}
+			if (err)
+				return translate_error(fs, fh->ino, err);
+			cleared += b - start;
+			fr->len = FUSE4FS_FSB_TO_B(ff, cleared);
+		}
+		start = b + 1;
+	}
+
+out:
+	fr->len = FUSE4FS_FSB_TO_B(ff, cleared);
+	dbg_printf(ff, "%s: len=%llu err=%ld\n", __func__, fr->len, err);
+	return err;
+}
+#endif /* FITRIM */
+
+#ifndef EXT4_IOC_SHUTDOWN
+# define EXT4_IOC_SHUTDOWN	_IOR('X', 125, __u32)
+#endif
+
+static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
+			  void *data)
+{
+	struct fuse_context *ctxt = fuse_get_context();
+	ext2_filsys fs = ff->fs;
+
+	if (!is_superuser(ff, ctxt))
+		return -EPERM;
+
+	err_printf(ff, "%s.\n", _("shut down requested"));
+
+	fuse4fs_mmp_cancel(ff);
+
+	/*
+	 * EXT4_IOC_SHUTDOWN inherited the inverted polarity on the ioctl
+	 * direction from XFS.  Unfortunately, that means we can't implement
+	 * any of the flags.  Flush whatever is dirty and shut down.
+	 */
+	if (ff->opstate == F4OP_WRITABLE)
+		ext2fs_flush2(fs, 0);
+	ff->opstate = F4OP_SHUTDOWN;
+	fs->flags &= ~EXT2_FLAG_RW;
+
+	return 0;
+}
+
+static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
+		    unsigned int cmd,
+		    void *arg EXT2FS_ATTR((unused)),
+		    struct fuse_file_info *fp,
+		    unsigned int flags EXT2FS_ATTR((unused)), void *data)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	fuse4fs_start(ff);
+	switch ((unsigned long) cmd) {
+#ifdef SUPPORT_I_FLAGS
+	case EXT2_IOC_GETFLAGS:
+		ret = ioctl_getflags(ff, fh, data);
+		break;
+	case EXT2_IOC_SETFLAGS:
+		ret = ioctl_setflags(ff, fh, data);
+		break;
+	case EXT2_IOC_GETVERSION:
+		ret = ioctl_getversion(ff, fh, data);
+		break;
+	case EXT2_IOC_SETVERSION:
+		ret = ioctl_setversion(ff, fh, data);
+		break;
+#endif
+#ifdef FS_IOC_FSGETXATTR
+	case FS_IOC_FSGETXATTR:
+		ret = ioctl_fsgetxattr(ff, fh, data);
+		break;
+	case FS_IOC_FSSETXATTR:
+		ret = ioctl_fssetxattr(ff, fh, data);
+		break;
+#endif
+#ifdef FITRIM
+	case FITRIM:
+		ret = ioctl_fitrim(ff, fh, data);
+		break;
+#endif
+	case EXT4_IOC_SHUTDOWN:
+		ret = ioctl_shutdown(ff, fh, data);
+		break;
+	default:
+		dbg_printf(ff, "%s: Unknown ioctl %d\n", __func__, cmd);
+		ret = -ENOTTY;
+	}
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+
+static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
+		   uint64_t *idx)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	ext2_filsys fs;
+	ext2_ino_t ino;
+	errcode_t err;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	fs = fuse4fs_start(ff);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	dbg_printf(ff, "%s: ino=%d blk=%"PRIu64"\n", __func__, ino, *idx);
+
+	err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, *idx, 0, (blk64_t *)idx);
+	if (err) {
+		ret = translate_error(fs, ino, err);
+		goto out;
+	}
+
+out:
+	fuse4fs_finish(ff, ret);
+	return ret;
+}
+
+#ifdef SUPPORT_FALLOCATE
+static int fuse4fs_allocate_range(struct fuse4fs *ff,
+				  struct fuse4fs_file_handle *fh, int mode,
+				  off_t offset, off_t len)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_inode_large inode;
+	blk64_t start, end;
+	__u64 fsize;
+	errcode_t err;
+	int flags;
+
+	start = FUSE4FS_B_TO_FSBT(ff, offset);
+	end = FUSE4FS_B_TO_FSBT(ff, offset + len - 1);
+	dbg_printf(ff, "%s: ino=%d mode=0x%x offset=0x%llx len=0x%llx start=0x%llx end=0x%llx\n",
+		   __func__, fh->ino, mode,
+		   (unsigned long long)offset,
+		   (unsigned long long)len,
+		   (unsigned long long)start,
+		   (unsigned long long)end);
+	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
+		return -ENOSPC;
+
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return err;
+	fsize = EXT2_I_SIZE(&inode);
+
+	/* Indirect files do not support unwritten extents */
+	if (!(inode.i_flags & EXT4_EXTENTS_FL))
+		return -EOPNOTSUPP;
+
+	/* Allocate a bunch of blocks */
+	flags = (mode & FL_KEEP_SIZE_FLAG ? 0 :
+			EXT2_FALLOCATE_INIT_BEYOND_EOF);
+	err = ext2fs_fallocate(fs, flags, fh->ino,
+			       EXT2_INODE(&inode),
+			       ~0ULL, start, end - start + 1);
+	if (err && err != EXT2_ET_BLOCK_ALLOC_FAIL)
+		return translate_error(fs, fh->ino, err);
+
+	/* Update i_size */
+	if (!(mode & FL_KEEP_SIZE_FLAG)) {
+		if ((__u64) offset + len > fsize) {
+			err = ext2fs_inode_size_set(fs,
+						EXT2_INODE(&inode),
+						offset + len);
+			if (err)
+				return translate_error(fs, fh->ino, err);
+		}
+	}
+
+	err = update_mtime(fs, fh->ino, &inode);
+	if (err)
+		return err;
+
+	err = fuse4fs_write_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	return err;
+}
+
+static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
+				    struct ext2_inode_large *inode,
+				    off_t offset, off_t len, char **buf)
+{
+	ext2_filsys fs = ff->fs;
+	blk64_t blk;
+	off_t residue = FUSE4FS_OFF_IN_FSB(ff, offset);
+	int retflags;
+	errcode_t err;
+
+	if (!*buf) {
+		err = ext2fs_get_mem(fs->blocksize, buf);
+		if (err)
+			return err;
+	}
+
+	err = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), *buf, 0,
+			   FUSE4FS_B_TO_FSBT(ff, offset), &retflags, &blk);
+	if (err)
+		return err;
+	if (!blk || (retflags & BMAP_RET_UNINIT))
+		return 0;
+
+	err = io_channel_read_blk64(fs->io, blk, 1, *buf);
+	if (err)
+		return err;
+
+	dbg_printf(ff, "%s: ino=%d offset=0x%llx len=0x%llx\n",
+		   __func__, ino,
+		   (unsigned long long)offset + residue,
+		   (unsigned long long)len);
+	memset(*buf + residue, 0, len);
+
+	return io_channel_write_blk64(fs->io, blk, 1, *buf);
+}
+
+static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
+				  struct ext2_inode_large *inode, off_t offset,
+				  int clean_before, char **buf)
+{
+	ext2_filsys fs = ff->fs;
+	blk64_t blk;
+	int retflags;
+	off_t residue;
+	errcode_t err;
+
+	residue = FUSE4FS_OFF_IN_FSB(ff, offset);
+	if (residue == 0)
+		return 0;
+
+	if (!*buf) {
+		err = ext2fs_get_mem(fs->blocksize, buf);
+		if (err)
+			return err;
+	}
+
+	err = ext2fs_bmap2(fs, ino, EXT2_INODE(inode), *buf, 0,
+			   FUSE4FS_B_TO_FSBT(ff, offset), &retflags, &blk);
+	if (err)
+		return err;
+
+	err = io_channel_read_blk64(fs->io, blk, 1, *buf);
+	if (err)
+		return err;
+	if (!blk || (retflags & BMAP_RET_UNINIT))
+		return 0;
+
+	if (clean_before) {
+		dbg_printf(ff, "%s: ino=%d before offset=0x%llx len=0x%llx\n",
+			   __func__, ino,
+			   (unsigned long long)offset,
+			   (unsigned long long)residue);
+		memset(*buf, 0, residue);
+	} else {
+		dbg_printf(ff, "%s: ino=%d after offset=0x%llx len=0x%llx\n",
+			   __func__, ino,
+			   (unsigned long long)offset,
+			   (unsigned long long)fs->blocksize - residue);
+		memset(*buf + residue, 0, fs->blocksize - residue);
+	}
+
+	return io_channel_write_blk64(fs->io, blk, 1, *buf);
+}
+
+static int fuse4fs_punch_range(struct fuse4fs *ff,
+			       struct fuse4fs_file_handle *fh, int mode,
+			       off_t offset, off_t len)
+{
+	ext2_filsys fs = ff->fs;
+	struct ext2_inode_large inode;
+	blk64_t start, end;
+	errcode_t err;
+	char *buf = NULL;
+
+	/* kernel ext4 punch requires this flag to be set */
+	if (!(mode & FL_KEEP_SIZE_FLAG))
+		return -EINVAL;
+
+	/*
+	 * Unmap out all full blocks in the middle of the range being punched.
+	 * The start of the unmap range should be the first byte of the first
+	 * fsblock that starts within the range.  The end of the range should
+	 * be the next byte after the last fsblock to end in the range.
+	 */
+	start = FUSE4FS_B_TO_FSBT(ff, round_up(offset, fs->blocksize));
+	end = FUSE4FS_B_TO_FSBT(ff, round_down(offset + len, fs->blocksize));
+
+	dbg_printf(ff,
+ "%s: ino=%d mode=0x%x offset=0x%llx len=0x%llx start=0x%llx end=0x%llx\n",
+		   __func__, fh->ino, mode,
+		   (unsigned long long)offset,
+		   (unsigned long long)len,
+		   (unsigned long long)start,
+		   (unsigned long long)end);
+
+	err = fuse4fs_read_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	/*
+	 * Indirect files do not support unwritten extents, which means we
+	 * can't support zero range.  Punch goes first in zero-range, which
+	 * is why the check is here.
+	 */
+	if ((mode & FL_ZERO_RANGE_FLAG) && !(inode.i_flags & EXT4_EXTENTS_FL))
+		return -EOPNOTSUPP;
+
+	/* Zero everything before the first block and after the last block */
+	if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
+		err = clean_block_middle(ff, fh->ino, &inode, offset,
+					 len, &buf);
+	else {
+		err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
+		if (!err)
+			err = clean_block_edge(ff, fh->ino, &inode,
+					       offset + len, 1, &buf);
+	}
+	if (buf)
+		ext2fs_free_mem(&buf);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	/*
+	 * Unmap full blocks in the middle, which is to say that start - end
+	 * must be at least one fsblock.  ext2fs_punch takes a closed interval
+	 * as its argument, so we pass [start, end - 1].
+	 */
+	if (start < end) {
+		err = ext2fs_punch(fs, fh->ino, EXT2_INODE(&inode),
+				   NULL, start, end - 1);
+		if (err)
+			return translate_error(fs, fh->ino, err);
+	}
+
+	err = update_mtime(fs, fh->ino, &inode);
+	if (err)
+		return err;
+
+	err = fuse4fs_write_inode(fs, fh->ino, &inode);
+	if (err)
+		return translate_error(fs, fh->ino, err);
+
+	return 0;
+}
+
+static int fuse4fs_zero_range(struct fuse4fs *ff,
+			      struct fuse4fs_file_handle *fh, int mode,
+			      off_t offset, off_t len)
+{
+	int ret = fuse4fs_punch_range(ff, fh, mode | FL_KEEP_SIZE_FLAG, offset,
+				      len);
+
+	if (!ret)
+		ret = fuse4fs_allocate_range(ff, fh, mode, offset, len);
+	return ret;
+}
+
+static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
+			off_t offset, off_t len,
+			struct fuse_file_info *fp)
+{
+	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	int ret;
+
+	/* Catch unknown flags */
+	if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
+		return -EOPNOTSUPP;
+
+	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_HANDLE(ff, fh);
+	fuse4fs_start(ff);
+	if (!fuse4fs_is_writeable(ff)) {
+		ret = -EROFS;
+		goto out;
+	}
+
+	dbg_printf(ff, "%s: ino=%d mode=0x%x start=0x%llx end=0x%llx\n", __func__,
+		   fh->ino, mode,
+		   (unsigned long long)offset,
+		   (unsigned long long)offset + len);
+
+	if (mode & FL_ZERO_RANGE_FLAG)
+		ret = fuse4fs_zero_range(ff, fh, mode, offset, len);
+	else if (mode & FL_PUNCH_HOLE_FLAG)
+		ret = fuse4fs_punch_range(ff, fh, mode, offset, len);
+	else
+		ret = fuse4fs_allocate_range(ff, fh, mode, offset, len);
+out:
+	fuse4fs_finish(ff, ret);
+
+	return ret;
+}
+#endif /* SUPPORT_FALLOCATE */
+
+static struct fuse_operations fs_ops = {
+	.init = op_init,
+	.destroy = op_destroy,
+	.getattr = op_getattr,
+	.readlink = op_readlink,
+	.mknod = op_mknod,
+	.mkdir = op_mkdir,
+	.unlink = op_unlink,
+	.rmdir = op_rmdir,
+	.symlink = op_symlink,
+	.rename = op_rename,
+	.link = op_link,
+	.chmod = op_chmod,
+	.chown = op_chown,
+	.truncate = op_truncate,
+	.open = op_open,
+	.read = op_read,
+	.write = op_write,
+	.statfs = op_statfs,
+	.release = op_release,
+	.fsync = op_fsync,
+	.setxattr = op_setxattr,
+	.getxattr = op_getxattr,
+	.listxattr = op_listxattr,
+	.removexattr = op_removexattr,
+	.opendir = op_open,
+	.readdir = op_readdir,
+	.releasedir = op_release,
+	.fsyncdir = op_fsync,
+	.access = op_access,
+	.create = op_create,
+	.utimens = op_utimens,
+	.bmap = op_bmap,
+#ifdef SUPERFLUOUS
+	.lock = op_lock,
+	.poll = op_poll,
+#endif
+	.ioctl = op_ioctl,
+#ifdef SUPPORT_FALLOCATE
+	.fallocate = op_fallocate,
+#endif
+};
+
+static int get_random_bytes(void *p, size_t sz)
+{
+	int fd;
+	ssize_t r;
+
+	fd = open("/dev/urandom", O_RDONLY);
+	if (fd < 0) {
+		perror("/dev/urandom");
+		return 0;
+	}
+
+	r = read(fd, p, sz);
+
+	close(fd);
+	return (size_t) r == sz;
+}
+
+enum {
+	FUSE4FS_IGNORED,
+	FUSE4FS_VERSION,
+	FUSE4FS_HELP,
+	FUSE4FS_HELPFULL,
+	FUSE4FS_CACHE_SIZE,
+	FUSE4FS_DIRSYNC,
+	FUSE4FS_ERRORS_BEHAVIOR,
+};
+
+#define FUSE4FS_OPT(t, p, v) { t, offsetof(struct fuse4fs, p), v }
+
+static struct fuse_opt fuse4fs_opts[] = {
+	FUSE4FS_OPT("ro",		ro,			1),
+	FUSE4FS_OPT("rw",		ro,			0),
+	FUSE4FS_OPT("minixdf",		minixdf,		1),
+	FUSE4FS_OPT("bsddf",		minixdf,		0),
+	FUSE4FS_OPT("fakeroot",		fakeroot,		1),
+	FUSE4FS_OPT("fuse4fs_debug",	debug,			1),
+	FUSE4FS_OPT("no_default_opts",	no_default_opts,	1),
+	FUSE4FS_OPT("norecovery",	norecovery,		1),
+	FUSE4FS_OPT("noload",		norecovery,		1),
+	FUSE4FS_OPT("offset=%lu",	offset,			0),
+	FUSE4FS_OPT("oom_score_adj=%d",	oom_score_adj,		-500),
+	FUSE4FS_OPT("kernel",		kernel,			1),
+	FUSE4FS_OPT("directio",		directio,		1),
+	FUSE4FS_OPT("acl",		acl,			1),
+	FUSE4FS_OPT("noacl",		acl,			0),
+	FUSE4FS_OPT("lockfile=%s",	lockfile,		0),
+#ifdef HAVE_CLOCK_MONOTONIC
+	FUSE4FS_OPT("timing",		timing,			1),
+#endif
+
+	FUSE_OPT_KEY("user_xattr",	FUSE4FS_IGNORED),
+	FUSE_OPT_KEY("noblock_validity", FUSE4FS_IGNORED),
+	FUSE_OPT_KEY("nodelalloc",	FUSE4FS_IGNORED),
+	FUSE_OPT_KEY("cache_size=%s",	FUSE4FS_CACHE_SIZE),
+	FUSE_OPT_KEY("dirsync",		FUSE4FS_DIRSYNC),
+	FUSE_OPT_KEY("errors=%s",	FUSE4FS_ERRORS_BEHAVIOR),
+
+	FUSE_OPT_KEY("-V",             FUSE4FS_VERSION),
+	FUSE_OPT_KEY("--version",      FUSE4FS_VERSION),
+	FUSE_OPT_KEY("-h",             FUSE4FS_HELP),
+	FUSE_OPT_KEY("--help",         FUSE4FS_HELP),
+	FUSE_OPT_KEY("--helpfull",     FUSE4FS_HELPFULL),
+	FUSE_OPT_END
+};
+
+
+static int fuse4fs_opt_proc(void *data, const char *arg,
+			    int key, struct fuse_args *outargs)
+{
+	struct fuse4fs *ff = data;
+
+	switch (key) {
+	case FUSE4FS_DIRSYNC:
+		ff->dirsync = 1;
+		/* pass through to libfuse */
+		return 1;
+	case FUSE_OPT_KEY_NONOPT:
+		if (!ff->device) {
+			ff->device = strdup(arg);
+			return 0;
+		}
+		return 1;
+	case FUSE4FS_CACHE_SIZE:
+		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."));
+			return -1;
+		}
+
+		/* do not pass through to libfuse */
+		return 0;
+	case FUSE4FS_ERRORS_BEHAVIOR:
+		if (strcmp(arg + 7, "continue") == 0)
+			ff->errors_behavior = EXT2_ERRORS_CONTINUE;
+		else if (strcmp(arg + 7, "remount-ro") == 0)
+			ff->errors_behavior = EXT2_ERRORS_RO;
+		else if (strcmp(arg + 7, "panic") == 0)
+			ff->errors_behavior = EXT2_ERRORS_PANIC;
+		else {
+			fprintf(stderr, "%s: %s\n", arg,
+ _("unknown errors behavior."));
+			return -1;
+		}
+
+		/* do not pass through to libfuse */
+		return 0;
+	case FUSE4FS_IGNORED:
+		return 0;
+	case FUSE4FS_HELP:
+	case FUSE4FS_HELPFULL:
+		fprintf(stderr,
+	"usage: %s device/image mountpoint [options]\n"
+	"\n"
+	"general options:\n"
+	"    -o opt,[opt...]  mount options\n"
+	"    -h   --help      print help\n"
+	"    -V   --version   print version\n"
+	"\n"
+	"fuse4fs options:\n"
+	"    -o errors=panic        dump core on error\n"
+	"    -o minixdf             minix-style df\n"
+	"    -o fakeroot            pretend to be root for permission checks\n"
+	"    -o no_default_opts     do not include default fuse options\n"
+	"    -o offset=<bytes>      similar to mount -o offset=<bytes>, mount the partition starting at <bytes>\n"
+	"    -o norecovery          don't replay the journal\n"
+	"    -o fuse4fs_debug       enable fuse4fs debugging\n"
+	"    -o lockfile=<file>     file to show that fuse is still using the file system image\n"
+	"    -o kernel              run this as if it were the kernel, which sets:\n"
+	"                           allow_others,default_permissions,suid,dev\n"
+	"    -o directio            use O_DIRECT to read and write the disk\n"
+	"    -o cache_size=N[KMG]   use a disk cache of this size\n"
+	"    -o errors=             behavior when an error is encountered:\n"
+	"                           continue|remount-ro|panic\n"
+	"\n",
+			outargs->argv[0]);
+		if (key == FUSE4FS_HELPFULL) {
+			fuse_opt_add_arg(outargs, "-h");
+			fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+		} else {
+			fprintf(stderr, "Try --helpfull to get a list of "
+				"all flags, including the FUSE options.\n");
+		}
+		exit(1);
+
+	case FUSE4FS_VERSION:
+		fprintf(stderr, "fuse4fs %s (%s)\n", E2FSPROGS_VERSION,
+			E2FSPROGS_DATE);
+		fuse_opt_add_arg(outargs, "--version");
+		fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+		exit(0);
+	}
+	return 1;
+}
+
+static const char *get_subtype(const char *argv0)
+{
+	size_t argvlen = strlen(argv0);
+
+	if (argvlen < 4)
+		goto out_default;
+
+	if (argv0[argvlen - 4] == 'e' &&
+	    argv0[argvlen - 3] == 'x' &&
+	    argv0[argvlen - 2] == 't' &&
+	    isdigit(argv0[argvlen - 1]))
+		return &argv0[argvlen - 4];
+
+out_default:
+	return "ext4";
+}
+
+static void fuse4fs_compute_libfuse_args(struct fuse4fs *ff,
+					 struct fuse_args *args,
+					 const char *argv0)
+{
+	char extra_args[BUFSIZ];
+
+	/* Set up default fuse parameters */
+	snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
+		 "fsname=%s,attr_timeout=0",
+		 get_subtype(argv0),
+		 ff->device);
+	if (ff->no_default_opts == 0)
+		fuse_opt_add_arg(args, extra_args);
+
+	if (ff->ro)
+		fuse_opt_add_arg(args, "-oro");
+
+	if (ff->fakeroot) {
+#ifdef HAVE_MOUNT_NODEV
+		fuse_opt_add_arg(args,"-onodev");
+#endif
+#ifdef HAVE_MOUNT_NOSUID
+		fuse_opt_add_arg(args,"-onosuid");
+#endif
+	}
+
+	if (ff->kernel) {
+		/*
+		 * ACLs are always enforced when kernel mode is enabled, to
+		 * match the kernel ext4 driver which always enables ACLs.
+		 */
+		ff->acl = 1;
+		fuse_opt_insert_arg(args, 1,
+ "-oallow_other,default_permissions,suid,dev");
+	}
+
+	/*
+	 * Since there's a Big Kernel Lock around all the libext2fs code, we
+	 * only need to start four threads -- one to decode a request, another
+	 * to do the filesystem work, a third to transmit the reply, and a
+	 * fourth to handle fuse notifications.
+	 */
+	fuse_opt_insert_arg(args, 1, "-omax_threads=4");
+
+	if (ff->debug) {
+		int	i;
+
+		printf("FUSE4FS (%s): fuse arguments:", ff->shortdev);
+		for (i = 0; i < args->argc; i++)
+			printf(" '%s'", args->argv[i]);
+		printf("\n");
+		fflush(stdout);
+	}
+}
+
+/*
+ * Try to register as a filesystem I/O server process so that our memory
+ * allocations don't cause fs reclaim.
+ */
+static void try_set_io_flusher(struct fuse4fs *ff)
+{
+#ifdef HAVE_PR_SET_IO_FLUSHER
+	int ret = prctl(PR_GET_IO_FLUSHER, 0, 0, 0, 0);
+
+	/*
+	 * positive ret means it's already set, negative means we can't even
+	 * look at the value so don't bother setting it
+	 */
+	if (ret)
+		return;
+
+	ret = prctl(PR_SET_IO_FLUSHER, 1, 0, 0, 0);
+	if (ret < 0)
+		err_printf(ff, "%s: %s.\n",
+ _("Could not register as IO flusher thread"),
+			   strerror(errno));
+#endif
+}
+
+/* Try to adjust the OOM score so that we don't get killed */
+static void try_adjust_oom_score(struct fuse4fs *ff)
+{
+	FILE *fp = fopen("/proc/self/oom_score_adj", "w+");
+
+	if (!fp)
+		return;
+
+	fprintf(fp, "%d\n", ff->oom_score_adj);
+	fclose(fp);
+}
+
+static void fuse4fs_com_err_proc(const char *whoami, errcode_t code,
+				 const char *fmt, va_list args)
+{
+	fprintf(stderr, "FUSE4FS (%s): ", err_shortdev ? err_shortdev : "?");
+	if (whoami)
+		fprintf(stderr, "%s: ", whoami);
+	fprintf(stderr, "%s ", error_message(code));
+        vfprintf(stderr, fmt, args);
+	fprintf(stderr, "\n");
+	fflush(stderr);
+}
+
+int main(int argc, char *argv[])
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct fuse4fs fctx = {
+		.magic = FUSE4FS_MAGIC,
+		.logfd = -1,
+		.bfl = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER,
+		.oom_score_adj = -500,
+		.opstate = F4OP_WRITABLE,
+	};
+	errcode_t err;
+	FILE *orig_stderr = stderr;
+	int ret;
+
+	ret = fuse_opt_parse(&args, &fctx, fuse4fs_opts, fuse4fs_opt_proc);
+	if (ret)
+		exit(1);
+	if (fctx.device == NULL) {
+		fprintf(stderr, "Missing ext4 device/image\n");
+		fprintf(stderr, "See '%s -h' for usage\n", argv[0]);
+		exit(1);
+	}
+
+	/* /dev/sda -> sda for reporting */
+	fctx.shortdev = strrchr(fctx.device, '/');
+	if (fctx.shortdev)
+		fctx.shortdev++;
+	else
+		fctx.shortdev = fctx.device;
+
+	/* capture library error messages */
+	err_shortdev = fctx.shortdev;
+	set_com_err_hook(fuse4fs_com_err_proc);
+
+#ifdef ENABLE_NLS
+	setlocale(LC_MESSAGES, "");
+	setlocale(LC_CTYPE, "");
+	bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+	textdomain(NLS_CAT_NAME);
+	set_com_err_gettext(gettext);
+#endif
+	add_error_table(&et_ext2_error_table);
+
+	ret = fuse4fs_setup_logging(&fctx);
+	if (ret) {
+		/* operational error */
+		ret = 2;
+		goto out;
+	}
+
+	try_set_io_flusher(&fctx);
+	try_adjust_oom_score(&fctx);
+
+	/* Will we allow users to allocate every last block? */
+	if (getenv("FUSE4FS_ALLOC_ALL_BLOCKS")) {
+		log_printf(&fctx, "%s\n",
+ _("Allowing users to allocate all blocks. This is dangerous!"));
+		fctx.alloc_all_blocks = 1;
+	}
+
+	err = fuse4fs_open(&fctx);
+	if (err) {
+		ret = 32;
+		goto out;
+	}
+
+	err = fuse4fs_config_cache(&fctx);
+	if (err) {
+		ret = 32;
+		goto out;
+	}
+
+	err = fuse4fs_check_support(&fctx);
+	if (err) {
+		ret = 32;
+		goto out;
+	}
+
+	/*
+	 * ext4 can't do COW of shared blocks, so if the feature is enabled,
+	 * we must force ro mode.
+	 */
+	if (ext2fs_has_feature_shared_blocks(fctx.fs->super))
+		fctx.ro = 1;
+
+	err = fuse4fs_mount(&fctx);
+	if (err) {
+		ret = 32;
+		goto out;
+	}
+
+	/* Initialize generation counter */
+	get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
+
+	fuse4fs_compute_libfuse_args(&fctx, &args, argv[0]);
+
+	ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+	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);
+	}
+	fuse4fs_mmp_destroy(&fctx);
+	fuse4fs_unmount(&fctx);
+	reset_com_err_hook();
+	err_shortdev = NULL;
+	if (fctx.device)
+		free(fctx.device);
+	pthread_mutex_destroy(&fctx.bfl);
+	fuse_opt_free_args(&args);
+	return ret;
+}
+
+static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
+			     const char *func, int line)
+{
+	struct timespec now;
+	int ret = err;
+	struct fuse4fs *ff = fs->priv_data;
+	int is_err = 0;
+
+	/* Translate ext2 error to unix error code */
+	switch (err) {
+	case 0:
+		break;
+	case EXT2_ET_NO_MEMORY:
+	case EXT2_ET_TDB_ERR_OOM:
+		ret = -ENOMEM;
+		break;
+	case EXT2_ET_INVALID_ARGUMENT:
+	case EXT2_ET_LLSEEK_FAILED:
+		ret = -EINVAL;
+		break;
+	case EXT2_ET_NO_DIRECTORY:
+		ret = -ENOTDIR;
+		break;
+	case EXT2_ET_FILE_NOT_FOUND:
+		ret = -ENOENT;
+		break;
+	case EXT2_ET_DIR_NO_SPACE:
+		is_err = 1;
+		/* fallthrough */
+	case EXT2_ET_TOOSMALL:
+	case EXT2_ET_BLOCK_ALLOC_FAIL:
+	case EXT2_ET_INODE_ALLOC_FAIL:
+	case EXT2_ET_EA_NO_SPACE:
+		ret = -ENOSPC;
+		break;
+	case EXT2_ET_SYMLINK_LOOP:
+		ret = -EMLINK;
+		break;
+	case EXT2_ET_FILE_TOO_BIG:
+		ret = -EFBIG;
+		break;
+	case EXT2_ET_TDB_ERR_EXISTS:
+	case EXT2_ET_FILE_EXISTS:
+		ret = -EEXIST;
+		break;
+	case EXT2_ET_MMP_FAILED:
+	case EXT2_ET_MMP_FSCK_ON:
+		ret = -EBUSY;
+		break;
+	case EXT2_ET_EA_KEY_NOT_FOUND:
+		ret = -ENODATA;
+		break;
+	case EXT2_ET_UNIMPLEMENTED:
+		ret = -EOPNOTSUPP;
+		break;
+	case EXT2_ET_RO_FILSYS:
+		ret = -EROFS;
+		break;
+	case EXT2_ET_MAGIC_EXT2_FILE:
+	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:
+	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;
+	case EIO:
+#ifdef EILSEQ
+	case EILSEQ:
+#endif
+	case EUCLEAN:
+		/* these errnos usually denote corruption or persistence fail */
+		is_err = 1;
+		ret = -err;
+		break;
+	default:
+		if (err < 256) {
+			/* other errno are usually operational errors */
+			ret = -err;
+		} else {
+			is_err = 1;
+			ret = -EIO;
+		}
+		break;
+	}
+
+	if (!is_err)
+		return ret;
+
+	if (ino)
+		err_printf(ff, "%s (inode #%d) at %s:%d.\n",
+			error_message(err), ino, func, line);
+	else
+		err_printf(ff, "%s at %s:%d.\n",
+			error_message(err), func, line);
+
+	/* Make a note in the error log */
+	get_now(&now);
+	ext2fs_set_tstamp(fs->super, s_last_error_time, now.tv_sec);
+	fs->super->s_last_error_ino = ino;
+	fs->super->s_last_error_line = line;
+	fs->super->s_last_error_block = err; /* Yeah... */
+	strncpy((char *)fs->super->s_last_error_func, func,
+		sizeof(fs->super->s_last_error_func));
+	if (ext2fs_get_tstamp(fs->super, s_first_error_time) == 0) {
+		ext2fs_set_tstamp(fs->super, s_first_error_time, now.tv_sec);
+		fs->super->s_first_error_ino = ino;
+		fs->super->s_first_error_line = line;
+		fs->super->s_first_error_block = err;
+		strncpy((char *)fs->super->s_first_error_func, func,
+			sizeof(fs->super->s_first_error_func));
+	}
+
+	fs->super->s_state |= EXT2_ERROR_FS;
+	fs->super->s_error_count++;
+	ext2fs_mark_super_dirty(fs);
+	ext2fs_flush(fs);
+	switch (ff->errors_behavior) {
+	case EXT2_ERRORS_CONTINUE:
+		err_printf(ff, "%s\n",
+ _("Continuing after errors; is this a good idea?"));
+		break;
+	case EXT2_ERRORS_RO:
+		if (ff->opstate == F4OP_WRITABLE) {
+			err_printf(ff, "%s\n",
+ _("Remounting read-only due to errors."));
+			ff->opstate = F4OP_READONLY;
+		}
+		fuse4fs_mmp_cancel(ff);
+		fs->flags &= ~EXT2_FLAG_RW;
+		break;
+	case EXT2_ERRORS_PANIC:
+		err_printf(ff, "%s\n",
+ _("Aborting filesystem mount due to errors."));
+		abort();
+		break;
+	}
+
+	return ret;
+}
diff --git a/lib/config.h.in b/lib/config.h.in
index a4d8ce1c3765ed..c3379758c3c9bc 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -73,6 +73,9 @@
 /* Define to 1 if PR_SET_IO_FLUSHER is present */
 #undef HAVE_PR_SET_IO_FLUSHER
 
+/* Define to 1 if fuse supports lowlevel API */
+#undef HAVE_FUSE_LOWLEVEL
+
 /* Define to 1 if you have the Mac OS X function
    CFLocaleCopyPreferredLanguages in the CoreFoundation framework. */
 #undef HAVE_CFLOCALECOPYPREFERREDLANGUAGES


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

* [PATCH 03/23] debian: create new package for fuse4fs
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure Darrick J. Wong
  2025-11-06 22:43   ` [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API Darrick J. Wong
@ 2025-11-06 22:43   ` Darrick J. Wong
  2025-11-06 22:44   ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
                     ` (19 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:43 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Create a new package for fuse4fs.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 debian/control         |   12 +++++++++++-
 debian/fuse4fs.install |    2 ++
 debian/fuse4fs.links   |    3 +++
 debian/rules           |   11 +++++++++++
 4 files changed, 27 insertions(+), 1 deletion(-)
 create mode 100644 debian/fuse4fs.install
 create mode 100644 debian/fuse4fs.links


diff --git a/debian/control b/debian/control
index fb3487cd32b99a..04df691d81b230 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: e2fsprogs
 Section: admin
 Priority: important
 Maintainer: Theodore Y. Ts'o <tytso@mit.edu>
-Build-Depends: dpkg-dev (>= 1.22.5), gettext, texinfo, pkgconf, libarchive-dev <!nocheck>, libfuse3-dev [linux-any kfreebsd-any] <!pkg.e2fsprogs.no-fuse2fs>, debhelper-compat (= 12), dh-exec, libblkid-dev, uuid-dev, m4, udev [linux-any], systemd [linux-any], systemd-dev [linux-any], cron [linux-any], dh-sequence-movetousr
+Build-Depends: dpkg-dev (>= 1.22.5), gettext, texinfo, pkgconf, libarchive-dev <!nocheck>, libfuse3-dev [linux-any kfreebsd-any] <!pkg.e2fsprogs.no-fuse2fs> <!pkg.e2fsprogs.no-fuse4fs>, debhelper-compat (= 12), dh-exec, libblkid-dev, uuid-dev, m4, udev [linux-any], systemd [linux-any], systemd-dev [linux-any], cron [linux-any], dh-sequence-movetousr
 Rules-Requires-Root: no
 Standards-Version: 4.7.2
 Homepage: http://e2fsprogs.sourceforge.net
@@ -21,6 +21,16 @@ Description: ext2 / ext3 / ext4 file system driver for FUSE
  writing from devices or image files containing ext2, ext3, and ext4
  file systems.
 
+Package: fuse4fs
+Build-Profiles: <!pkg.e2fsprogs.no-fuse4fs>
+Priority: optional
+Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3
+Architecture: linux-any kfreebsd-any
+Description: ext2 / ext3 / ext4 file system driver for FUSE
+ fuse4fs is a faster FUSE file system client that supports reading and
+ writing from devices or image files containing ext2, ext3, and ext4
+ file systems.
+
 Package: fuseext2
 Build-Profiles: <!pkg.e2fsprogs.no-fuse2fs>
 Depends: fuse2fs (>= 1.47.1-2), ${misc:Depends}
diff --git a/debian/fuse4fs.install b/debian/fuse4fs.install
new file mode 100644
index 00000000000000..17bdc90e33cb67
--- /dev/null
+++ b/debian/fuse4fs.install
@@ -0,0 +1,2 @@
+usr/bin/fuse4fs
+usr/share/man/man1/fuse4fs.1
diff --git a/debian/fuse4fs.links b/debian/fuse4fs.links
new file mode 100644
index 00000000000000..825017e11b951e
--- /dev/null
+++ b/debian/fuse4fs.links
@@ -0,0 +1,3 @@
+/usr/bin/fuse4fs          /usr/bin/ext4
+/usr/bin/fuse4fs          /usr/bin/ext3
+/usr/bin/fuse4fs          /usr/bin/ext2
diff --git a/debian/rules b/debian/rules
index c88675c9228bd0..b680eb33ceac9e 100755
--- a/debian/rules
+++ b/debian/rules
@@ -12,6 +12,7 @@ export LC_ALL ?= C
 
 ifeq ($(DEB_HOST_ARCH_OS), hurd)
 SKIP_FUSE2FS=yes
+SKIP_FUSE4FS=yes
 endif
 
 ifeq ($(DEB_HOST_ARCH_OS), linux)
@@ -22,6 +23,9 @@ endif
 ifneq ($(filter pkg.e2fsprogs.no-fuse2fs,$(DEB_BUILD_PROFILES)),)
 SKIP_FUSE2FS=yes
 endif
+ifneq ($(filter pkg.e2fsprogs.no-fuse4fs,$(DEB_BUILD_PROFILES)),)
+SKIP_FUSE4FS=yes
+endif
 
 ifneq (,$(filter-out parallel=1,$(filter parallel=%,$(DEB_BUILD_OPTIONS))))
     NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
@@ -60,6 +64,9 @@ COMMON_CONF_FLAGS = --enable-elf-shlibs --disable-ubsan \
 ifneq ($(SKIP_FUSE2FS),)
 COMMON_CONF_FLAGS +=  --disable-fuse2fs
 endif
+ifneq ($(SKIP_FUSE4FS),)
+COMMON_CONF_FLAGS +=  --disable-fuse4fs
+endif
 
 ifneq ($(DEB_BUILD_GNU_TYPE),$(DEB_HOST_GNU_TYPE))
 CC ?= $(DEB_HOST_GNU_TYPE)-gcc
@@ -189,6 +196,10 @@ endif
 ifeq ($(SKIP_FUSE2FS),)
 	dh_shlibdeps -pfuse2fs -l${stdbuilddir}/lib \
 		-- -Ldebian/e2fsprogs.shlibs.local
+endif
+ifeq ($(SKIP_FUSE4FS),)
+	dh_shlibdeps -pfuse4fs -l${stdbuilddir}/lib \
+		-- -Ldebian/e2fsprogs.shlibs.local
 endif
 	dh_shlibdeps --remaining-packages -l${stdbuilddir}/lib
 


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

* [PATCH 04/23] fuse4fs: namespace some helpers
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (2 preceding siblings ...)
  2025-11-06 22:43   ` [PATCH 03/23] debian: create new package for fuse4fs Darrick J. Wong
@ 2025-11-06 22:44   ` Darrick J. Wong
  2025-11-07  8:09     ` Amir Goldstein
  2025-11-06 22:44   ` [PATCH 05/23] fuse4fs: convert to low level API Darrick J. Wong
                     ` (18 subsequent siblings)
  22 siblings, 1 reply; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Prepend "fuse4fs_" to all helper functions that take a struct fuse4fs
object pointer.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 fuse4fs/fuse4fs.c |  177 +++++++++++++++++++++++++++--------------------------
 1 file changed, 90 insertions(+), 87 deletions(-)


diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index daf22e0fe7fde5..2ef5ad60163639 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -2,6 +2,7 @@
  * fuse4fs.c - FUSE low-level server for e2fsprogs.
  *
  * Copyright (C) 2014-2025 Oracle.
+ * Copyright (C) 2025 CTERA Networks.
  *
  * %Begin-Header%
  * This file may be redistributed under the terms of the GNU Public
@@ -852,7 +853,7 @@ static int ext2_file_type(unsigned int mode)
 	return 0;
 }
 
-static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
+static int fuse4fs_can_allocate(struct fuse4fs *ff, blk64_t num)
 {
 	ext2_filsys fs = ff->fs;
 	blk64_t reserved;
@@ -879,21 +880,22 @@ static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
 	return ext2fs_free_blocks_count(fs->super) > reserved + num;
 }
 
-static int fuse4fs_is_writeable(struct fuse4fs *ff)
+static int fuse4fs_is_writeable(const struct fuse4fs *ff)
 {
 	return ff->opstate == F4OP_WRITABLE &&
 		(ff->fs->super->s_error_count == 0);
 }
 
-static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
+static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
+				       const struct fuse_context *ctxt)
 {
 	if (ff->fakeroot)
 		return 1;
 	return ctxt->uid == 0;
 }
 
-static inline int want_check_owner(struct fuse4fs *ff,
-				   struct fuse_context *ctxt)
+static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
+					   const struct fuse_context *ctxt)
 {
 	/*
 	 * The kernel is responsible for access control, so we allow anything
@@ -901,14 +903,14 @@ static inline int want_check_owner(struct fuse4fs *ff,
 	 */
 	if (ff->kernel)
 		return 0;
-	return !is_superuser(ff, ctxt);
+	return !fuse4fs_is_superuser(ff, ctxt);
 }
 
 /* Test for append permission */
 #define A_OK	16
 
-static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
-			       const struct ext2_inode *inode, int mask)
+static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
+				 const struct ext2_inode *inode, int mask)
 {
 	EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
 
@@ -936,7 +938,7 @@ static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
 	return 0;
 }
 
-static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
+static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
 {
 	struct fuse_context *ctxt = fuse_get_context();
 	ext2_filsys fs = ff->fs;
@@ -968,7 +970,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
 	if (mask == 0)
 		return 0;
 
-	ret = check_iflags_access(ff, ino, &inode, mask);
+	ret = fuse4fs_iflags_access(ff, ino, &inode, mask);
 	if (ret)
 		return ret;
 
@@ -977,7 +979,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
 		return 0;
 
 	/* Figure out what root's allowed to do */
-	if (is_superuser(ff, ctxt)) {
+	if (fuse4fs_is_superuser(ff, ctxt)) {
 		/* Non-file access always ok */
 		if (!LINUX_S_ISREG(inode.i_mode))
 			return 0;
@@ -1783,8 +1785,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
 	return ret;
 }
 
-static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
-		      void **value, size_t *value_len)
+static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
+			    const char *name, void **value, size_t *value_len)
 {
 	ext2_filsys fs = ff->fs;
 	struct ext2_xattr_handle *h;
@@ -1814,8 +1816,8 @@ static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
 	return ret;
 }
 
-static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
-		      void *value, size_t valuelen)
+static int fuse4fs_setxattr(struct fuse4fs *ff, ext2_ino_t ino,
+			    const char *name, void *value, size_t valuelen)
 {
 	ext2_filsys fs = ff->fs;
 	struct ext2_xattr_handle *h;
@@ -1845,8 +1847,8 @@ static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
 	return ret;
 }
 
-static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
-				  ext2_ino_t child, mode_t mode)
+static int fuse4fs_propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
+					  ext2_ino_t child, mode_t mode)
 {
 	void *def;
 	size_t deflen;
@@ -1855,8 +1857,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
 	if (!ff->acl || S_ISDIR(mode))
 		return 0;
 
-	ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
-			 &deflen);
+	ret = fuse4fs_getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
+			       &deflen);
 	switch (ret) {
 	case -ENODATA:
 	case -ENOENT:
@@ -1868,7 +1870,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
 		return ret;
 	}
 
-	ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
+	ret = fuse4fs_setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def,
+			       deflen);
 	ext2fs_free_mem(&def);
 	return ret;
 }
@@ -1997,7 +2000,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 	*node_name = 0;
 
 	fs = fuse4fs_start(ff);
-	if (!fs_can_allocate(ff, 2)) {
+	if (!fuse4fs_can_allocate(ff, 2)) {
 		ret = -ENOSPC;
 		goto out2;
 	}
@@ -2009,7 +2012,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -2079,7 +2082,7 @@ 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, inode.i_mode);
+	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
 	if (ret)
 		goto out2;
 
@@ -2127,7 +2130,7 @@ static int op_mkdir(const char *path, mode_t mode)
 	*node_name = 0;
 
 	fs = fuse4fs_start(ff);
-	if (!fs_can_allocate(ff, 1)) {
+	if (!fuse4fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
 	}
@@ -2139,7 +2142,7 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -2212,7 +2215,7 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out3;
 	}
 
-	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
+	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
 	if (ret)
 		goto out3;
 
@@ -2253,7 +2256,7 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
 		base_name = filename;
 	}
 
-	ret = check_inum_access(ff, dir, W_OK);
+	ret = fuse4fs_inum_access(ff, dir, W_OK);
 	if (ret) {
 		free(filename);
 		return ret;
@@ -2275,8 +2278,8 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
 	return 0;
 }
 
-static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
-			    struct ext2_inode_large *inode)
+static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
+				    struct ext2_inode_large *inode)
 {
 	ext2_filsys fs = ff->fs;
 	struct ext2_xattr_handle *h;
@@ -2320,7 +2323,7 @@ static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
 	return 0;
 }
 
-static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
+static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
@@ -2366,7 +2369,7 @@ static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 		goto write_out;
 
 	if (ext2fs_has_feature_ea_inode(fs->super)) {
-		ret = remove_ea_inodes(ff, ino, &inode);
+		ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
 		if (ret)
 			return ret;
 	}
@@ -2407,7 +2410,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
 		goto out;
 	}
 
-	ret = check_inum_access(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ino, W_OK);
 	if (ret)
 		goto out;
 
@@ -2415,7 +2418,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
 	if (ret)
 		goto out;
 
-	ret = remove_inode(ff, ino);
+	ret = fuse4fs_remove_inode(ff, ino);
 	if (ret)
 		goto out;
 
@@ -2483,7 +2486,7 @@ static int __op_rmdir(struct fuse4fs *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);
+	ret = fuse4fs_inum_access(ff, child, W_OK);
 	if (ret)
 		goto out;
 
@@ -2502,7 +2505,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
 		goto out;
 	}
 
-	ret = check_inum_access(ff, rds.parent, W_OK);
+	ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
 	if (ret)
 		goto out;
 
@@ -2514,7 +2517,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
 	ret = fuse4fs_unlink(ff, path, &parent);
 	if (ret)
 		goto out;
-	ret = remove_inode(ff, child);
+	ret = fuse4fs_remove_inode(ff, child);
 	if (ret)
 		goto out;
 
@@ -2587,7 +2590,7 @@ static int op_symlink(const char *src, const char *dest)
 	*node_name = 0;
 
 	fs = fuse4fs_start(ff);
-	if (!fs_can_allocate(ff, 1)) {
+	if (!fuse4fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
 	}
@@ -2599,7 +2602,7 @@ static int op_symlink(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -2746,7 +2749,7 @@ static int op_rename(const char *from, const char *to,
 	FUSE4FS_CHECK_CONTEXT(ff);
 	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
 	fs = fuse4fs_start(ff);
-	if (!fs_can_allocate(ff, 5)) {
+	if (!fuse4fs_can_allocate(ff, 5)) {
 		ret = -ENOSPC;
 		goto out;
 	}
@@ -2772,12 +2775,12 @@ static int op_rename(const char *from, const char *to,
 		goto out;
 	}
 
-	ret = check_inum_access(ff, from_ino, W_OK);
+	ret = fuse4fs_inum_access(ff, from_ino, W_OK);
 	if (ret)
 		goto out;
 
 	if (to_ino) {
-		ret = check_inum_access(ff, to_ino, W_OK);
+		ret = fuse4fs_inum_access(ff, to_ino, W_OK);
 		if (ret)
 			goto out;
 	}
@@ -2815,7 +2818,7 @@ static int op_rename(const char *from, const char *to,
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, from_dir_ino, W_OK);
+	ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
 	if (ret)
 		goto out2;
 
@@ -2840,7 +2843,7 @@ static int op_rename(const char *from, const char *to,
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, to_dir_ino, W_OK);
+	ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
 	if (ret)
 		goto out2;
 
@@ -2992,7 +2995,7 @@ static int op_link(const char *src, const char *dest)
 	*node_name = 0;
 
 	fs = fuse4fs_start(ff);
-	if (!fs_can_allocate(ff, 2)) {
+	if (!fuse4fs_can_allocate(ff, 2)) {
 		ret = -ENOSPC;
 		goto out2;
 	}
@@ -3005,7 +3008,7 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -3021,7 +3024,7 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 	}
 
-	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
 	if (ret)
 		goto out2;
 
@@ -3066,7 +3069,7 @@ static int op_link(const char *src, const char *dest)
 }
 
 /* Obtain group ids of the process that sent us a command(?) */
-static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
+static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
@@ -3111,8 +3114,8 @@ static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
  * 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)
+static int fuse4fs_in_file_group(struct fuse_context *ctxt,
+				 const struct ext2_inode_large *inode)
 {
 	struct fuse4fs *ff = fuse4fs_get();
 	gid_t *gids = NULL;
@@ -3124,7 +3127,7 @@ static int in_file_group(struct fuse_context *ctxt,
 	if (ctxt->gid == gid)
 		return 1;
 
-	ret = get_req_groups(ff, &gids, &nr_gids);
+	ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
 	if (ret == -ENOENT) {
 		/* magic return code for "could not get caller group info" */
 		return 0;
@@ -3167,11 +3170,11 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
 		goto out;
 	}
 
-	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
 	if (ret)
 		goto out;
 
-	if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
+	if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
 		ret = -EPERM;
 		goto out;
 	}
@@ -3181,8 +3184,8 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
 	 * of the user's groups, but FUSE only tells us about the primary
 	 * group.
 	 */
-	if (!is_superuser(ff, ctxt)) {
-		ret = in_file_group(ctxt, &inode);
+	if (!fuse4fs_is_superuser(ff, ctxt)) {
+		ret = fuse4fs_in_file_group(ctxt, &inode);
 		if (ret < 0)
 			goto out;
 
@@ -3236,14 +3239,14 @@ 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);
+	ret = fuse4fs_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. */
-		if (want_check_owner(ff, ctxt) &&
+		if (fuse4fs_want_check_owner(ff, ctxt) &&
 		    !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
 			ret = -EPERM;
 			goto out;
@@ -3253,7 +3256,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
 
 	if (group != (gid_t) ~0) {
 		/* Only root or the owner get to change GID. */
-		if (want_check_owner(ff, ctxt) &&
+		if (fuse4fs_want_check_owner(ff, ctxt) &&
 		    inode_uid(inode) != ctxt->uid) {
 			ret = -EPERM;
 			goto out;
@@ -3363,7 +3366,7 @@ static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
 		goto out;
 	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
 
-	ret = check_inum_access(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ino, W_OK);
 	if (ret)
 		goto out;
 
@@ -3445,7 +3448,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
 	}
 	dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
 
-	ret = check_inum_access(ff, file->ino, check);
+	ret = fuse4fs_inum_access(ff, file->ino, check);
 	if (ret) {
 		/*
 		 * In a regular (Linux) fs driver, the kernel will open
@@ -3457,7 +3460,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
 		 * also employ undocumented hacks (see above).
 		 */
 		if (check == R_OK) {
-			ret = check_inum_access(ff, file->ino, X_OK);
+			ret = fuse4fs_inum_access(ff, file->ino, X_OK);
 			if (ret)
 				goto out;
 			check = X_OK;
@@ -3568,7 +3571,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 		goto out;
 	}
 
-	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
+	if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
 		ret = -ENOSPC;
 		goto out;
 	}
@@ -3768,11 +3771,11 @@ 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(ff, ino, R_OK);
+	ret = fuse4fs_inum_access(ff, ino, R_OK);
 	if (ret)
 		goto out;
 
-	ret = __getxattr(ff, ino, key, &ptr, &plen);
+	ret = fuse4fs_getxattr(ff, ino, key, &ptr, &plen);
 	if (ret)
 		goto out;
 
@@ -3838,7 +3841,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(ff, ino, R_OK);
+	ret = fuse4fs_inum_access(ff, ino, R_OK);
 	if (ret)
 		goto out;
 
@@ -3919,7 +3922,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(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ino, W_OK);
 	if (ret == -EACCES) {
 		ret = -EPERM;
 		goto out;
@@ -4008,7 +4011,7 @@ static int op_removexattr(const char *path, const char *key)
 		goto out;
 	}
 
-	if (!fs_can_allocate(ff, 1)) {
+	if (!fuse4fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out;
 	}
@@ -4020,7 +4023,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(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ino, W_OK);
 	if (ret)
 		goto out;
 
@@ -4207,7 +4210,7 @@ static int op_access(const char *path, int mask)
 		goto out;
 	}
 
-	ret = check_inum_access(ff, ino, mask);
+	ret = fuse4fs_inum_access(ff, ino, mask);
 	if (ret)
 		goto out;
 
@@ -4247,7 +4250,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	*node_name = 0;
 
 	fs = fuse4fs_start(ff);
-	if (!fs_can_allocate(ff, 1)) {
+	if (!fuse4fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
 	}
@@ -4259,7 +4262,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 	}
 
-	ret = check_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
@@ -4326,7 +4329,7 @@ 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, inode.i_mode);
+	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
 	if (ret)
 		goto out2;
 
@@ -4374,7 +4377,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
 	 */
 	if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
 		access |= A_OK;
-	ret = check_inum_access(ff, ino, access);
+	ret = fuse4fs_inum_access(ff, ino, access);
 	if (ret)
 		goto out;
 
@@ -4459,7 +4462,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	if (err)
 		return translate_error(fs, fh->ino, err);
 
-	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
 		return -EPERM;
 
 	ret = set_iflags(&inode, flags);
@@ -4508,7 +4511,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	if (err)
 		return translate_error(fs, fh->ino, err);
 
-	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
 		return -EPERM;
 
 	inode.i_generation = generation;
@@ -4633,7 +4636,7 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	if (err)
 		return translate_error(fs, fh->ino, err);
 
-	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
+	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
 		return -EPERM;
 
 	ret = set_xflags(&inode, fsx->fsx_xflags);
@@ -4762,7 +4765,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	struct fuse_context *ctxt = fuse_get_context();
 	ext2_filsys fs = ff->fs;
 
-	if (!is_superuser(ff, ctxt))
+	if (!fuse4fs_is_superuser(ff, ctxt))
 		return -EPERM;
 
 	err_printf(ff, "%s.\n", _("shut down requested"));
@@ -4884,7 +4887,7 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
 		   (unsigned long long)len,
 		   (unsigned long long)start,
 		   (unsigned long long)end);
-	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
+	if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
 		return -ENOSPC;
 
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
@@ -4927,9 +4930,9 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
 	return err;
 }
 
-static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
-				    struct ext2_inode_large *inode,
-				    off_t offset, off_t len, char **buf)
+static errcode_t fuse4fs_zero_middle(struct fuse4fs *ff, ext2_ino_t ino,
+				     struct ext2_inode_large *inode,
+				     off_t offset, off_t len, char **buf)
 {
 	ext2_filsys fs = ff->fs;
 	blk64_t blk;
@@ -4963,9 +4966,9 @@ static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
 	return io_channel_write_blk64(fs->io, blk, 1, *buf);
 }
 
-static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
-				  struct ext2_inode_large *inode, off_t offset,
-				  int clean_before, char **buf)
+static errcode_t fuse4fs_zero_edge(struct fuse4fs *ff, ext2_ino_t ino,
+				   struct ext2_inode_large *inode, off_t offset,
+				   int clean_before, char **buf)
 {
 	ext2_filsys fs = ff->fs;
 	blk64_t blk;
@@ -5056,13 +5059,13 @@ static int fuse4fs_punch_range(struct fuse4fs *ff,
 
 	/* Zero everything before the first block and after the last block */
 	if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
-		err = clean_block_middle(ff, fh->ino, &inode, offset,
+		err = fuse4fs_zero_middle(ff, fh->ino, &inode, offset,
 					 len, &buf);
 	else {
-		err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
+		err = fuse4fs_zero_edge(ff, fh->ino, &inode, offset, 0, &buf);
 		if (!err)
-			err = clean_block_edge(ff, fh->ino, &inode,
-					       offset + len, 1, &buf);
+			err = fuse4fs_zero_edge(ff, fh->ino, &inode,
+						offset + len, 1, &buf);
 	}
 	if (buf)
 		ext2fs_free_mem(&buf);


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

* [PATCH 05/23] fuse4fs: convert to low level API
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (3 preceding siblings ...)
  2025-11-06 22:44   ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
@ 2025-11-06 22:44   ` Darrick J. Wong
  2025-11-06 22:44   ` [PATCH 06/23] libsupport: port the kernel list.h to libsupport Darrick J. Wong
                     ` (17 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
  To: tytso; +Cc: amir73il, linux-ext4

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

Convert fuse4fs to the lowlevel fuse API.  Amir supplied the auto
translation; I ported and cleaned it up by hand, and did the QA work to
make sure it still runs correctly.

Co-developed-by: Claude claude-4-sonnet
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 fuse4fs/fuse4fs.c | 2018 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 1074 insertions(+), 944 deletions(-)


diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 2ef5ad60163639..7585f1ff346d84 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -40,7 +40,7 @@
 # define __SET_FOB_FOR_FUSE
 # define _FILE_OFFSET_BITS 64
 #endif /* _FILE_OFFSET_BITS */
-#include <fuse.h>
+#include <fuse_lowlevel.h>
 #ifdef __SET_FOB_FOR_FUSE
 # undef _FILE_OFFSET_BITS
 #endif /* __SET_FOB_FOR_FUSE */
@@ -116,6 +116,8 @@
 #endif
 #endif /* !defined(ENODATA) */
 
+#define FUSE4FS_ATTR_TIMEOUT	(0.0)
+
 static inline uint64_t round_up(uint64_t b, unsigned int align)
 {
 	unsigned int m;
@@ -258,16 +260,18 @@ struct fuse4fs {
 	/* options set by fuse_opt_parse must be of type int */
 	int timing;
 #endif
+	struct fuse_session *fuse;
 };
 
-#define FUSE4FS_CHECK_HANDLE(ff, fh) \
+#define FUSE4FS_CHECK_HANDLE(req, fh) \
 	do { \
 		if ((fh) == NULL || (fh)->magic != FUSE4FS_FILE_MAGIC) { \
 			fprintf(stderr, \
 				"FUSE4FS: Corrupt in-memory file handle at %s:%d!\n", \
 				__func__, __LINE__); \
 			fflush(stderr); \
-			return -EUCLEAN; \
+			fuse_reply_err(req, EUCLEAN); \
+			return; \
 		} \
 	} while (0)
 
@@ -285,12 +289,43 @@ struct fuse4fs {
 		} \
 	} while (0)
 
-#define FUSE4FS_CHECK_CONTEXT(ff) \
-	__FUSE4FS_CHECK_CONTEXT((ff), return -EUCLEAN, return -EIO)
-#define FUSE4FS_CHECK_CONTEXT_DESTROY(ff) \
-	__FUSE4FS_CHECK_CONTEXT((ff), return, /* do not return */)
-#define FUSE4FS_CHECK_CONTEXT_INIT(ff) \
-	__FUSE4FS_CHECK_CONTEXT((ff), abort(), abort())
+#define FUSE4FS_CHECK_CONTEXT(req) \
+	__FUSE4FS_CHECK_CONTEXT(fuse4fs_get(req), \
+				fuse_reply_err((req), EUCLEAN); return, \
+				fuse_reply_err((req), EIO); return)
+#define FUSE4FS_CHECK_CONTEXT_DESTROY(req) \
+	__FUSE4FS_CHECK_CONTEXT((req), return, /* do not return */)
+#define FUSE4FS_CHECK_CONTEXT_INIT(req) \
+	__FUSE4FS_CHECK_CONTEXT((req), abort(), abort())
+
+static inline void fuse4fs_ino_from_fuse(ext2_ino_t *inop, fuse_ino_t fino)
+{
+	if (fino == FUSE_ROOT_ID)
+		*inop = EXT2_ROOT_INO;
+	else
+		*inop = fino;
+}
+
+static inline void fuse4fs_ino_to_fuse(fuse_ino_t *finop, ext2_ino_t ino)
+{
+	if (ino == EXT2_ROOT_INO)
+		*finop = FUSE_ROOT_ID;
+	else
+		*finop = ino;
+}
+
+#define FUSE4FS_CONVERT_FINO(req, ext2_inop, fuse_ino) \
+	do { \
+		if ((fuse_ino) > UINT32_MAX) { \
+			fprintf(stderr, \
+				"FUSE4FS: Bogus inode number 0x%llx at %s:%d!\n", \
+				(unsigned long long)(fuse_ino), __func__, __LINE__); \
+			fflush(stderr); \
+			fuse_reply_err((req), EIO); \
+			return; \
+		} \
+		fuse4fs_ino_from_fuse(ext2_inop, fuse_ino); \
+	} while (0)
 
 static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
 			     const char *func, int line);
@@ -674,11 +709,9 @@ static void fuse4fs_mmp_destroy(struct fuse4fs *ff)
 # define fuse4fs_mmp_destroy(...)	((void)0)
 #endif
 
-static inline struct fuse4fs *fuse4fs_get(void)
+static inline struct fuse4fs *fuse4fs_get(fuse_req_t req)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-
-	return ctxt->private_data;
+	return (struct fuse4fs *)fuse_req_userdata(req);
 }
 
 static inline struct fuse4fs_file_handle *
@@ -691,6 +724,7 @@ static inline void
 fuse4fs_set_handle(struct fuse_file_info *fp, struct fuse4fs_file_handle *fh)
 {
 	fp->fh = (uintptr_t)fh;
+	fp->keep_cache = 1;
 }
 
 static void get_now(struct timespec *now)
@@ -887,7 +921,7 @@ static int fuse4fs_is_writeable(const struct fuse4fs *ff)
 }
 
 static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
-				       const struct fuse_context *ctxt)
+				       const struct fuse_ctx *ctxt)
 {
 	if (ff->fakeroot)
 		return 1;
@@ -895,7 +929,7 @@ static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
 }
 
 static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
-					   const struct fuse_context *ctxt)
+					   const struct fuse_ctx *ctxt)
 {
 	/*
 	 * The kernel is responsible for access control, so we allow anything
@@ -938,9 +972,9 @@ static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
 	return 0;
 }
 
-static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
+static int fuse4fs_inum_access(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			       ext2_ino_t ino, int mask)
 {
-	struct fuse_context *ctxt = fuse_get_context();
 	ext2_filsys fs = ff->fs;
 	struct ext2_inode inode;
 	mode_t perms;
@@ -1372,9 +1406,9 @@ static int fuse4fs_mount(struct fuse4fs *ff)
 	return 0;
 }
 
-static void op_destroy(void *p EXT2FS_ATTR((unused)))
+static void op_destroy(void *userdata)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = userdata;
 	ext2_filsys fs;
 	errcode_t err;
 
@@ -1546,24 +1580,13 @@ static inline int fuse_set_feature_flag(struct fuse_conn_info *conn,
 }
 #endif
 
-static void *op_init(struct fuse_conn_info *conn,
-		     struct fuse_config *cfg EXT2FS_ATTR((unused)))
+static void op_init(void *userdata, struct fuse_conn_info *conn)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = userdata;
 	ext2_filsys fs;
 
 	FUSE4FS_CHECK_CONTEXT_INIT(ff);
 
-	/*
-	 * Configure logging a second time, because libfuse might have
-	 * redirected std{out,err} as part of daemonization.  If this fails,
-	 * give up and move on.
-	 */
-	fuse4fs_setup_logging(ff);
-	if (ff->logfd >= 0)
-		close(ff->logfd);
-	ff->logfd = -1;
-
 	fs = ff->fs;
 	dbg_printf(ff, "%s: dev=%s\n", __func__, fs->device_name);
 #ifdef FUSE_CAP_IOCTL_DIR
@@ -1580,10 +1603,6 @@ static void *op_init(struct fuse_conn_info *conn,
 	fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT);
 #endif
 	conn->time_gran = 1;
-	cfg->use_ino = 1;
-	if (ff->debug)
-		cfg->debug = 1;
-	cfg->nullpath_ok = 1;
 
 	if (ff->opstate == F4OP_WRITABLE)
 		fuse4fs_read_bitmaps(ff);
@@ -1608,132 +1627,151 @@ static void *op_init(struct fuse_conn_info *conn,
 	 */
 	conn->want = conn->want_ext & 0xFFFFFFFF;
 #endif
-	return ff;
 }
 
-static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
+struct fuse4fs_stat {
+	struct fuse_entry_param	entry;
+};
+
+static int fuse4fs_stat_inode(struct fuse4fs *ff, ext2_ino_t ino,
+			      struct ext2_inode_large *inodep,
+			      struct fuse4fs_stat *fstat)
 {
 	struct ext2_inode_large inode;
+	ext2_filsys fs = ff->fs;
+	struct fuse_entry_param *entry = &fstat->entry;
+	struct stat *statbuf = &entry->attr;
 	dev_t fakedev = 0;
 	errcode_t err;
-	int ret = 0;
 	struct timespec tv;
 
-	err = fuse4fs_read_inode(fs, ino, &inode);
-	if (err)
-		return translate_error(fs, ino, err);
+	memset(fstat, 0, sizeof(*fstat));
+
+	if (!inodep) {
+		err = fuse4fs_read_inode(fs, ino, &inode);
+		if (err)
+			return translate_error(fs, ino, err);
+		inodep = &inode;
+	}
 
 	memcpy(&fakedev, fs->super->s_uuid, sizeof(fakedev));
 	statbuf->st_dev = fakedev;
 	statbuf->st_ino = ino;
-	statbuf->st_mode = inode.i_mode;
-	statbuf->st_nlink = inode.i_links_count;
-	statbuf->st_uid = inode_uid(inode);
-	statbuf->st_gid = inode_gid(inode);
-	statbuf->st_size = EXT2_I_SIZE(&inode);
+	statbuf->st_mode = inodep->i_mode;
+	statbuf->st_nlink = inodep->i_links_count;
+	statbuf->st_uid = inode_uid(*inodep);
+	statbuf->st_gid = inode_gid(*inodep);
+	statbuf->st_size = EXT2_I_SIZE(inodep);
 	statbuf->st_blksize = fs->blocksize;
 	statbuf->st_blocks = ext2fs_get_stat_i_blocks(fs,
-						EXT2_INODE(&inode));
-	EXT4_INODE_GET_XTIME(i_atime, &tv, &inode);
+						EXT2_INODE(inodep));
+	EXT4_INODE_GET_XTIME(i_atime, &tv, inodep);
 #if HAVE_STRUCT_STAT_ST_ATIM
 	statbuf->st_atim = tv;
 #else
 	statbuf->st_atime = tv.tv_sec;
 #endif
-	EXT4_INODE_GET_XTIME(i_mtime, &tv, &inode);
+	EXT4_INODE_GET_XTIME(i_mtime, &tv, inodep);
 #if HAVE_STRUCT_STAT_ST_ATIM
 	statbuf->st_mtim = tv;
 #else
 	statbuf->st_mtime = tv.tv_sec;
 #endif
-	EXT4_INODE_GET_XTIME(i_ctime, &tv, &inode);
+	EXT4_INODE_GET_XTIME(i_ctime, &tv, inodep);
 #if HAVE_STRUCT_STAT_ST_ATIM
 	statbuf->st_ctim = tv;
 #else
 	statbuf->st_ctime = tv.tv_sec;
 #endif
-	if (LINUX_S_ISCHR(inode.i_mode) ||
-	    LINUX_S_ISBLK(inode.i_mode)) {
-		if (inode.i_block[0])
-			statbuf->st_rdev = inode.i_block[0];
+	if (LINUX_S_ISCHR(inodep->i_mode) ||
+	    LINUX_S_ISBLK(inodep->i_mode)) {
+		if (inodep->i_block[0])
+			statbuf->st_rdev = inodep->i_block[0];
 		else
-			statbuf->st_rdev = inode.i_block[1];
+			statbuf->st_rdev = inodep->i_block[1];
 	}
 
-	return ret;
-}
-
-static int __fuse4fs_file_ino(struct fuse4fs *ff, const char *path,
-			      struct fuse_file_info *fp EXT2FS_ATTR((unused)),
-			      ext2_ino_t *inop,
-			      const char *func,
-			      int line)
-{
-	ext2_filsys fs = ff->fs;
-	errcode_t err;
-
-	if (fp) {
-		struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
-
-		if (fh->ino == 0)
-			return -ESTALE;
-
-		*inop = fh->ino;
-		dbg_printf(ff, "%s: get ino=%d\n", func, fh->ino);
-		return 0;
-	}
-
-	dbg_printf(ff, "%s: get path=%s\n", func, path);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, inop);
-	if (err)
-		return __translate_error(fs, 0, err, func, line);
+	fuse4fs_ino_to_fuse(&entry->ino, ino);
+	entry->generation = inodep->i_generation;
+	entry->attr_timeout = FUSE4FS_ATTR_TIMEOUT;
+	entry->entry_timeout = FUSE4FS_ATTR_TIMEOUT;
 
 	return 0;
 }
 
-# define fuse4fs_file_ino(ff, path, fp, inop) \
-	__fuse4fs_file_ino((ff), (path), (fp), (inop), __func__, __LINE__)
-
-static int op_getattr(const char *path, struct stat *statbuf,
-		      struct fuse_file_info *fi)
+static void op_lookup(fuse_req_t req, fuse_ino_t fino, const char *name)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_stat fstat;
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
-	ext2_ino_t ino;
+	ext2_ino_t parent, child;
+	errcode_t err;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	dbg_printf(ff, "%s: parent=%d name='%s'\n", __func__, parent, name);
 	fs = fuse4fs_start(ff);
-	ret = fuse4fs_file_ino(ff, path, fi, &ino);
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
+	if (err || child == 0) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	ret = fuse4fs_stat_inode(ff, child, NULL, &fstat);
 	if (ret)
 		goto out;
-	ret = stat_inode(fs, ino, statbuf);
+
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_entry(req, &fstat.entry);
 }
 
-static int op_readlink(const char *path, char *buf, size_t len)
+static void op_getattr(fuse_req_t req, fuse_ino_t fino,
+		       struct fuse_file_info *fi EXT2FS_ATTR((unused)))
 {
-	struct fuse4fs *ff = fuse4fs_get();
-	ext2_filsys fs;
-	errcode_t err;
+	struct fuse4fs_stat fstat;
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_ino_t ino;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
+	fuse4fs_start(ff);
+	ret = fuse4fs_stat_inode(ff, ino, NULL, &fstat);
+	fuse4fs_finish(ff, ret);
+
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_attr(req, &fstat.entry.attr,
+				fstat.entry.attr_timeout);
+}
+
+static void op_readlink(fuse_req_t req, fuse_ino_t fino)
+{
 	struct ext2_inode inode;
+	char buf[PATH_MAX + 1];
+	struct fuse4fs *ff = fuse4fs_get(req);
+	ext2_filsys fs;
+	ext2_file_t file;
+	errcode_t err;
+	ext2_ino_t ino;
+	size_t len = PATH_MAX;
 	unsigned int got;
-	ext2_file_t file;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: path=%s\n", __func__, path);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
+	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
 	fs = fuse4fs_start(ff);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
 
-	err = ext2fs_read_inode(fs, ino, &inode);
+	err = ext2fs_read_inode(fs, fino, &inode);
 	if (err) {
 		ret = translate_error(fs, ino, err);
 		goto out;
@@ -1744,7 +1782,6 @@ static int op_readlink(const char *path, char *buf, size_t len)
 		goto out;
 	}
 
-	len--;
 	if (inode.i_size < len)
 		len = inode.i_size;
 	if (ext2fs_is_fast_symlink(&inode))
@@ -1782,7 +1819,11 @@ static int op_readlink(const char *path, char *buf, size_t len)
 
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_readlink(req, buf);
 }
 
 static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
@@ -1888,11 +1929,12 @@ static inline void fuse4fs_set_gid(struct ext2_inode_large *inode, gid_t gid)
 	ext2fs_set_i_gid_high(*inode, gid >> 16);
 }
 
-static int fuse4fs_new_child_gid(struct fuse4fs *ff, ext2_ino_t parent,
-				 gid_t *gid, int *parent_sgid)
+static int fuse4fs_new_child_gid(struct fuse4fs *ff,
+				 const struct fuse_ctx *ctxt,
+				 ext2_ino_t parent, gid_t *gid,
+				 int *parent_sgid)
 {
 	struct ext2_inode_large inode;
-	struct fuse_context *ctxt = fuse_get_context();
 	errcode_t err;
 
 	err = fuse4fs_read_inode(ff->fs, parent, &inode);
@@ -1968,36 +2010,44 @@ static void fuse4fs_set_extra_isize(struct fuse4fs *ff, ext2_ino_t ino,
 	inode->i_extra_isize = extra;
 }
 
-static int op_mknod(const char *path, mode_t mode, dev_t dev)
+static void fuse4fs_reply_entry(fuse_req_t req, ext2_ino_t ino,
+				struct ext2_inode_large *inode, int ret)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs_stat fstat;
+	struct fuse4fs *ff = fuse4fs_get(req);
+
+	if (ret) {
+		fuse_reply_err(req, -ret);
+		return;
+	}
+
+	/* Get stat info for the new entry */
+	ret = fuse4fs_stat_inode(ff, ino, inode, &fstat);
+	if (ret) {
+		fuse_reply_err(req, -ret);
+		return;
+	}
+
+	fuse_reply_entry(req, &fstat.entry);
+}
+
+static void op_mknod(fuse_req_t req, fuse_ino_t fino, const char *name,
+		     mode_t mode, dev_t dev)
+{
+	struct ext2_inode_large inode;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
-	char *temp_path;
 	errcode_t err;
-	char *node_name, a;
 	int filetype;
-	struct ext2_inode_large inode;
 	gid_t gid;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
-		   (unsigned int)dev);
-	temp_path = strdup(path);
-	if (!temp_path) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name = strrchr(temp_path, '/');
-	if (!node_name) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name++;
-	a = *node_name;
-	*node_name = 0;
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	dbg_printf(ff, "%s: parent=%d name='%s' mode=0%o dev=0x%x\n",
+		   __func__, parent, name, mode, (unsigned int)dev);
 
 	fs = fuse4fs_start(ff);
 	if (!fuse4fs_can_allocate(ff, 2)) {
@@ -2005,33 +2055,14 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 		goto out2;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &parent);
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out2;
-	}
-
-	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
-	*node_name = a;
+	/* On a low level server, mknod handles all non-directory types */
+	filetype = ext2_file_type(mode);
 
-	if (LINUX_S_ISCHR(mode))
-		filetype = EXT2_FT_CHRDEV;
-	else if (LINUX_S_ISBLK(mode))
-		filetype = EXT2_FT_BLKDEV;
-	else if (LINUX_S_ISFIFO(mode))
-		filetype = EXT2_FT_FIFO;
-	else if (LINUX_S_ISSOCK(mode))
-		filetype = EXT2_FT_SOCK;
-	else {
-		ret = -EINVAL;
-		goto out2;
-	}
-
-	err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+	err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, NULL);
 	if (err)
 		goto out2;
 
@@ -2041,9 +2072,9 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 		goto out2;
 	}
 
-	dbg_printf(ff, "%s: create ino=%d/name=%s in dir=%d\n", __func__, child,
-		   node_name, parent);
-	err = ext2fs_link(fs, parent, node_name, child,
+	dbg_printf(ff, "%s: create ino=%d name='%s' in dir=%d\n", __func__,
+		   child, name, parent);
+	err = ext2fs_link(fs, parent, name, child,
 			  filetype | EXT2FS_LINK_EXPAND);
 	if (err) {
 		ret = translate_error(fs, parent, err);
@@ -2092,42 +2123,28 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
 
 out2:
 	fuse4fs_finish(ff, ret);
-out:
-	free(temp_path);
-	return ret;
+	fuse4fs_reply_entry(req, child, &inode, ret);
 }
 
-static int op_mkdir(const char *path, mode_t mode)
+static void op_mkdir(fuse_req_t req, fuse_ino_t fino, const char *name,
+		     mode_t mode)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse4fs *ff = fuse4fs_get();
+	struct ext2_inode_large inode;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
-	char *temp_path;
 	errcode_t err;
-	char *node_name, a;
-	struct ext2_inode_large inode;
 	char *block;
 	blk64_t blk;
 	int ret = 0;
 	gid_t gid;
 	int parent_sgid;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
-	temp_path = strdup(path);
-	if (!temp_path) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name = strrchr(temp_path, '/');
-	if (!node_name) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name++;
-	a = *node_name;
-	*node_name = 0;
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	dbg_printf(ff, "%s: parent=%d name='%s' mode=0%o\n",
+		   __func__, parent, name, mode);
 
 	fs = fuse4fs_start(ff);
 	if (!fuse4fs_can_allocate(ff, 1)) {
@@ -2135,25 +2152,15 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out2;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &parent);
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out2;
-	}
-
-	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
-	err = fuse4fs_new_child_gid(ff, parent, &gid, &parent_sgid);
+	err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, &parent_sgid);
 	if (err)
 		goto out2;
 
-	*node_name = a;
-
-	err = ext2fs_mkdir2(fs, parent, 0, 0, EXT2FS_LINK_EXPAND,
-			    node_name, NULL);
+	err = ext2fs_mkdir2(fs, parent, 0, 0, EXT2FS_LINK_EXPAND, name, NULL);
 	if (err) {
 		ret = translate_error(fs, parent, err);
 		goto out2;
@@ -2164,14 +2171,13 @@ static int op_mkdir(const char *path, mode_t mode)
 		goto out2;
 
 	/* Still have to update the uid/gid of the dir */
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &child);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
 	if (err) {
 		ret = translate_error(fs, 0, err);
 		goto out2;
 	}
-	dbg_printf(ff, "%s: created ino=%d/path=%s in dir=%d\n", __func__, child,
-		   node_name, parent);
+	dbg_printf(ff, "%s: created ino=%d name='%s' in dir=%d\n",
+		   __func__, child, name, parent);
 
 	err = fuse4fs_read_inode(fs, child, &inode);
 	if (err) {
@@ -2227,55 +2233,7 @@ static int op_mkdir(const char *path, mode_t mode)
 	ext2fs_free_mem(&block);
 out2:
 	fuse4fs_finish(ff, ret);
-out:
-	free(temp_path);
-	return ret;
-}
-
-static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
-			  ext2_ino_t *parent)
-{
-	ext2_filsys fs = ff->fs;
-	errcode_t err;
-	ext2_ino_t dir;
-	char *filename = strdup(path);
-	char *base_name;
-	int ret;
-
-	base_name = strrchr(filename, '/');
-	if (base_name) {
-		*base_name++ = '\0';
-		err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, filename,
-				   &dir);
-		if (err) {
-			free(filename);
-			return translate_error(fs, 0, err);
-		}
-	} else {
-		dir = EXT2_ROOT_INO;
-		base_name = filename;
-	}
-
-	ret = fuse4fs_inum_access(ff, dir, W_OK);
-	if (ret) {
-		free(filename);
-		return ret;
-	}
-
-	dbg_printf(ff, "%s: unlinking name=%s from dir=%d\n", __func__,
-		   base_name, dir);
-	err = ext2fs_unlink(fs, dir, base_name, 0, 0);
-	free(filename);
-	if (err)
-		return translate_error(fs, dir, err);
-
-	ret = update_mtime(fs, dir, NULL);
-	if (ret)
-		return ret;
-
-	if (parent)
-		*parent = dir;
-	return 0;
+	fuse4fs_reply_entry(req, child, &inode, ret);
 }
 
 static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
@@ -2397,49 +2355,78 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 	return 0;
 }
 
-static int __op_unlink(struct fuse4fs *ff, const char *path)
+static int fuse4fs_unlink(struct fuse4fs *ff, ext2_ino_t parent,
+			  const char *name, ext2_ino_t child)
 {
 	ext2_filsys fs = ff->fs;
-	ext2_ino_t parent, ino;
 	errcode_t err;
 	int ret = 0;
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+	err = ext2fs_unlink(fs, parent, name, child, 0);
+	if (err) {
+		ret = translate_error(fs, parent, err);
+		goto out;
+	}
+
+	ret = update_mtime(fs, parent, NULL);
+	if (ret)
+		goto out;
+out:
+	return ret;
+}
+
+static int fuse4fs_rmfile(struct fuse4fs *ff, ext2_ino_t parent,
+			  const char *name, ext2_ino_t child)
+{
+	int ret;
+
+	ret = fuse4fs_unlink(ff, parent, name, child);
+	if (ret)
+		return ret;
+
+	return fuse4fs_remove_inode(ff, child);
+}
+
+static void op_unlink(fuse_req_t req, fuse_ino_t fino, const char *name)
+{
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	errcode_t err;
+	int ret;
+
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	fs = fuse4fs_start(ff);
+
+	/* Get the inode number for the file */
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
 	if (err) {
 		ret = translate_error(fs, 0, err);
 		goto out;
 	}
 
-	ret = fuse4fs_inum_access(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, child, W_OK);
 	if (ret)
 		goto out;
 
-	ret = fuse4fs_unlink(ff, path, &parent);
+	ret = fuse4fs_inum_access(ff, ctxt, parent, W_OK);
 	if (ret)
 		goto out;
 
-	ret = fuse4fs_remove_inode(ff, ino);
+	dbg_printf(ff, "%s: unlink parent=%d name='%s' child=%d\n",
+		   __func__, parent, name, child);
+	ret = fuse4fs_rmfile(ff, parent, name, child);
 	if (ret)
 		goto out;
 
 	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
 	if (ret)
 		goto out;
-
 out:
-	return ret;
-}
-
-static int op_unlink(const char *path)
-{
-	struct fuse4fs *ff = fuse4fs_get();
-	int ret;
-
-	FUSE4FS_CHECK_CONTEXT(ff);
-	fuse4fs_start(ff);
-	ret = __op_unlink(ff, path);
 	fuse4fs_finish(ff, ret);
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
 struct rd_struct {
@@ -2470,51 +2457,36 @@ static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
 	return 0;
 }
 
-static int __op_rmdir(struct fuse4fs *ff, const char *path)
+static int fuse4fs_rmdir(struct fuse4fs *ff, ext2_ino_t parent,
+			 const char *name, ext2_ino_t child)
 {
 	ext2_filsys fs = ff->fs;
-	ext2_ino_t parent, child;
 	errcode_t err;
 	struct ext2_inode_large inode;
-	struct rd_struct rds;
+	struct rd_struct rds = {
+		.parent = 0,
+		.empty = 1,
+	};
 	int ret = 0;
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &child);
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
-	dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
-
-	ret = fuse4fs_inum_access(ff, child, W_OK);
-	if (ret)
-		goto out;
-
-	rds.parent = 0;
-	rds.empty = 1;
-
 	err = ext2fs_dir_iterate2(fs, child, 0, 0, rmdir_proc, &rds);
 	if (err) {
 		ret = translate_error(fs, child, err);
 		goto out;
 	}
 
-	/* the kernel checks parent permissions before emptiness */
+	/* Make sure we found a dotdot entry */
 	if (rds.parent == 0) {
 		ret = translate_error(fs, child, EXT2_ET_FILESYSTEM_CORRUPTED);
 		goto out;
 	}
 
-	ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
-	if (ret)
-		goto out;
-
 	if (rds.empty == 0) {
 		ret = -ENOTEMPTY;
 		goto out;
 	}
 
-	ret = fuse4fs_unlink(ff, path, &parent);
+	ret = fuse4fs_unlink(ff, parent, name, child);
 	if (ret)
 		goto out;
 	ret = fuse4fs_remove_inode(ff, child);
@@ -2540,78 +2512,85 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
 		}
 	}
 
-	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
-	if (ret)
-		goto out;
-
 out:
 	return ret;
 }
 
-static int op_rmdir(const char *path)
+static void op_rmdir(fuse_req_t req, fuse_ino_t fino, const char *name)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	errcode_t err;
 	int ret;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	fuse4fs_start(ff);
-	ret = __op_rmdir(ff, path);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	fs = fuse4fs_start(ff);
+
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+
+	ret = fuse4fs_inum_access(ff, ctxt, parent, W_OK);
+	if (ret)
+		goto out;
+
+	ret = fuse4fs_inum_access(ff, ctxt, child, W_OK);
+	if (ret)
+		goto out;
+
+	dbg_printf(ff, "%s: unlink parent=%d name='%s' child=%d\n",
+		   __func__, parent, name, child);
+	ret = fuse4fs_rmdir(ff, parent, name, child);
+	if (ret)
+		goto out;
+
+	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+	if (ret)
+		goto out;
+
+out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
-static int op_symlink(const char *src, const char *dest)
+static void op_symlink(fuse_req_t req, const char *target, fuse_ino_t fino,
+		       const char *name)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse4fs *ff = fuse4fs_get();
-	ext2_filsys fs;
-	ext2_ino_t parent, child;
-	char *temp_path;
-	errcode_t err;
-	char *node_name, a;
 	struct ext2_inode_large inode;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
+	ext2_filsys fs;
+	ext2_ino_t parent, child;
+	errcode_t err;
 	gid_t gid;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: symlink %s to %s\n", __func__, src, dest);
-	temp_path = strdup(dest);
-	if (!temp_path) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name = strrchr(temp_path, '/');
-	if (!node_name) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name++;
-	a = *node_name;
-	*node_name = 0;
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	dbg_printf(ff, "%s: symlink dir=%d name='%s' target='%s'\n",
+		   __func__, parent, name, target);
 
 	fs = fuse4fs_start(ff);
 	if (!fuse4fs_can_allocate(ff, 1)) {
 		ret = -ENOSPC;
 		goto out2;
 	}
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &parent);
-	*node_name = a;
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out2;
-	}
 
-	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
-	err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+	err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, NULL);
 	if (err)
 		goto out2;
 
 	/* Create symlink */
-	err = ext2fs_symlink(fs, parent, 0, node_name, src);
+	err = ext2fs_symlink(fs, parent, 0, name, target);
 	if (err == EXT2_ET_DIR_NO_SPACE) {
 		err = ext2fs_expand_dir(fs, parent);
 		if (err) {
@@ -2619,7 +2598,7 @@ static int op_symlink(const char *src, const char *dest)
 			goto out2;
 		}
 
-		err = ext2fs_symlink(fs, parent, 0, node_name, src);
+		err = ext2fs_symlink(fs, parent, 0, name, target);
 	}
 	if (err) {
 		ret = translate_error(fs, parent, err);
@@ -2632,14 +2611,13 @@ static int op_symlink(const char *src, const char *dest)
 		goto out2;
 
 	/* Still have to update the uid/gid of the symlink */
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &child);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, parent, name, &child);
 	if (err) {
 		ret = translate_error(fs, 0, err);
 		goto out2;
 	}
-	dbg_printf(ff, "%s: symlinking ino=%d/name=%s to dir=%d\n", __func__,
-		   child, node_name, parent);
+	dbg_printf(ff, "%s: symlinking dir=%d name='%s' child=%d\n",
+		   __func__, parent, name, child);
 
 	err = fuse4fs_read_inode(fs, child, &inode);
 	if (err) {
@@ -2665,9 +2643,7 @@ static int op_symlink(const char *src, const char *dest)
 
 out2:
 	fuse4fs_finish(ff, ret);
-out:
-	free(temp_path);
-	return ret;
+	fuse4fs_reply_entry(req, child, &inode, ret);
 }
 
 struct update_dotdot {
@@ -2728,39 +2704,43 @@ static int fuse4fs_check_from_dir_nlink(struct fuse4fs *ff, ext2_ino_t from_ino,
 	return 0;
 }
 
-static int op_rename(const char *from, const char *to,
-		     unsigned int flags EXT2FS_ATTR((unused)))
+static void op_rename(fuse_req_t req, fuse_ino_t from_parent, const char *from,
+		      fuse_ino_t to_parent, const char *to, unsigned int flags)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	errcode_t err;
 	ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
-	char *temp_to = NULL, *temp_from = NULL;
-	char *cp, a;
 	struct ext2_inode inode;
 	struct update_dotdot ud;
 	int flushed = 0;
 	int ret = 0;
 
 	/* renameat2 is not supported */
-	if (flags)
-		return -ENOSYS;
+	if (flags) {
+		fuse_reply_err(req, ENOSYS);
+		return;
+	}
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &from_dir_ino, from_parent);
+	FUSE4FS_CONVERT_FINO(req, &to_dir_ino, to_parent);
+	dbg_printf(ff, "%s: renaming dir=%d name='%s' to dir=%d name='%s'\n",
+		   __func__, from_dir_ino, from, to_dir_ino, to);
 	fs = fuse4fs_start(ff);
 	if (!fuse4fs_can_allocate(ff, 5)) {
 		ret = -ENOSPC;
 		goto out;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, from, &from_ino);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, from_dir_ino, from, &from_ino);
 	if (err || from_ino == 0) {
 		ret = translate_error(fs, 0, err);
 		goto out;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, to, &to_ino);
+	err = ext2fs_namei(fs, EXT2_ROOT_INO, to_dir_ino, to, &to_ino);
 	if (err && err != EXT2_ET_FILE_NOT_FOUND) {
 		ret = translate_error(fs, 0, err);
 		goto out;
@@ -2769,141 +2749,85 @@ static int op_rename(const char *from, const char *to,
 	if (err == EXT2_ET_FILE_NOT_FOUND)
 		to_ino = 0;
 
+	dbg_printf(ff,
+ "%s: renaming dir=%d name='%s' child=%d to dir=%d name='%s' child=%d\n",
+		   __func__, from_dir_ino, from, from_ino, to_dir_ino, to,
+		   to_ino);
+
 	/* Already the same file? */
 	if (to_ino != 0 && to_ino == from_ino) {
 		ret = 0;
 		goto out;
 	}
 
-	ret = fuse4fs_inum_access(ff, from_ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, from_ino, W_OK);
 	if (ret)
 		goto out;
 
 	if (to_ino) {
-		ret = fuse4fs_inum_access(ff, to_ino, W_OK);
+		ret = fuse4fs_inum_access(ff, ctxt, to_ino, W_OK);
 		if (ret)
 			goto out;
 	}
 
-	temp_to = strdup(to);
-	if (!temp_to) {
-		ret = -ENOMEM;
-		goto out;
-	}
-
-	temp_from = strdup(from);
-	if (!temp_from) {
-		ret = -ENOMEM;
-		goto out2;
-	}
-
-	/* Find parent dir of the source and check write access */
-	cp = strrchr(temp_from, '/');
-	if (!cp) {
-		ret = -EINVAL;
-		goto out2;
-	}
-
-	a = *(cp + 1);
-	*(cp + 1) = 0;
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_from,
-			   &from_dir_ino);
-	*(cp + 1) = a;
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out2;
-	}
-	if (from_dir_ino == 0) {
-		ret = -ENOENT;
-		goto out2;
-	}
-
-	ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, from_dir_ino, W_OK);
 	if (ret)
-		goto out2;
-
-	/* Find parent dir of the destination and check write access */
-	cp = strrchr(temp_to, '/');
-	if (!cp) {
-		ret = -EINVAL;
-		goto out2;
-	}
-
-	a = *(cp + 1);
-	*(cp + 1) = 0;
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_to,
-			   &to_dir_ino);
-	*(cp + 1) = a;
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out2;
-	}
-	if (to_dir_ino == 0) {
-		ret = -ENOENT;
-		goto out2;
-	}
+		goto out;
 
-	ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, to_dir_ino, W_OK);
 	if (ret)
-		goto out2;
+		goto out;
 
 	ret = fuse4fs_check_from_dir_nlink(ff, from_ino, to_ino, from_dir_ino,
 					   to_dir_ino);
 	if (ret)
-		goto out2;
+		goto out;
 
 	/* If the target exists, unlink it first */
 	if (to_ino != 0) {
 		err = ext2fs_read_inode(fs, to_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, to_ino, err);
-			goto out2;
+			goto out;
 		}
 
-		dbg_printf(ff, "%s: unlinking %s ino=%d\n", __func__,
-			   LINUX_S_ISDIR(inode.i_mode) ? "dir" : "file",
-			   to_ino);
+		dbg_printf(ff, "%s: unlink dir=%d name='%s' child=%d\n",
+			   __func__, to_dir_ino, to, to_ino);
 		if (LINUX_S_ISDIR(inode.i_mode))
-			ret = __op_rmdir(ff, to);
+			ret = fuse4fs_rmdir(ff, to_dir_ino, to, to_ino);
 		else
-			ret = __op_unlink(ff, to);
+			ret = fuse4fs_rmfile(ff, to_dir_ino, to, to_ino);
 		if (ret)
-			goto out2;
+			goto out;
 	}
 
 	/* Get ready to do the move */
 	err = ext2fs_read_inode(fs, from_ino, &inode);
 	if (err) {
 		ret = translate_error(fs, from_ino, err);
-		goto out2;
+		goto out;
 	}
 
 	/* Link in the new file */
-	dbg_printf(ff, "%s: linking ino=%d/path=%s to dir=%d\n", __func__,
-		   from_ino, cp + 1, to_dir_ino);
-	err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+	dbg_printf(ff, "%s: link dir=%d name='%s' child=%d\n",
+		   __func__, to_dir_ino, to, from_ino);
+	err = ext2fs_link(fs, to_dir_ino, to, from_ino,
 			  ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
 	if (err) {
 		ret = translate_error(fs, to_dir_ino, err);
-		goto out2;
+		goto out;
 	}
 
 	/* Update '..' pointer if dir */
-	err = ext2fs_read_inode(fs, from_ino, &inode);
-	if (err) {
-		ret = translate_error(fs, from_ino, err);
-		goto out2;
-	}
-
 	if (LINUX_S_ISDIR(inode.i_mode)) {
 		ud.new_dotdot = to_dir_ino;
-		dbg_printf(ff, "%s: updating .. entry for dir=%d\n", __func__,
-			   to_dir_ino);
+		dbg_printf(ff, "%s: updating .. entry for child=%d parent=%d\n",
+			   __func__, from_ino, to_dir_ino);
 		err = ext2fs_dir_iterate2(fs, from_ino, 0, NULL,
 					  update_dotdot_helper, &ud);
 		if (err) {
 			ret = translate_error(fs, from_ino, err);
-			goto out2;
+			goto out;
 		}
 
 		/* Decrease from_dir_ino's links_count */
@@ -2912,87 +2836,76 @@ static int op_rename(const char *from, const char *to,
 		err = ext2fs_read_inode(fs, from_dir_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, from_dir_ino, err);
-			goto out2;
+			goto out;
 		}
 		ext2fs_dec_nlink(&inode);
 		err = ext2fs_write_inode(fs, from_dir_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, from_dir_ino, err);
-			goto out2;
+			goto out;
 		}
 
 		/* Increase to_dir_ino's links_count */
 		err = ext2fs_read_inode(fs, to_dir_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, to_dir_ino, err);
-			goto out2;
+			goto out;
 		}
 		ext2fs_inc_nlink(fs, &inode);
 		err = ext2fs_write_inode(fs, to_dir_ino, &inode);
 		if (err) {
 			ret = translate_error(fs, to_dir_ino, err);
-			goto out2;
+			goto out;
 		}
 	}
 
 	/* Update timestamps */
 	ret = update_ctime(fs, from_ino, NULL);
 	if (ret)
-		goto out2;
+		goto out;
 
 	ret = update_mtime(fs, to_dir_ino, NULL);
 	if (ret)
-		goto out2;
+		goto out;
 
 	/* Remove the old file */
-	ret = fuse4fs_unlink(ff, from, NULL);
+	dbg_printf(ff, "%s: unlink dir=%d name='%s' child=%d\n",
+		   __func__, from_dir_ino, from, from_ino);
+	ret = fuse4fs_unlink(ff, from_dir_ino, from, from_ino);
 	if (ret)
-		goto out2;
+		goto out;
 
 	ret = fuse4fs_dirsync_flush(ff, from_dir_ino, &flushed);
 	if (ret)
-		goto out2;
+		goto out;
 
 	if (from_dir_ino != to_dir_ino && !flushed) {
 		ret = fuse4fs_dirsync_flush(ff, to_dir_ino, NULL);
 		if (ret)
-			goto out2;
+			goto out;
 	}
 
-out2:
-	free(temp_from);
-	free(temp_to);
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
-static int op_link(const char *src, const char *dest)
+static void op_link(fuse_req_t req, fuse_ino_t child_fino,
+		    fuse_ino_t parent_fino, const char *name)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct ext2_inode_large inode;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
-	char *temp_path;
 	errcode_t err;
-	char *node_name, a;
-	ext2_ino_t parent, ino;
-	struct ext2_inode_large inode;
+	ext2_ino_t parent, child;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: src=%s dest=%s\n", __func__, src, dest);
-	temp_path = strdup(dest);
-	if (!temp_path) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name = strrchr(temp_path, '/');
-	if (!node_name) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name++;
-	a = *node_name;
-	*node_name = 0;
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, parent_fino);
+	FUSE4FS_CONVERT_FINO(req, &child, child_fino);
+	dbg_printf(ff, "%s: link dir=%d name='%s' child=%d\n",
+		   __func__, parent, name, child);
 
 	fs = fuse4fs_start(ff);
 	if (!fuse4fs_can_allocate(ff, 2)) {
@@ -3000,31 +2913,17 @@ static int op_link(const char *src, const char *dest)
 		goto out2;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &parent);
-	*node_name = a;
-	if (err) {
-		err = -ENOENT;
-		goto out2;
-	}
-
-	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
 	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);
-		goto out2;
-	}
-
-	err = fuse4fs_read_inode(fs, ino, &inode);
+	err = fuse4fs_read_inode(fs, child, &inode);
 	if (err) {
-		ret = translate_error(fs, ino, err);
+		ret = translate_error(fs, child, err);
 		goto out2;
 	}
 
-	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	ret = fuse4fs_iflags_access(ff, child, EXT2_INODE(&inode), W_OK);
 	if (ret)
 		goto out2;
 
@@ -3034,19 +2933,17 @@ static int op_link(const char *src, const char *dest)
 	}
 
 	ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
-	ret = update_ctime(fs, ino, &inode);
+	ret = update_ctime(fs, child, &inode);
 	if (ret)
 		goto out2;
 
-	err = fuse4fs_write_inode(fs, ino, &inode);
+	err = fuse4fs_write_inode(fs, child, &inode);
 	if (err) {
-		ret = translate_error(fs, ino, err);
+		ret = translate_error(fs, child, err);
 		goto out2;
 	}
 
-	dbg_printf(ff, "%s: linking ino=%d/name=%s to dir=%d\n", __func__, ino,
-		   node_name, parent);
-	err = ext2fs_link(fs, parent, node_name, ino,
+	err = ext2fs_link(fs, parent, name, child,
 			  ext2_file_type(inode.i_mode) | EXT2FS_LINK_EXPAND);
 	if (err) {
 		ret = translate_error(fs, parent, err);
@@ -3063,13 +2960,12 @@ static int op_link(const char *src, const char *dest)
 
 out2:
 	fuse4fs_finish(ff, ret);
-out:
-	free(temp_path);
-	return ret;
+	fuse4fs_reply_entry(req, child, &inode, ret);
 }
 
 /* Obtain group ids of the process that sent us a command(?) */
-static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
+static int fuse4fs_get_groups(struct fuse4fs *ff, fuse_req_t req, gid_t **gids,
+			      size_t *nr_gids)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
@@ -3082,7 +2978,7 @@ static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
 		if (err)
 			return translate_error(fs, 0, err);
 
-		ret = fuse_getgroups(nr, array);
+		ret = fuse_req_getgroups(req, nr, array);
 		if (ret < 0) {
 			/*
 			 * If there's an error, we failed to find the group
@@ -3114,10 +3010,10 @@ static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
  * that initiated the fuse request?  Returns 1 for yes, 0 for no, or a negative
  * errno.
  */
-static int fuse4fs_in_file_group(struct fuse_context *ctxt,
+static int fuse4fs_in_file_group(struct fuse4fs *ff, fuse_req_t req,
 				 const struct ext2_inode_large *inode)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
 	gid_t *gids = NULL;
 	size_t i, nr_gids = 0;
 	gid_t gid = inode_gid(*inode);
@@ -3127,7 +3023,7 @@ static int fuse4fs_in_file_group(struct fuse_context *ctxt,
 	if (ctxt->gid == gid)
 		return 1;
 
-	ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
+	ret = fuse4fs_get_groups(ff, req, &gids, &nr_gids);
 	if (ret == -ENOENT) {
 		/* magic return code for "could not get caller group info" */
 		return 0;
@@ -3147,37 +3043,21 @@ static int fuse4fs_in_file_group(struct fuse_context *ctxt,
 	return ret;
 }
 
-static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
+static int fuse4fs_chmod(struct fuse4fs *ff, fuse_req_t req, ext2_ino_t ino,
+			 mode_t mode, struct ext2_inode_large *inode)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse4fs *ff = fuse4fs_get();
-	ext2_filsys fs;
-	errcode_t err;
-	ext2_ino_t ino;
-	struct ext2_inode_large inode;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	fs = fuse4fs_start(ff);
-	ret = fuse4fs_file_ino(ff, path, fi, &ino);
-	if (ret)
-		goto out;
-	dbg_printf(ff, "%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
-
-	err = fuse4fs_read_inode(fs, ino, &inode);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
+	dbg_printf(ff, "%s: ino=%d mode=0%o\n", __func__, ino, mode);
 
-	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(inode), W_OK);
 	if (ret)
-		goto out;
+		return ret;
 
-	if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
-		ret = -EPERM;
-		goto out;
-	}
+	if (fuse4fs_want_check_owner(ff, ctxt) &&
+	    ctxt->uid != inode_uid(*inode))
+		return -EPERM;
 
 	/*
 	 * XXX: We should really check that the inode gid is not in /any/
@@ -3185,100 +3065,60 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
 	 * group.
 	 */
 	if (!fuse4fs_is_superuser(ff, ctxt)) {
-		ret = fuse4fs_in_file_group(ctxt, &inode);
+		ret = fuse4fs_in_file_group(ff, req, inode);
 		if (ret < 0)
-			goto out;
+			return ret;
 
 		if (!ret)
 			mode &= ~S_ISGID;
 	}
 
-	inode.i_mode &= ~0xFFF;
-	inode.i_mode |= mode & 0xFFF;
+	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);
+	dbg_printf(ff, "%s: ino=%d new_mode=0%o\n",
+		   __func__, ino, inode->i_mode);
 
-	ret = update_ctime(fs, ino, &inode);
-	if (ret)
-		goto out;
-
-	err = fuse4fs_write_inode(fs, ino, &inode);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
-
-out:
-	fuse4fs_finish(ff, ret);
-	return ret;
+	return 0;
 }
 
-static int op_chown(const char *path, uid_t owner, gid_t group,
-		    struct fuse_file_info *fi)
+static int fuse4fs_chown(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			 ext2_ino_t ino, const int to_set,
+			 const struct stat *attr,
+			 struct ext2_inode_large *inode)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse4fs *ff = fuse4fs_get();
-	ext2_filsys fs;
-	errcode_t err;
-	ext2_ino_t ino;
-	struct ext2_inode_large inode;
+	uid_t owner = (to_set & FUSE_SET_ATTR_UID) ? attr->st_uid : (uid_t)~0;
+	gid_t group = (to_set & FUSE_SET_ATTR_GID) ? attr->st_gid : (gid_t)~0;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	fs = fuse4fs_start(ff);
-	ret = fuse4fs_file_ino(ff, path, fi, &ino);
-	if (ret)
-		goto out;
-	dbg_printf(ff, "%s: path=%s owner=%d group=%d ino=%d\n", __func__,
-		   path, owner, group, ino);
-
-	err = fuse4fs_read_inode(fs, ino, &inode);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
+	dbg_printf(ff, "%s: ino=%d owner=%d group=%d\n",
+		   __func__, ino, owner, group);
 
-	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(inode), W_OK);
 	if (ret)
-		goto out;
+		return ret;
 
 	/* FUSE seems to feed us ~0 to mean "don't change" */
 	if (owner != (uid_t) ~0) {
 		/* Only root gets to change UID. */
 		if (fuse4fs_want_check_owner(ff, ctxt) &&
-		    !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
-			ret = -EPERM;
-			goto out;
-		}
-		fuse4fs_set_uid(&inode, owner);
+		    !(inode_uid(*inode) == ctxt->uid && owner == ctxt->uid))
+			return -EPERM;
+
+		fuse4fs_set_uid(inode, owner);
 	}
 
 	if (group != (gid_t) ~0) {
 		/* Only root or the owner get to change GID. */
 		if (fuse4fs_want_check_owner(ff, ctxt) &&
-		    inode_uid(inode) != ctxt->uid) {
-			ret = -EPERM;
-			goto out;
-		}
+		    inode_uid(*inode) != ctxt->uid)
+			return -EPERM;
 
 		/* XXX: We /should/ check group membership but FUSE */
-		fuse4fs_set_gid(&inode, group);
+		fuse4fs_set_gid(inode, group);
 	}
 
-	ret = update_ctime(fs, ino, &inode);
-	if (ret)
-		goto out;
-
-	err = fuse4fs_write_inode(fs, ino, &inode);
-	if (err) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
-
-out:
-	fuse4fs_finish(ff, ret);
-	return ret;
+	return 0;
 }
 
 static int fuse4fs_punch_posteof(struct fuse4fs *ff, ext2_ino_t ino,
@@ -3353,32 +3193,6 @@ static int fuse4fs_truncate(struct fuse4fs *ff, ext2_ino_t ino, off_t new_size)
 	return 0;
 }
 
-static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
-{
-	struct fuse4fs *ff = fuse4fs_get();
-	ext2_ino_t ino;
-	int ret = 0;
-
-	FUSE4FS_CHECK_CONTEXT(ff);
-	fuse4fs_start(ff);
-	ret = fuse4fs_file_ino(ff, path, fi, &ino);
-	if (ret)
-		goto out;
-	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
-
-	ret = fuse4fs_inum_access(ff, ino, W_OK);
-	if (ret)
-		goto out;
-
-	ret = fuse4fs_truncate(ff, ino, len);
-	if (ret)
-		goto out;
-
-out:
-	fuse4fs_finish(ff, ret);
-	return ret;
-}
-
 #ifdef __linux__
 static void detect_linux_executable_open(int kernel_flags, int *access_check,
 				  int *e2fs_open_flags)
@@ -3400,19 +3214,20 @@ static void detect_linux_executable_open(int kernel_flags, int *access_check,
 }
 #endif /* __linux__ */
 
-static int __op_open(struct fuse4fs *ff, const char *path,
-		     struct fuse_file_info *fp)
+static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			     ext2_ino_t ino, struct fuse_file_info *fp)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct fuse4fs_file_handle *file;
 	int check = 0, ret = 0;
 
-	dbg_printf(ff, "%s: path=%s oflags=0o%o\n", __func__, path, fp->flags);
+	dbg_printf(ff, "%s: ino=%d oflags=0o%o\n", __func__, ino, fp->flags);
 	err = ext2fs_get_mem(sizeof(*file), &file);
 	if (err)
 		return translate_error(fs, 0, err);
 	file->magic = FUSE4FS_FILE_MAGIC;
+	file->ino = ino;
 
 	file->open_flags = 0;
 	switch (fp->flags & O_ACCMODE) {
@@ -3441,14 +3256,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
 	if (fp->flags & O_CREAT)
 		file->open_flags |= EXT2_FILE_CREATE;
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &file->ino);
-	if (err || file->ino == 0) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
-	dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
-
-	ret = fuse4fs_inum_access(ff, file->ino, check);
+	ret = fuse4fs_inum_access(ff, ctxt, file->ino, check);
 	if (ret) {
 		/*
 		 * In a regular (Linux) fs driver, the kernel will open
@@ -3460,7 +3268,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
 		 * also employ undocumented hacks (see above).
 		 */
 		if (check == R_OK) {
-			ret = fuse4fs_inum_access(ff, file->ino, X_OK);
+			ret = fuse4fs_inum_access(ff, ctxt, file->ino, X_OK);
 			if (ret)
 				goto out;
 			check = X_OK;
@@ -3483,34 +3291,48 @@ static int __op_open(struct fuse4fs *ff, const char *path,
 	return ret;
 }
 
-static int op_open(const char *path, struct fuse_file_info *fp)
+static void op_open(fuse_req_t req, fuse_ino_t fino, struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
+	ext2_ino_t ino;
 	int ret;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
 	fuse4fs_start(ff);
-	ret = __op_open(ff, path, fp);
+	ret = fuse4fs_open_file(ff, ctxt, ino, fp);
 	fuse4fs_finish(ff, ret);
-	return ret;
+
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_open(req, fp);
 }
 
-static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
-		   size_t len, off_t offset,
-		   struct fuse_file_info *fp)
+static void op_read(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+		    size_t len, off_t offset, struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	char *buf;
 	ext2_filsys fs;
 	ext2_file_t efp;
 	errcode_t err;
 	unsigned int got = 0;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	buf = calloc(len, sizeof(char));
+	if (!buf) {
+		fuse_reply_err(req, errno);
+		return;
+	}
+
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
 		   (unsigned long long)offset, len);
+
 	fs = fuse4fs_start(ff);
 	err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
 	if (err) {
@@ -3546,14 +3368,18 @@ static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
 	}
 out:
 	fuse4fs_finish(ff, ret);
-	return got ? (int) got : ret;
+	if (got)
+		fuse_reply_buf(req, buf, got);
+	else
+		fuse_reply_err(req, -ret);
+	ext2fs_free_mem(&buf);
 }
 
-static int op_write(const char *path EXT2FS_ATTR((unused)),
-		    const char *buf, size_t len, off_t offset,
-		    struct fuse_file_info *fp)
+static void op_write(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+		     const char *buf, size_t len, off_t offset,
+		     struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
 	ext2_filsys fs;
 	ext2_file_t efp;
@@ -3561,8 +3387,8 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 	unsigned int got = 0;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	dbg_printf(ff, "%s: ino=%d off=0x%llx len=0x%zx\n", __func__, fh->ino,
 		   (unsigned long long) offset, len);
 	fs = fuse4fs_start(ff);
@@ -3616,20 +3442,23 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
 
 out:
 	fuse4fs_finish(ff, ret);
-	return got ? (int) got : ret;
+	if (got)
+		fuse_reply_write(req, got);
+	else
+		fuse_reply_err(req, -ret);
 }
 
-static int op_release(const char *path EXT2FS_ATTR((unused)),
-		      struct fuse_file_info *fp)
+static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+		       struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
 	ext2_filsys fs;
 	errcode_t err;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	fs = fuse4fs_start(ff);
 
@@ -3646,21 +3475,21 @@ static int op_release(const char *path EXT2FS_ATTR((unused)),
 
 	ext2fs_free_mem(&fh);
 
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
-static int op_fsync(const char *path EXT2FS_ATTR((unused)),
-		    int datasync EXT2FS_ATTR((unused)),
-		    struct fuse_file_info *fp)
+static void op_fsync(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+		     int datasync EXT2FS_ATTR((unused)),
+		     struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
 	ext2_filsys fs;
 	errcode_t err;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	fs = fuse4fs_start(ff);
 	/* For now, flush everything, even if it's slow */
@@ -3671,22 +3500,24 @@ static int op_fsync(const char *path EXT2FS_ATTR((unused)),
 	}
 	fuse4fs_finish(ff, ret);
 
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
-static int op_statfs(const char *path EXT2FS_ATTR((unused)),
-		     struct statvfs *buf)
+static void op_statfs(fuse_req_t req, fuse_ino_t fino)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct statvfs buf;
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	uint64_t fsid, *f;
+	ext2_ino_t ino;
 	blk64_t overhead, reserved, free;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: path=%s\n", __func__, path);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
+	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
 	fs = fuse4fs_start(ff);
-	buf->f_bsize = fs->blocksize;
-	buf->f_frsize = 0;
+	buf.f_bsize = fs->blocksize;
+	buf.f_frsize = 0;
 
 	if (ff->minixdf)
 		overhead = 0;
@@ -3699,27 +3530,27 @@ static int op_statfs(const char *path EXT2FS_ATTR((unused)),
 		reserved = ext2fs_blocks_count(fs->super) / 10;
 	free = ext2fs_free_blocks_count(fs->super);
 
-	buf->f_blocks = ext2fs_blocks_count(fs->super) - overhead;
-	buf->f_bfree = free;
+	buf.f_blocks = ext2fs_blocks_count(fs->super) - overhead;
+	buf.f_bfree = free;
 	if (free < reserved)
-		buf->f_bavail = 0;
+		buf.f_bavail = 0;
 	else
-		buf->f_bavail = free - reserved;
-	buf->f_files = fs->super->s_inodes_count;
-	buf->f_ffree = fs->super->s_free_inodes_count;
-	buf->f_favail = fs->super->s_free_inodes_count;
+		buf.f_bavail = free - reserved;
+	buf.f_files = fs->super->s_inodes_count;
+	buf.f_ffree = fs->super->s_free_inodes_count;
+	buf.f_favail = fs->super->s_free_inodes_count;
 	f = (uint64_t *)fs->super->s_uuid;
 	fsid = *f;
 	f++;
 	fsid ^= *f;
-	buf->f_fsid = fsid;
-	buf->f_flag = 0;
+	buf.f_fsid = fsid;
+	buf.f_flag = 0;
 	if (ff->opstate != F4OP_WRITABLE)
-		buf->f_flag |= ST_RDONLY;
-	buf->f_namemax = EXT2_NAME_LEN;
+		buf.f_flag |= ST_RDONLY;
+	buf.f_namemax = EXT2_NAME_LEN;
 	fuse4fs_finish(ff, 0);
 
-	return 0;
+	fuse_reply_statfs(req, &buf);
 }
 
 static const char *valid_xattr_prefixes[] = {
@@ -3743,35 +3574,33 @@ static int validate_xattr_name(const char *name)
 	return 0;
 }
 
-static int op_getxattr(const char *path, const char *key, char *value,
-		       size_t len)
+static void op_getxattr(fuse_req_t req, fuse_ino_t fino, const char *key,
+			size_t len)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
-	void *ptr;
+	void *ptr = NULL;
 	size_t plen;
 	ext2_ino_t ino;
-	errcode_t err;
 	int ret = 0;
 
-	if (!validate_xattr_name(key))
-		return -ENODATA;
+	if (!validate_xattr_name(key)) {
+		fuse_reply_err(req, ENODATA);
+		return;
+	}
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
 	fs = fuse4fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
-	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+	dbg_printf(ff, "%s: ino=%d name='%s'\n", __func__, ino, key);
 
-	ret = fuse4fs_inum_access(ff, ino, R_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, ino, R_OK);
 	if (ret)
 		goto out;
 
@@ -3780,19 +3609,26 @@ static int op_getxattr(const char *path, const char *key, char *value,
 		goto out;
 
 	if (!len) {
+		/* Just tell us the length */
 		ret = plen;
 	} else if (len < plen) {
+		/* Caller's buffer wasn't big enough */
 		ret = -ERANGE;
 	} else {
-		memcpy(value, ptr, plen);
+		/* We have data */
 		ret = plen;
 	}
 
+out:
+	fuse4fs_finish(ff, ret);
+
+	if (ret < 0)
+		fuse_reply_err(req, -ret);
+	else if (!len)
+		fuse_reply_xattr(req, ret);
+	else
+		fuse_reply_buf(req, ptr, ret);
 	ext2fs_free_mem(&ptr);
-out:
-	fuse4fs_finish(ff, ret);
-
-	return ret;
 }
 
 static int count_buffer_space(char *name, char *value EXT2FS_ATTR((unused)),
@@ -3817,31 +3653,30 @@ static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
 	return 0;
 }
 
-static int op_listxattr(const char *path, char *names, size_t len)
+static void op_listxattr(fuse_req_t req, fuse_ino_t fino, size_t len)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
+	char *names = NULL;
+	char *next_name;
 	unsigned int bufsz;
 	ext2_ino_t ino;
 	errcode_t err;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
 	fs = fuse4fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
-		ret = translate_error(fs, ino, err);
-		goto out;
-	}
 	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
 
-	ret = fuse4fs_inum_access(ff, ino, R_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, ino, R_OK);
 	if (ret)
 		goto out;
 
@@ -3866,21 +3701,28 @@ static int op_listxattr(const char *path, char *names, size_t len)
 	}
 
 	if (len == 0) {
-		ret = bufsz;
+		/* Just tell us the length */
 		goto out2;
 	} else if (len < bufsz) {
+		/* Caller's buffer wasn't big enough */
 		ret = -ERANGE;
 		goto out2;
 	}
 
 	/* Copy names out */
-	memset(names, 0, len);
-	err = ext2fs_xattrs_iterate(h, copy_names, &names);
+	names = calloc(len, sizeof(char));
+	if (!names) {
+		ret = translate_error(fs, ino, errno);
+		goto out2;
+	}
+	next_name = names;
+
+	err = ext2fs_xattrs_iterate(h, copy_names, &next_name);
 	if (err) {
 		ret = translate_error(fs, ino, err);
 		goto out2;
 	}
-	ret = bufsz;
+
 out2:
 	err = ext2fs_xattrs_close(&h);
 	if (err && !ret)
@@ -3888,41 +3730,47 @@ static int op_listxattr(const char *path, char *names, size_t len)
 out:
 	fuse4fs_finish(ff, ret);
 
-	return ret;
+	if (ret < 0)
+		fuse_reply_err(req, -ret);
+	else if (names)
+		fuse_reply_buf(req, names, bufsz);
+	else
+		fuse_reply_xattr(req, bufsz);
+	free(names);
 }
 
-static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
-		       const char *key, const char *value,
-		       size_t len, int flags)
+static void op_setxattr(fuse_req_t req, fuse_ino_t fino, const char *key,
+			const char *value, size_t len, int flags)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
 	ext2_ino_t ino;
 	errcode_t err;
 	int ret = 0;
 
-	if (flags & ~(XATTR_CREATE | XATTR_REPLACE))
-		return -EOPNOTSUPP;
+	if (flags & ~(XATTR_CREATE | XATTR_REPLACE)) {
+		fuse_reply_err(req, EOPNOTSUPP);
+		return;
+	}
 
-	if (!validate_xattr_name(key))
-		return -EINVAL;
+	if (!validate_xattr_name(key)) {
+		fuse_reply_err(req, EINVAL);
+		return;
+	}
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
 	fs = fuse4fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
 		goto out;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
-	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
+	dbg_printf(ff, "%s: ino=%d name='%s'\n", __func__, ino, key);
 
-	ret = fuse4fs_inum_access(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, ino, W_OK);
 	if (ret == -EACCES) {
 		ret = -EPERM;
 		goto out;
@@ -3979,13 +3827,13 @@ static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
 		ret = translate_error(fs, ino, err);
 out:
 	fuse4fs_finish(ff, ret);
-
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
-static int op_removexattr(const char *path, const char *key)
+static void op_removexattr(fuse_req_t req, fuse_ino_t fino, const char *key)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	struct ext2_xattr_handle *h;
 	void *buf;
@@ -3998,13 +3846,18 @@ static int op_removexattr(const char *path, const char *key)
 	 * 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 (key[0] == 0) {
+		fuse_reply_err(req, 0);
+		return;
+	}
 
-	if (!validate_xattr_name(key))
-		return -ENODATA;
+	if (!validate_xattr_name(key)) {
+		fuse_reply_err(req, ENODATA);
+		return;
+	}
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
 	fs = fuse4fs_start(ff);
 	if (!ext2fs_has_feature_xattr(fs->super)) {
 		ret = -ENOTSUP;
@@ -4016,14 +3869,9 @@ static int op_removexattr(const char *path, const char *key)
 		goto out;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
 	dbg_printf(ff, "%s: ino=%d name=%s\n", __func__, ino, key);
 
-	ret = fuse4fs_inum_access(ff, ino, W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, ino, W_OK);
 	if (ret)
 		goto out;
 
@@ -4073,24 +3921,26 @@ static int op_removexattr(const char *path, const char *key)
 		ret = translate_error(fs, ino, err);
 out:
 	fuse4fs_finish(ff, ret);
-
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
 struct readdir_iter {
 	void *buf;
-	ext2_filsys fs;
-	fuse_fill_dir_t func;
+	size_t bufsz;
+	size_t bufused;
 
+	ext2_filsys fs;
 	struct fuse4fs *ff;
-	enum fuse_readdir_flags flags;
+	fuse_req_t req;
+
+	bool readdirplus;
 	unsigned int nr;
 	off_t startpos;
 	off_t dirpos;
 };
 
 static inline mode_t dirent_fmode(ext2_filsys fs,
-				   const struct ext2_dir_entry *dirent)
+				  const struct ext2_dir_entry *dirent)
 {
 	if (!ext2fs_has_feature_filetype(fs->super))
 		return 0;
@@ -4124,10 +3974,15 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 {
 	struct readdir_iter *i = data;
 	char namebuf[EXT2_NAME_LEN + 1];
-	struct stat stat = {
-		.st_ino = dirent->inode,
-		.st_mode = dirent_fmode(i->fs, dirent),
+	struct fuse4fs_stat fstat = {
+		.entry = {
+			.attr = {
+				.st_ino = dirent->inode,
+				.st_mode = dirent_fmode(i->fs, dirent),
+			},
+		},
 	};
+	size_t entrysize;
 	int ret;
 
 	i->dirpos++;
@@ -4135,48 +3990,67 @@ static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
 		return 0;
 
 	dbg_printf(i->ff, "READDIR%s ino=%d %u offset=0x%llx\n",
-			i->flags == FUSE_READDIR_PLUS ? "PLUS" : "",
+			i->readdirplus ? "PLUS" : "",
 			dir,
 			i->nr++,
 			(unsigned long long)i->dirpos);
 
-	if (i->flags == FUSE_READDIR_PLUS) {
-		ret = stat_inode(i->fs, dirent->inode, &stat);
+	if (i->readdirplus) {
+		ret = fuse4fs_stat_inode(i->ff, dirent->inode, NULL, &fstat);
 		if (ret)
 			return DIRENT_ABORT;
 	}
 
 	memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
 	namebuf[dirent->name_len & 0xFF] = 0;
-	ret = i->func(i->buf, namebuf, &stat, i->dirpos , 0);
-	if (ret)
+
+	if (i->readdirplus) {
+		entrysize = fuse_add_direntry_plus(i->req, i->buf + i->bufused,
+						   i->bufsz - i->bufused,
+						   namebuf, &fstat.entry,
+						   i->dirpos);
+	} else {
+		entrysize = fuse_add_direntry(i->req, i->buf + i->bufused,
+					      i->bufsz - i->bufused, namebuf,
+					      &fstat.entry.attr, i->dirpos);
+	}
+	if (entrysize > i->bufsz - i->bufused) {
+		/* Buffer is full */
 		return DIRENT_ABORT;
+	}
 
+	i->bufused += entrysize;
 	return 0;
 }
 
-static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
-		      fuse_fill_dir_t fill_func, off_t offset,
-		      struct fuse_file_info *fp, enum fuse_readdir_flags flags)
+static void __op_readdir(fuse_req_t req, fuse_ino_t fino, size_t size,
+			 off_t offset, bool plus, struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
 	errcode_t err;
 	struct readdir_iter i = {
 		.ff = ff,
+		.req = req,
 		.dirpos = 0,
 		.startpos = offset,
-		.flags = flags,
+		.readdirplus = plus,
+		.bufsz = size,
 	};
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	dbg_printf(ff, "%s: ino=%d offset=0x%llx\n", __func__, fh->ino,
 			(unsigned long long)offset);
+
+	err = ext2fs_get_mem(size, &i.buf);
+	if (err) {
+		ret = translate_error(i.fs, fh->ino, err);
+		goto out;
+	}
+
 	i.fs = fuse4fs_start(ff);
-	i.buf = buf;
-	i.func = fill_func;
 	err = ext2fs_dir_iterate2(i.fs, fh->ino, 0, NULL, op_readdir_iter, &i);
 	if (err) {
 		ret = translate_error(i.fs, fh->ino, err);
@@ -4190,64 +4064,66 @@ static int op_readdir(const char *path EXT2FS_ATTR((unused)), void *buf,
 	}
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_buf(req, i.buf, i.bufused);
+
+	ext2fs_free_mem(&i.buf);
+}
+
+static void op_readdir(fuse_req_t req, fuse_ino_t fino, size_t size,
+		       off_t offset, struct fuse_file_info *fp)
+{
+	__op_readdir(req, fino, size, offset, false, fp);
+}
+
+static void op_readdirplus(fuse_req_t req, fuse_ino_t fino, size_t size,
+			   off_t offset, struct fuse_file_info *fp)
+{
+	__op_readdir(req, fino, size, offset, true, fp);
 }
 
-static int op_access(const char *path, int mask)
+static void op_access(fuse_req_t req, fuse_ino_t fino, int mask)
 {
-	struct fuse4fs *ff = fuse4fs_get();
-	ext2_filsys fs;
-	errcode_t err;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_ino_t ino;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: path=%s mask=0x%x\n", __func__, path, mask);
-	fs = fuse4fs_start(ff);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err || ino == 0) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
+	dbg_printf(ff, "%s: ino=%d mask=0x%x\n",
+		   __func__, ino, mask);
+	fuse4fs_start(ff);
 
-	ret = fuse4fs_inum_access(ff, ino, mask);
+	ret = fuse4fs_inum_access(ff, ctxt, ino, mask);
 	if (ret)
 		goto out;
 
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 
-static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
+static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
+		      mode_t mode, struct fuse_file_info *fp)
 {
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fuse4fs *ff = fuse4fs_get();
+	struct ext2_inode_large inode;
+	struct fuse4fs_stat fstat;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	ext2_ino_t parent, child;
-	char *temp_path;
 	errcode_t err;
-	char *node_name, a;
 	int filetype;
-	struct ext2_inode_large inode;
 	gid_t gid;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	dbg_printf(ff, "%s: path=%s mode=0%o\n", __func__, path, mode);
-	temp_path = strdup(path);
-	if (!temp_path) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name = strrchr(temp_path, '/');
-	if (!node_name) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	node_name++;
-	a = *node_name;
-	*node_name = 0;
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &parent, fino);
+	dbg_printf(ff, "%s: parent=%d name='%s' mode=0%o\n",
+		   __func__, parent, name, mode);
 
 	fs = fuse4fs_start(ff);
 	if (!fuse4fs_can_allocate(ff, 1)) {
@@ -4255,23 +4131,14 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 	}
 
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
-			   &parent);
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out2;
-	}
-
-	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
+	ret = fuse4fs_inum_access(ff, ctxt, parent, A_OK | W_OK);
 	if (ret)
 		goto out2;
 
-	err = fuse4fs_new_child_gid(ff, parent, &gid, NULL);
+	err = fuse4fs_new_child_gid(ff, ctxt, parent, &gid, NULL);
 	if (err)
 		goto out2;
 
-	*node_name = a;
-
 	filetype = ext2_file_type(mode);
 
 	err = ext2fs_new_inode(fs, parent, mode, 0, &child);
@@ -4280,9 +4147,9 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 	}
 
-	dbg_printf(ff, "%s: creating ino=%d/name=%s in dir=%d\n", __func__, child,
-		   node_name, parent);
-	err = ext2fs_link(fs, parent, node_name, child,
+	dbg_printf(ff, "%s: creating dir=%d name='%s' child=%d\n",
+		   __func__, parent, name, child);
+	err = ext2fs_link(fs, parent, name, child,
 			  filetype | EXT2FS_LINK_EXPAND);
 	if (err) {
 		ret = translate_error(fs, parent, err);
@@ -4334,7 +4201,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 		goto out2;
 
 	fp->flags &= ~O_TRUNC;
-	ret = __op_open(ff, path, fp);
+	ret = fuse4fs_open_file(ff, ctxt, child, fp);
 	if (ret)
 		goto out2;
 
@@ -4342,44 +4209,152 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
 	if (ret)
 		goto out2;
 
+	ret = fuse4fs_stat_inode(ff, child, NULL, &fstat);
+	if (ret)
+		goto out2;
+
 out2:
 	fuse4fs_finish(ff, ret);
-out:
-	free(temp_path);
-	return ret;
+
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_create(req, &fstat.entry, fp);
+}
+
+enum fuse4fs_time_action {
+	TA_NOW,		/* set to current time */
+	TA_OMIT,	/* do not set timestamp */
+	TA_THIS,	/* set to specific timestamp */
+};
+
+static inline const char *
+fuse4fs_time_action_string(enum fuse4fs_time_action act)
+{
+	switch (act) {
+	case TA_NOW:
+		return "now";
+	case TA_OMIT:
+		return "omit";
+	case TA_THIS:
+		return "specific";
+	}
+	return NULL; /* shut up gcc */
 }
 
-static int op_utimens(const char *path, const struct timespec ctv[2],
-		      struct fuse_file_info *fi)
+static int fuse4fs_utimens(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			   ext2_ino_t ino, const int to_set,
+			   const struct stat *attr,
+			   struct ext2_inode_large *inode)
 {
-	struct fuse4fs *ff = fuse4fs_get();
-	struct timespec tv[2];
-	ext2_filsys fs;
-	errcode_t err;
-	ext2_ino_t ino;
-	struct ext2_inode_large inode;
+	enum fuse4fs_time_action aact = TA_OMIT;
+	enum fuse4fs_time_action mact = TA_OMIT;
+	struct timespec atime = { };
+	struct timespec mtime = { };
+	struct timespec now = { };
 	int access = W_OK;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	fs = fuse4fs_start(ff);
-	ret = fuse4fs_file_ino(ff, path, fi, &ino);
-	if (ret)
-		goto out;
-	dbg_printf(ff, "%s: ino=%d atime=%lld.%ld mtime=%lld.%ld\n", __func__,
-			ino,
-			(long long int)ctv[0].tv_sec, ctv[0].tv_nsec,
-			(long long int)ctv[1].tv_sec, ctv[1].tv_nsec);
+	if (to_set & (FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW))
+		get_now(&now);
+
+	if (to_set & FUSE_SET_ATTR_ATIME_NOW) {
+		atime = now;
+		aact = TA_NOW;
+	} else if (to_set & FUSE_SET_ATTR_ATIME) {
+#if HAVE_STRUCT_STAT_ST_ATIM
+		atime = attr->st_atim;
+#else
+		atime.tv_sec = attr->st_atime;
+#endif
+		aact = TA_THIS;
+	}
+
+	if (to_set & FUSE_SET_ATTR_MTIME_NOW) {
+		mtime = now;
+		mact = TA_NOW;
+	} else if (to_set & FUSE_SET_ATTR_MTIME) {
+#if HAVE_STRUCT_STAT_ST_ATIM
+		mtime = attr->st_mtim;
+#else
+		mtime.tv_sec = attr->st_mtime;
+#endif
+		mact = TA_THIS;
+	}
+
+	dbg_printf(ff, "%s: ino=%d atime=%s:%lld.%ld mtime=%s:%lld.%ld\n",
+		   __func__, ino, fuse4fs_time_action_string(aact),
+		   (long long int)atime.tv_sec, atime.tv_nsec,
+		   fuse4fs_time_action_string(mact),
+		   (long long int)mtime.tv_sec, mtime.tv_nsec);
 
 	/*
 	 * 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)
+	if (aact == TA_NOW && mact == TA_NOW)
 		access |= A_OK;
-	ret = fuse4fs_inum_access(ff, ino, access);
+	ret = fuse4fs_inum_access(ff, ctxt, ino, access);
 	if (ret)
+		return ret;
+
+	if (aact != TA_OMIT)
+		EXT4_INODE_SET_XTIME(i_atime, &atime, inode);
+	if (mact != TA_OMIT)
+		EXT4_INODE_SET_XTIME(i_mtime, &mtime, inode);
+
+	return 0;
+}
+
+static int fuse4fs_setsize(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			   ext2_ino_t ino, off_t new_size,
+			   struct ext2_inode_large *inode)
+{
+	errcode_t err;
+	int ret;
+
+	/* Write inode because truncate makes its own copy */
+	err = fuse4fs_write_inode(ff->fs, ino, inode);
+	if (err)
+		return translate_error(ff->fs, ino, err);
+
+	ret = fuse4fs_inum_access(ff, ctxt, ino, W_OK);
+	if (ret)
+		return ret;
+
+	ret = fuse4fs_truncate(ff, ino, new_size);
+	if (ret)
+		return ret;
+
+	/* Re-read inode after truncate */
+	err = fuse4fs_read_inode(ff->fs, ino, inode);
+	if (err)
+		return translate_error(ff->fs, ino, err);
+
+	return 0;
+}
+
+static void op_setattr(fuse_req_t req, fuse_ino_t fino, struct stat *attr,
+		       int to_set, struct fuse_file_info *fi EXT2FS_ATTR((unused)))
+{
+	struct ext2_inode_large inode;
+	struct fuse4fs_stat fstat;
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
+	ext2_filsys fs;
+	ext2_ino_t ino;
+	errcode_t err;
+	int ret = 0;
+
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
+	dbg_printf(ff, "%s: ino=%d to_set=0x%x\n", __func__, ino, to_set);
+	fs = fuse4fs_start(ff);
+
+	if (!fuse4fs_is_writeable(ff)) {
+		ret = -EROFS;
 		goto out;
+	}
 
 	err = fuse4fs_read_inode(fs, ino, &inode);
 	if (err) {
@@ -4387,20 +4362,35 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
 		goto out;
 	}
 
-	tv[0] = ctv[0];
-	tv[1] = ctv[1];
-#ifdef UTIME_NOW
-	if (tv[0].tv_nsec == UTIME_NOW)
-		get_now(tv);
-	if (tv[1].tv_nsec == UTIME_NOW)
-		get_now(tv + 1);
-#endif /* UTIME_NOW */
-#ifdef UTIME_OMIT
-	if (tv[0].tv_nsec != UTIME_OMIT)
-		EXT4_INODE_SET_XTIME(i_atime, &tv[0], &inode);
-	if (tv[1].tv_nsec != UTIME_OMIT)
-		EXT4_INODE_SET_XTIME(i_mtime, &tv[1], &inode);
-#endif /* UTIME_OMIT */
+	/* Handle mode change using helper */
+	if (to_set & FUSE_SET_ATTR_MODE) {
+		ret = fuse4fs_chmod(ff, req, ino, attr->st_mode, &inode);
+		if (ret)
+			goto out;
+	}
+
+	/* Handle owner/group change using helper */
+	if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+		ret = fuse4fs_chown(ff, ctxt, ino, to_set, attr, &inode);
+		if (ret)
+			goto out;
+	}
+
+	/* Handle size change using helper */
+	if (to_set & FUSE_SET_ATTR_SIZE) {
+		ret = fuse4fs_setsize(ff, ctxt, ino, attr->st_size, &inode);
+		if (ret)
+			goto out;
+	}
+
+	/* Handle time changes using helper */
+	if (to_set & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+		ret = fuse4fs_utimens(ff, ctxt, ino, to_set, attr, &inode);
+		if (ret)
+			goto out;
+	}
+
+	/* Update ctime for any attribute change */
 	ret = update_ctime(fs, ino, &inode);
 	if (ret)
 		goto out;
@@ -4411,9 +4401,17 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
 		goto out;
 	}
 
+	/* Get updated stat info to return */
+	ret = fuse4fs_stat_inode(ff, ino, &inode, &fstat);
+
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_attr(req, &fstat.entry.attr,
+				fstat.entry.attr_timeout);
 }
 
 #define FUSE4FS_MODIFIABLE_IFLAGS \
@@ -4432,32 +4430,38 @@ static inline int set_iflags(struct ext2_inode_large *inode, __u32 iflags)
 
 #ifdef SUPPORT_I_FLAGS
 static int ioctl_getflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			  void *data)
+			  __u32 *outdata, size_t *outsize)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct ext2_inode_large inode;
 
+	if (*outsize < sizeof(__u32))
+		return -EFAULT;
+
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
 	if (err)
 		return translate_error(fs, fh->ino, err);
 
-	*(__u32 *)data = inode.i_flags & EXT2_FL_USER_VISIBLE;
+	*outdata = inode.i_flags & EXT2_FL_USER_VISIBLE;
+	*outsize = sizeof(__u32);
 	return 0;
 }
 
-static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			  void *data)
+static int ioctl_setflags(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			  struct fuse4fs_file_handle *fh, const __u32 *indata,
+			  size_t insize)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct ext2_inode_large inode;
 	int ret;
-	__u32 flags = *(__u32 *)data;
-	struct fuse_context *ctxt = fuse_get_context();
 
-	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	if (insize < sizeof(__u32))
+		return -EFAULT;
+
+	dbg_printf(ff, "%s: ino=%d iflags=0x%x\n", __func__, fh->ino, *indata);
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
 	if (err)
 		return translate_error(fs, fh->ino, err);
@@ -4465,7 +4469,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
 		return -EPERM;
 
-	ret = set_iflags(&inode, flags);
+	ret = set_iflags(&inode, *indata);
 	if (ret)
 		return ret;
 
@@ -4481,32 +4485,38 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 }
 
 static int ioctl_getversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			    void *data)
+			    __u32 *outdata, size_t *outsize)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct ext2_inode_large inode;
 
+	if (*outsize < sizeof(__u32))
+		return -EFAULT;
+
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
 	if (err)
 		return translate_error(fs, fh->ino, err);
 
-	*(__u32 *)data = inode.i_generation;
+	*outdata = inode.i_generation;
+	*outsize = sizeof(__u32);
 	return 0;
 }
 
-static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			    void *data)
+static int ioctl_setversion(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			    struct fuse4fs_file_handle *fh, const __u32 *indata,
+			    size_t insize)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct ext2_inode_large inode;
 	int ret;
-	__u32 generation = *(__u32 *)data;
-	struct fuse_context *ctxt = fuse_get_context();
 
-	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	if (insize < sizeof(__u32))
+		return -EFAULT;
+
+	dbg_printf(ff, "%s: ino=%d generation=%d\n", __func__, fh->ino, *indata);
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
 	if (err)
 		return translate_error(fs, fh->ino, err);
@@ -4514,7 +4524,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
 		return -EPERM;
 
-	inode.i_generation = generation;
+	inode.i_generation = *indata;
 
 	ret = update_ctime(fs, fh->ino, &inode);
 	if (ret)
@@ -4551,14 +4561,16 @@ static __u32 iflags_to_fsxflags(__u32 iflags)
 }
 
 static int ioctl_fsgetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			    void *data)
+			    struct fsxattr *fsx, size_t *outsize)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct ext2_inode_large inode;
-	struct fsxattr *fsx = data;
 	unsigned int inode_size;
 
+	if (*outsize < sizeof(struct fsxattr))
+		return -EFAULT;
+
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4569,6 +4581,7 @@ static int ioctl_fsgetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	if (ext2fs_inode_includes(inode_size, i_projid))
 		fsx->fsx_projid = inode_projid(inode);
 	fsx->fsx_xflags = iflags_to_fsxflags(inode.i_flags);
+	*outsize = sizeof(struct fsxattr);
 	return 0;
 }
 
@@ -4620,17 +4633,19 @@ static inline int set_xflags(struct ext2_inode_large *inode, __u32 xflags)
 	return 0;
 }
 
-static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			    void *data)
+static int ioctl_fssetxattr(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			    struct fuse4fs_file_handle *fh,
+			    const struct fsxattr *fsx, size_t insize)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
 	struct ext2_inode_large inode;
 	int ret;
-	struct fuse_context *ctxt = fuse_get_context();
-	struct fsxattr *fsx = data;
 	unsigned int inode_size;
 
+	if (insize < sizeof(struct fsxattr))
+		return -EFAULT;
+
 	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
 	err = fuse4fs_read_inode(fs, fh->ino, &inode);
 	if (err)
@@ -4661,17 +4676,24 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 
 #ifdef FITRIM
 static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			void *data)
+			const struct fstrim_range *fr_in, size_t insize,
+			struct fstrim_range *fr, size_t *outsize)
 {
 	ext2_filsys fs = ff->fs;
-	struct fstrim_range *fr = data;
 	blk64_t start, end, max_blocks, b, cleared, minlen;
 	blk64_t max_blks = ext2fs_blocks_count(fs->super);
 	errcode_t err = 0;
 
+	if (insize < sizeof(struct fstrim_range))
+		return -EFAULT;
+
+	if (*outsize < sizeof(struct fstrim_range))
+		return -EFAULT;
+
 	if (!fuse4fs_is_writeable(ff))
 		return -EROFS;
 
+	memcpy(fr, fr_in, sizeof(*fr));
 	start = FUSE4FS_B_TO_FSBT(ff, fr->start);
 	if (fr->len == -1ULL)
 		end = -1ULL;
@@ -4750,6 +4772,7 @@ static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 
 out:
 	fr->len = FUSE4FS_FSB_TO_B(ff, cleared);
+	*outsize = sizeof(struct fstrim_range);
 	dbg_printf(ff, "%s: len=%llu err=%ld\n", __func__, fr->len, err);
 	return err;
 }
@@ -4759,10 +4782,10 @@ static int ioctl_fitrim(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 # define EXT4_IOC_SHUTDOWN	_IOR('X', 125, __u32)
 #endif
 
-static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
-			  void *data)
+static int ioctl_shutdown(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
+			  struct fuse4fs_file_handle *fh, const void *indata,
+			  size_t insize)
 {
-	struct fuse_context *ctxt = fuse_get_context();
 	ext2_filsys fs = ff->fs;
 
 	if (!fuse4fs_is_superuser(ff, ctxt))
@@ -4785,49 +4808,61 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
 	return 0;
 }
 
-static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
-		    unsigned int cmd,
-		    void *arg EXT2FS_ATTR((unused)),
-		    struct fuse_file_info *fp,
-		    unsigned int flags EXT2FS_ATTR((unused)), void *data)
+static void op_ioctl(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+		     unsigned int cmd,
+		     void *arg EXT2FS_ATTR((unused)),
+		     struct fuse_file_info *fp,
+		     unsigned int flags EXT2FS_ATTR((unused)),
+		     const void *indata, size_t insize,
+		     size_t outsize)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	const struct fuse_ctx *ctxt = fuse_req_ctx(req);
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
+	void *outdata = NULL;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	if (outsize > 0) {
+		outdata = calloc(outsize, sizeof(char));
+		if (!outdata) {
+			fuse_reply_err(req, errno);
+			return;
+		}
+	}
+
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	fuse4fs_start(ff);
 	switch ((unsigned long) cmd) {
 #ifdef SUPPORT_I_FLAGS
 	case EXT2_IOC_GETFLAGS:
-		ret = ioctl_getflags(ff, fh, data);
+		ret = ioctl_getflags(ff, fh, outdata, &outsize);
 		break;
 	case EXT2_IOC_SETFLAGS:
-		ret = ioctl_setflags(ff, fh, data);
+		ret = ioctl_setflags(ff, ctxt, fh, indata, insize);
 		break;
 	case EXT2_IOC_GETVERSION:
-		ret = ioctl_getversion(ff, fh, data);
+		ret = ioctl_getversion(ff, fh, outdata, &outsize);
 		break;
 	case EXT2_IOC_SETVERSION:
-		ret = ioctl_setversion(ff, fh, data);
+		ret = ioctl_setversion(ff, ctxt, fh, indata, insize);
 		break;
 #endif
 #ifdef FS_IOC_FSGETXATTR
 	case FS_IOC_FSGETXATTR:
-		ret = ioctl_fsgetxattr(ff, fh, data);
+		ret = ioctl_fsgetxattr(ff, fh, outdata, &outsize);
 		break;
 	case FS_IOC_FSSETXATTR:
-		ret = ioctl_fssetxattr(ff, fh, data);
+		ret = ioctl_fssetxattr(ff, ctxt, fh, indata, insize);
 		break;
 #endif
 #ifdef FITRIM
 	case FITRIM:
-		ret = ioctl_fitrim(ff, fh, data);
+		ret = ioctl_fitrim(ff, fh, indata, insize, outdata, &outsize);
 		break;
 #endif
 	case EXT4_IOC_SHUTDOWN:
-		ret = ioctl_shutdown(ff, fh, data);
+		ret = ioctl_shutdown(ff, ctxt, fh, indata, insize);
 		break;
 	default:
 		dbg_printf(ff, "%s: Unknown ioctl %d\n", __func__, cmd);
@@ -4835,28 +4870,29 @@ static int op_ioctl(const char *path EXT2FS_ATTR((unused)),
 	}
 	fuse4fs_finish(ff, ret);
 
-	return ret;
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_ioctl(req, 0, outdata, outsize);
+	free(outdata);
 }
 
-static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
-		   uint64_t *idx)
+static void op_bmap(fuse_req_t req, fuse_ino_t fino,
+		    size_t blocksize EXT2FS_ATTR((unused)), uint64_t idx)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	ext2_filsys fs;
 	ext2_ino_t ino;
+	blk64_t blkno;
 	errcode_t err;
 	int ret = 0;
 
-	FUSE4FS_CHECK_CONTEXT(ff);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CONVERT_FINO(req, &ino, fino);
 	fs = fuse4fs_start(ff);
-	err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
-	if (err) {
-		ret = translate_error(fs, 0, err);
-		goto out;
-	}
-	dbg_printf(ff, "%s: ino=%d blk=%"PRIu64"\n", __func__, ino, *idx);
+	dbg_printf(ff, "%s: ino=%d blk=%"PRIu64"\n", __func__, ino, idx);
 
-	err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, *idx, 0, (blk64_t *)idx);
+	err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, idx, 0, &blkno);
 	if (err) {
 		ret = translate_error(fs, ino, err);
 		goto out;
@@ -4864,7 +4900,10 @@ static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
 
 out:
 	fuse4fs_finish(ff, ret);
-	return ret;
+	if (ret)
+		fuse_reply_err(req, -ret);
+	else
+		fuse_reply_bmap(req, blkno);
 }
 
 #ifdef SUPPORT_FALLOCATE
@@ -5107,20 +5146,22 @@ static int fuse4fs_zero_range(struct fuse4fs *ff,
 	return ret;
 }
 
-static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
-			off_t offset, off_t len,
-			struct fuse_file_info *fp)
+static void op_fallocate(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
+			 int mode, off_t offset, off_t len,
+			 struct fuse_file_info *fp)
 {
-	struct fuse4fs *ff = fuse4fs_get();
+	struct fuse4fs *ff = fuse4fs_get(req);
 	struct fuse4fs_file_handle *fh = fuse4fs_get_handle(fp);
 	int ret;
 
 	/* Catch unknown flags */
-	if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
-		return -EOPNOTSUPP;
+	if (mode & ~(FL_ZERO_RANGE_FLAG | FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG)) {
+		fuse_reply_err(req, EOPNOTSUPP);
+		return;
+	}
 
-	FUSE4FS_CHECK_CONTEXT(ff);
-	FUSE4FS_CHECK_HANDLE(ff, fh);
+	FUSE4FS_CHECK_CONTEXT(req);
+	FUSE4FS_CHECK_HANDLE(req, fh);
 	fuse4fs_start(ff);
 	if (!fuse4fs_is_writeable(ff)) {
 		ret = -EROFS;
@@ -5140,12 +5181,13 @@ static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
 		ret = fuse4fs_allocate_range(ff, fh, mode, offset, len);
 out:
 	fuse4fs_finish(ff, ret);
-
-	return ret;
+	fuse_reply_err(req, -ret);
 }
 #endif /* SUPPORT_FALLOCATE */
 
-static struct fuse_operations fs_ops = {
+static struct fuse_lowlevel_ops fs_ops = {
+	.lookup = op_lookup,
+	.setattr = op_setattr,
 	.init = op_init,
 	.destroy = op_destroy,
 	.getattr = op_getattr,
@@ -5157,9 +5199,6 @@ static struct fuse_operations fs_ops = {
 	.symlink = op_symlink,
 	.rename = op_rename,
 	.link = op_link,
-	.chmod = op_chmod,
-	.chown = op_chown,
-	.truncate = op_truncate,
 	.open = op_open,
 	.read = op_read,
 	.write = op_write,
@@ -5172,11 +5211,11 @@ static struct fuse_operations fs_ops = {
 	.removexattr = op_removexattr,
 	.opendir = op_open,
 	.readdir = op_readdir,
+	.readdirplus = op_readdirplus,
 	.releasedir = op_release,
 	.fsyncdir = op_fsync,
 	.access = op_access,
 	.create = op_create,
-	.utimens = op_utimens,
 	.bmap = op_bmap,
 #ifdef SUPERFLUOUS
 	.lock = op_lock,
@@ -5325,8 +5364,8 @@ static int fuse4fs_opt_proc(void *data, const char *arg,
 	"\n",
 			outargs->argv[0]);
 		if (key == FUSE4FS_HELPFULL) {
-			fuse_opt_add_arg(outargs, "-h");
-			fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+			printf("FUSE options:\n");
+			fuse_cmdline_help();
 		} else {
 			fprintf(stderr, "Try --helpfull to get a list of "
 				"all flags, including the FUSE options.\n");
@@ -5336,8 +5375,7 @@ static int fuse4fs_opt_proc(void *data, const char *arg,
 	case FUSE4FS_VERSION:
 		fprintf(stderr, "fuse4fs %s (%s)\n", E2FSPROGS_VERSION,
 			E2FSPROGS_DATE);
-		fuse_opt_add_arg(outargs, "--version");
-		fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+		fprintf(stderr, "FUSE library version %s\n", fuse_pkgversion());
 		exit(0);
 	}
 	return 1;
@@ -5367,8 +5405,7 @@ static void fuse4fs_compute_libfuse_args(struct fuse4fs *ff,
 	char extra_args[BUFSIZ];
 
 	/* Set up default fuse parameters */
-	snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=%s,"
-		 "fsname=%s,attr_timeout=0",
+	snprintf(extra_args, BUFSIZ, "-osubtype=%s,fsname=%s",
 		 get_subtype(argv0),
 		 ff->device);
 	if (ff->no_default_opts == 0)
@@ -5396,14 +5433,6 @@ static void fuse4fs_compute_libfuse_args(struct fuse4fs *ff,
  "-oallow_other,default_permissions,suid,dev");
 	}
 
-	/*
-	 * Since there's a Big Kernel Lock around all the libext2fs code, we
-	 * only need to start four threads -- one to decode a request, another
-	 * to do the filesystem work, a third to transmit the reply, and a
-	 * fourth to handle fuse notifications.
-	 */
-	fuse_opt_insert_arg(args, 1, "-omax_threads=4");
-
 	if (ff->debug) {
 		int	i;
 
@@ -5463,6 +5492,107 @@ static void fuse4fs_com_err_proc(const char *whoami, errcode_t code,
 	fflush(stderr);
 }
 
+static int fuse4fs_main(struct fuse_args *args, struct fuse4fs *ff)
+{
+	struct fuse_cmdline_opts opts;
+	struct fuse_session *se;
+	struct fuse_loop_config *loop_config = NULL;
+	int ret;
+
+	if (fuse_parse_cmdline(args, &opts) != 0) {
+		ret = 1;
+		goto out;
+	}
+
+	if (ff->debug)
+		opts.debug = true;
+
+	if (opts.show_help) {
+		fuse_cmdline_help();
+		ret = 0;
+		goto out_free_opts;
+	}
+
+	if (opts.show_version) {
+		printf("FUSE library version %s\n", fuse_pkgversion());
+		ret = 0;
+		goto out_free_opts;
+	}
+
+	if (!opts.mountpoint) {
+		fprintf(stderr, "error: no mountpoint specified\n");
+		ret = 2;
+		goto out_free_opts;
+	}
+
+	se = fuse_session_new(args, &fs_ops, sizeof(fs_ops), ff);
+	if (se == NULL) {
+		ret = 3;
+		goto out_free_opts;
+	}
+	ff->fuse = se;
+
+	if (fuse_session_mount(se, opts.mountpoint) != 0) {
+		ret = 4;
+		goto out_destroy_session;
+	}
+
+	if (fuse_daemonize(opts.foreground) != 0) {
+		ret = 5;
+		goto out_unmount;
+	}
+
+	/*
+	 * Configure logging a second time, because libfuse might have
+	 * redirected std{out,err} as part of daemonization.  If this fails,
+	 * give up and move on.
+	 */
+	fuse4fs_setup_logging(ff);
+	if (ff->logfd >= 0)
+		close(ff->logfd);
+	ff->logfd = -1;
+
+	if (fuse_set_signal_handlers(se) != 0) {
+		ret = 6;
+		goto out_unmount;
+	}
+
+	loop_config = fuse_loop_cfg_create();
+	if (loop_config == NULL) {
+		ret = 7;
+		goto out_remove_signal_handlers;
+	}
+
+	/*
+	 * Since there's a Big Kernel Lock around all the libext2fs code, we
+	 * only need to start four threads -- one to decode a request, another
+	 * to do the filesystem work, a third to transmit the reply, and a
+	 * fourth to handle fuse notifications.
+	 */
+	fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+	fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+	fuse_loop_cfg_set_max_threads(loop_config, 4);
+
+	if (fuse_session_loop_mt(se, loop_config) != 0) {
+		ret = 8;
+		goto out_loopcfg;
+	}
+
+out_loopcfg:
+	fuse_loop_cfg_destroy(loop_config);
+out_remove_signal_handlers:
+	fuse_remove_signal_handlers(se);
+out_unmount:
+	fuse_session_unmount(se);
+out_destroy_session:
+	ff->fuse = NULL;
+	fuse_session_destroy(se);
+out_free_opts:
+	free(opts.mountpoint);
+out:
+	return ret;
+}
+
 int main(int argc, char *argv[])
 {
 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
@@ -5559,7 +5689,7 @@ int main(int argc, char *argv[])
 
 	fuse4fs_compute_libfuse_args(&fctx, &args, argv[0]);
 
-	ret = fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+	ret = fuse4fs_main(&args, &fctx);
 	switch(ret) {
 	case 0:
 		/* success */


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

* [PATCH 06/23] libsupport: port the kernel list.h to libsupport
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (4 preceding siblings ...)
  2025-11-06 22:44   ` [PATCH 05/23] fuse4fs: convert to low level API Darrick J. Wong
@ 2025-11-06 22:44   ` Darrick J. Wong
  2025-11-06 22:44   ` [PATCH 07/23] libsupport: add a cache Darrick J. Wong
                     ` (16 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

In the next patch, we're going to add the xfsprogs cache manager code to
e2fsprogs.  That code is going into libsupport so that it doesn't become
part of the libext2fs ABI, and it depends on a richer set of list_head
helpers than what is in kernel-list.h, so port the Linux 6.17 list.h to
libsupport and drop the one in libext2fs.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/ext2fs/jfs_compat.h  |    2 
 lib/ext2fs/kernel-list.h |  111 ------
 lib/support/list.h       |  894 ++++++++++++++++++++++++++++++++++++++++++++++
 debugfs/Makefile.in      |   12 -
 e2fsck/Makefile.in       |   56 +--
 fuse4fs/Makefile.in      |    6 
 lib/e2p/Makefile.in      |    4 
 lib/ext2fs/Makefile.in   |   14 -
 misc/Makefile.in         |   12 -
 misc/tune2fs.c           |    4 
 10 files changed, 947 insertions(+), 168 deletions(-)
 delete mode 100644 lib/ext2fs/kernel-list.h
 create mode 100644 lib/support/list.h


diff --git a/lib/ext2fs/jfs_compat.h b/lib/ext2fs/jfs_compat.h
index 30b05822b6fd4d..8e598bcfa73ef7 100644
--- a/lib/ext2fs/jfs_compat.h
+++ b/lib/ext2fs/jfs_compat.h
@@ -2,7 +2,7 @@
 #ifndef _JFS_COMPAT_H
 #define _JFS_COMPAT_H
 
-#include "kernel-list.h"
+#include "support/list.h"
 #include <errno.h>
 #ifdef HAVE_NETINET_IN_H
 #include <netinet/in.h>
diff --git a/lib/ext2fs/kernel-list.h b/lib/ext2fs/kernel-list.h
deleted file mode 100644
index dd7b8e07dd56c4..00000000000000
--- a/lib/ext2fs/kernel-list.h
+++ /dev/null
@@ -1,111 +0,0 @@
-#ifndef _LINUX_LIST_H
-#define _LINUX_LIST_H
-
-#include "compiler.h"
-
-/*
- * Simple doubly linked list implementation.
- *
- * Some of the internal functions ("__xxx") are useful when
- * manipulating whole lists rather than single entries, as
- * sometimes we already know the next/prev entries and we can
- * generate better code by using them directly rather than
- * using the generic single-entry routines.
- */
-
-struct list_head {
-	struct list_head *next, *prev;
-};
-
-#define LIST_HEAD_INIT(name) { &(name), &(name) }
-
-#define INIT_LIST_HEAD(ptr) do { \
-	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
-} while (0)
-
-#if (!defined(__GNUC__) && !defined(__WATCOMC__))
-#define __inline__
-#endif
-
-/*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static __inline__ void __list_add(struct list_head * new,
-	struct list_head * prev,
-	struct list_head * next)
-{
-	next->prev = new;
-	new->next = next;
-	new->prev = prev;
-	prev->next = new;
-}
-
-/*
- * Insert a new entry after the specified head..
- */
-static __inline__ void list_add(struct list_head *new, struct list_head *head)
-{
-	__list_add(new, head, head->next);
-}
-
-/*
- * Insert a new entry at the tail
- */
-static __inline__ void list_add_tail(struct list_head *new, struct list_head *head)
-{
-	__list_add(new, head->prev, head);
-}
-
-/*
- * Delete a list entry by making the prev/next entries
- * point to each other.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static __inline__ void __list_del(struct list_head * prev,
-				  struct list_head * next)
-{
-	next->prev = prev;
-	prev->next = next;
-}
-
-static __inline__ void list_del(struct list_head *entry)
-{
-	__list_del(entry->prev, entry->next);
-}
-
-static __inline__ int list_empty(struct list_head *head)
-{
-	return head->next == head;
-}
-
-/*
- * Splice in "list" into "head"
- */
-static __inline__ void list_splice(struct list_head *list, struct list_head *head)
-{
-	struct list_head *first = list->next;
-
-	if (first != list) {
-		struct list_head *last = list->prev;
-		struct list_head *at = head->next;
-
-		first->prev = head;
-		head->next = first;
-
-		last->next = at;
-		at->prev = last;
-	}
-}
-
-#define list_entry(ptr, type, member) \
-	container_of(ptr, type, member)
-
-#define list_for_each(pos, head) \
-        for (pos = (head)->next; pos != (head); pos = pos->next)
-
-#endif
diff --git a/lib/support/list.h b/lib/support/list.h
new file mode 100644
index 00000000000000..df6c99708e4a8e
--- /dev/null
+++ b/lib/support/list.h
@@ -0,0 +1,894 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <stdbool.h>
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#ifdef __GNUC__
+#define container_of(ptr, type, member) ({				\
+	__typeof__( ((type *)0)->member ) *__mptr = (ptr);	\
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+#else
+#define container_of(ptr, type, member)				\
+	((type *)((char *)(ptr) - offsetof(type, member)))
+#endif
+
+/*
+ * Circular doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+/**
+ * INIT_LIST_HEAD - Initialize a list_head structure
+ * @list: list_head structure to be initialized.
+ *
+ * Initializes the list_head to point to itself.  If it is a list header,
+ * the result is an empty list.
+ */
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+	list->next = list;
+	list->prev = list;
+}
+
+#ifdef CONFIG_LIST_HARDENED
+
+#ifdef CONFIG_DEBUG_LIST
+# define __list_valid_slowpath
+#else
+# define __list_valid_slowpath __cold __preserve_most
+#endif
+
+/*
+ * Performs the full set of list corruption checks before __list_add().
+ * On list corruption reports a warning, and returns false.
+ */
+bool __list_valid_slowpath __list_add_valid_or_report(struct list_head *new,
+						      struct list_head *prev,
+						      struct list_head *next);
+
+/*
+ * Performs list corruption checks before __list_add(). Returns false if a
+ * corruption is detected, true otherwise.
+ *
+ * With CONFIG_LIST_HARDENED only, performs minimal list integrity checking
+ * inline to catch non-faulting corruptions, and only if a corruption is
+ * detected calls the reporting function __list_add_valid_or_report().
+ */
+static __always_inline bool __list_add_valid(struct list_head *new,
+					     struct list_head *prev,
+					     struct list_head *next)
+{
+	bool ret = true;
+
+	if (!IS_ENABLED(CONFIG_DEBUG_LIST)) {
+		/*
+		 * With the hardening version, elide checking if next and prev
+		 * are NULL, since the immediate dereference of them below would
+		 * result in a fault if NULL.
+		 *
+		 * With the reduced set of checks, we can afford to inline the
+		 * checks, which also gives the compiler a chance to elide some
+		 * of them completely if they can be proven at compile-time. If
+		 * one of the pre-conditions does not hold, the slow-path will
+		 * show a report which pre-condition failed.
+		 */
+		if (likely(next->prev == prev && prev->next == next && new != prev && new != next))
+			return true;
+		ret = false;
+	}
+
+	ret &= __list_add_valid_or_report(new, prev, next);
+	return ret;
+}
+
+/*
+ * Performs the full set of list corruption checks before __list_del_entry().
+ * On list corruption reports a warning, and returns false.
+ */
+bool __list_valid_slowpath __list_del_entry_valid_or_report(struct list_head *entry);
+
+/*
+ * Performs list corruption checks before __list_del_entry(). Returns false if a
+ * corruption is detected, true otherwise.
+ *
+ * With CONFIG_LIST_HARDENED only, performs minimal list integrity checking
+ * inline to catch non-faulting corruptions, and only if a corruption is
+ * detected calls the reporting function __list_del_entry_valid_or_report().
+ */
+static __always_inline bool __list_del_entry_valid(struct list_head *entry)
+{
+	bool ret = true;
+
+	if (!IS_ENABLED(CONFIG_DEBUG_LIST)) {
+		struct list_head *prev = entry->prev;
+		struct list_head *next = entry->next;
+
+		/*
+		 * With the hardening version, elide checking if next and prev
+		 * are NULL, LIST_POISON1 or LIST_POISON2, since the immediate
+		 * dereference of them below would result in a fault.
+		 */
+		if (likely(prev->next == entry && next->prev == entry))
+			return true;
+		ret = false;
+	}
+
+	ret &= __list_del_entry_valid_or_report(entry);
+	return ret;
+}
+#else
+static inline bool __list_add_valid(struct list_head *new,
+				struct list_head *prev,
+				struct list_head *next)
+{
+	return true;
+}
+static inline bool __list_del_entry_valid(struct list_head *entry)
+{
+	return true;
+}
+#endif
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	if (!__list_add_valid(new, prev, next))
+		return;
+
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/*
+ * Delete a list entry and clear the 'prev' pointer.
+ *
+ * This is a special-purpose list clearing method used in the networking code
+ * for lists allocated as per-cpu, where we don't want to incur the extra
+ * WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
+ * needs to check the node 'prev' pointer instead of calling list_empty().
+ */
+static inline void __list_del_clearprev(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->prev = NULL;
+}
+
+static inline void __list_del_entry(struct list_head *entry)
+{
+	if (!__list_del_entry_valid(entry))
+		return;
+
+	__list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+	__list_del_entry(entry);
+	entry->next = NULL;
+	entry->prev = NULL;
+}
+
+/**
+ * list_replace - replace old entry by new one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace(struct list_head *old,
+				struct list_head *new)
+{
+	new->next = old->next;
+	new->next->prev = new;
+	new->prev = old->prev;
+	new->prev->next = new;
+}
+
+/**
+ * list_replace_init - replace old entry by new one and initialize the old one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace_init(struct list_head *old,
+				     struct list_head *new)
+{
+	list_replace(old, new);
+	INIT_LIST_HEAD(old);
+}
+
+/**
+ * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
+ * @entry1: the location to place entry2
+ * @entry2: the location to place entry1
+ */
+static inline void list_swap(struct list_head *entry1,
+			     struct list_head *entry2)
+{
+	struct list_head *pos = entry2->prev;
+
+	list_del(entry2);
+	list_replace(entry1, entry2);
+	if (pos == entry1)
+		pos = entry2;
+	list_add(entry1, pos);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+	__list_del_entry(entry);
+	INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+	__list_del_entry(list);
+	list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+				  struct list_head *head)
+{
+	__list_del_entry(list);
+	list_add_tail(list, head);
+}
+
+/**
+ * list_bulk_move_tail - move a subsection of a list to its tail
+ * @head: the head that will follow our entry
+ * @first: first entry to move
+ * @last: last entry to move, can be the same as first
+ *
+ * Move all entries between @first and including @last before @head.
+ * All three entries must belong to the same linked list.
+ */
+static inline void list_bulk_move_tail(struct list_head *head,
+				       struct list_head *first,
+				       struct list_head *last)
+{
+	first->prev->next = last->next;
+	last->next->prev = first->prev;
+
+	head->prev->next = first;
+	first->prev = head->prev;
+
+	last->next = head;
+	head->prev = last;
+}
+
+/**
+ * list_is_first -- tests whether @list is the first entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_first(const struct list_head *list, const struct list_head *head)
+{
+	return list->prev == head;
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list, const struct list_head *head)
+{
+	return list->next == head;
+}
+
+/**
+ * list_is_head - tests whether @list is the list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_head(const struct list_head *list, const struct list_head *head)
+{
+	return list == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+	return head->next == head;
+}
+
+/**
+ * list_rotate_left - rotate the list to the left
+ * @head: the head of the list
+ */
+static inline void list_rotate_left(struct list_head *head)
+{
+	struct list_head *first;
+
+	if (!list_empty(head)) {
+		first = head->next;
+		list_move_tail(first, head);
+	}
+}
+
+/**
+ * list_rotate_to_front() - Rotate list to specific item.
+ * @list: The desired new front of the list.
+ * @head: The head of the list.
+ *
+ * Rotates list so that @list becomes the new front of the list.
+ */
+static inline void list_rotate_to_front(struct list_head *list,
+					struct list_head *head)
+{
+	/*
+	 * Deletes the list head from the list denoted by @head and
+	 * places it as the tail of @list, this effectively rotates the
+	 * list so that @list is at the front.
+	 */
+	list_move_tail(head, list);
+}
+
+/**
+ * list_is_singular - tests whether a list has just one entry.
+ * @head: the list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+	return !list_empty(head) && (head->next == head->prev);
+}
+
+static inline void __list_cut_position(struct list_head *list,
+		struct list_head *head, struct list_head *entry)
+{
+	struct list_head *new_first = entry->next;
+	list->next = head->next;
+	list->next->prev = list;
+	list->prev = entry;
+	entry->next = list;
+	head->next = new_first;
+	new_first->prev = head;
+}
+
+/**
+ * list_cut_position - cut a list into two
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *	and if so we won't cut the list
+ *
+ * This helper moves the initial part of @head, up to and
+ * including @entry, from @head to @list. You should
+ * pass on @entry an element you know is on @head. @list
+ * should be an empty list or a list you do not care about
+ * losing its data.
+ *
+ */
+static inline void list_cut_position(struct list_head *list,
+		struct list_head *head, struct list_head *entry)
+{
+	if (list_empty(head))
+		return;
+	if (list_is_singular(head) && !list_is_head(entry, head) && (entry != head->next))
+		return;
+	if (list_is_head(entry, head))
+		INIT_LIST_HEAD(list);
+	else
+		__list_cut_position(list, head, entry);
+}
+
+/**
+ * list_cut_before - cut a list into two, before given entry
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *
+ * This helper moves the initial part of @head, up to but
+ * excluding @entry, from @head to @list.  You should pass
+ * in @entry an element you know is on @head.  @list should
+ * be an empty list or a list you do not care about losing
+ * its data.
+ * If @entry == @head, all entries on @head are moved to
+ * @list.
+ */
+static inline void list_cut_before(struct list_head *list,
+				   struct list_head *head,
+				   struct list_head *entry)
+{
+	if (head->next == entry) {
+		INIT_LIST_HEAD(list);
+		return;
+	}
+	list->next = head->next;
+	list->next->prev = list;
+	list->prev = entry->prev;
+	list->prev->next = list;
+	head->next = entry;
+	entry->prev = head;
+}
+
+static inline void __list_splice(const struct list_head *list,
+				 struct list_head *prev,
+				 struct list_head *next)
+{
+	struct list_head *first = list->next;
+	struct list_head *last = list->prev;
+
+	first->prev = prev;
+	prev->next = first;
+
+	last->next = next;
+	next->prev = last;
+}
+
+/**
+ * list_splice - join two lists, this is designed for stacks
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(const struct list_head *list,
+				struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head, head->next);
+}
+
+/**
+ * list_splice_tail - join two lists, each list being a queue
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice_tail(struct list_head *list,
+				struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head->prev, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+				    struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head, head->next);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_splice_tail_init - join two lists and reinitialise the emptied list
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * Each of the lists is a queue.
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_tail_init(struct list_head *list,
+					 struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head->prev, head);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+	list_entry((ptr)->next, type, member)
+
+/**
+ * list_last_entry - get the last element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_last_entry(ptr, type, member) \
+	list_entry((ptr)->prev, type, member)
+
+/**
+ * list_first_entry_or_null - get the first element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_first_entry_or_null(ptr, type, member) ({ \
+	struct list_head *head__ = (ptr); \
+	struct list_head *pos__ = head__->next; \
+	pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
+})
+
+/**
+ * list_next_entry - get the next element in list
+ * @pos:	the type * to cursor
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_next_entry(pos, member) \
+	list_entry((pos)->member.next, typeof(*(pos)), member)
+
+/**
+ * list_next_entry_circular - get the next element in list
+ * @pos:	the type * to cursor.
+ * @head:	the list head to take the element from.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Wraparound if pos is the last element (return the first element).
+ * Note, that list is expected to be not empty.
+ */
+#define list_next_entry_circular(pos, head, member) \
+	(list_is_last(&(pos)->member, head) ? \
+	list_first_entry(head, typeof(*(pos)), member) : list_next_entry(pos, member))
+
+/**
+ * list_prev_entry - get the prev element in list
+ * @pos:	the type * to cursor
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_prev_entry(pos, member) \
+	list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+/**
+ * list_prev_entry_circular - get the prev element in list
+ * @pos:	the type * to cursor.
+ * @head:	the list head to take the element from.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Wraparound if pos is the first element (return the last element).
+ * Note, that list is expected to be not empty.
+ */
+#define list_prev_entry_circular(pos, head, member) \
+	(list_is_first(&(pos)->member, head) ? \
+	list_last_entry(head, typeof(*(pos)), member) : list_prev_entry(pos, member))
+
+/**
+ * list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; !list_is_head(pos, (head)); pos = pos->next)
+
+/**
+ * list_for_each_rcu - Iterate over a list in an RCU-safe fashion
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ */
+#define list_for_each_rcu(pos, head)		  \
+	for (pos = rcu_dereference((head)->next); \
+	     !list_is_head(pos, (head)); \
+	     pos = rcu_dereference(pos->next))
+
+/**
+ * list_for_each_continue - continue iteration over a list
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ *
+ * Continue to iterate over a list, continuing after the current position.
+ */
+#define list_for_each_continue(pos, head) \
+	for (pos = pos->next; !list_is_head(pos, (head)); pos = pos->next)
+
+/**
+ * list_for_each_prev	-	iterate over a list backwards
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+	for (pos = (head)->prev; !list_is_head(pos, (head)); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; \
+	     !list_is_head(pos, (head)); \
+	     pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+	for (pos = (head)->prev, n = pos->prev; \
+	     !list_is_head(pos, (head)); \
+	     pos = n, n = pos->prev)
+
+/**
+ * list_count_nodes - count nodes in the list
+ * @head:	the head for your list.
+ */
+static inline size_t list_count_nodes(struct list_head *head)
+{
+	struct list_head *pos;
+	size_t count = 0;
+
+	list_for_each(pos, head)
+		count++;
+
+	return count;
+}
+
+/**
+ * list_entry_is_head - test if the entry points to the head of the list
+ * @pos:	the type * to cursor
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_entry_is_head(pos, head, member)				\
+	list_is_head(&pos->member, (head))
+
+/**
+ * list_for_each_entry	-	iterate over list of given type
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_first_entry(head, typeof(*pos), member);	\
+	     !list_entry_is_head(pos, head, member);			\
+	     pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)			\
+	for (pos = list_last_entry(head, typeof(*pos), member);		\
+	     !list_entry_is_head(pos, head, member); 			\
+	     pos = list_prev_entry(pos, member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
+ * @pos:	the type * to use as a start point
+ * @head:	the head of the list
+ * @member:	the name of the list_head within the struct.
+ *
+ * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+	((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) 		\
+	for (pos = list_next_entry(pos, member);			\
+	     !list_entry_is_head(pos, head, member);			\
+	     pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member)		\
+	for (pos = list_prev_entry(pos, member);			\
+	     !list_entry_is_head(pos, head, member);			\
+	     pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current point
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) 			\
+	for (; !list_entry_is_head(pos, head, member);			\
+	     pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_from_reverse - iterate backwards over list of given type
+ *                                    from the current point
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from_reverse(pos, head, member)		\
+	for (; !list_entry_is_head(pos, head, member);			\
+	     pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_first_entry(head, typeof(*pos), member),	\
+		n = list_next_entry(pos, member);			\
+	     !list_entry_is_head(pos, head, member); 			\
+	     pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_continue - continue list iteration safe against removal
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) 		\
+	for (pos = list_next_entry(pos, member), 				\
+		n = list_next_entry(pos, member);				\
+	     !list_entry_is_head(pos, head, member);				\
+	     pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_from - iterate over list from current point safe against removal
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) 			\
+	for (n = list_next_entry(pos, member);					\
+	     !list_entry_is_head(pos, head, member);				\
+	     pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member)		\
+	for (pos = list_last_entry(head, typeof(*pos), member),		\
+		n = list_prev_entry(pos, member);			\
+	     !list_entry_is_head(pos, head, member); 			\
+	     pos = n, n = list_prev_entry(n, member))
+
+/**
+ * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
+ * @pos:	the loop cursor used in the list_for_each_entry_safe loop
+ * @n:		temporary storage used in list_for_each_entry_safe
+ * @member:	the name of the list_head within the struct.
+ *
+ * list_safe_reset_next is not safe to use in general if the list may be
+ * modified concurrently (eg. the lock is dropped in the loop body). An
+ * exception to this is if the cursor element (pos) is pinned in the list,
+ * and list_safe_reset_next is called after re-taking the lock and before
+ * completing the current iteration of the loop body.
+ */
+#define list_safe_reset_next(pos, n, member)				\
+	n = list_next_entry(pos, member)
+
+#endif
diff --git a/debugfs/Makefile.in b/debugfs/Makefile.in
index 689bf0c4a3c13d..700ae87418c268 100644
--- a/debugfs/Makefile.in
+++ b/debugfs/Makefile.in
@@ -195,7 +195,7 @@ debugfs.o: $(srcdir)/debugfs.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/version.h \
  $(srcdir)/../e2fsck/jfs_user.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/plausible.h
 util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ss/ss.h \
@@ -287,7 +287,7 @@ logdump.o: $(srcdir)/logdump.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/../e2fsck/jfs_user.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h
 htree.o: $(srcdir)/htree.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \
@@ -408,7 +408,7 @@ journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h
 revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -418,7 +418,7 @@ revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h
 recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -428,7 +428,7 @@ recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h
 do_journal.o: $(srcdir)/do_journal.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \
@@ -442,7 +442,7 @@ do_journal.o: $(srcdir)/do_journal.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/journal.h $(srcdir)/../e2fsck/jfs_user.h
 do_orphan.o: $(srcdir)/do_orphan.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \
diff --git a/e2fsck/Makefile.in b/e2fsck/Makefile.in
index fbb7b156d5c759..52fad9cbfd2b23 100644
--- a/e2fsck/Makefile.in
+++ b/e2fsck/Makefile.in
@@ -282,7 +282,7 @@ e2fsck.o: $(srcdir)/e2fsck.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 super.o: $(srcdir)/super.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -296,7 +296,7 @@ super.o: $(srcdir)/super.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 pass1.o: $(srcdir)/pass1.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -310,7 +310,7 @@ pass1.o: $(srcdir)/pass1.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/e2p/e2p.h $(srcdir)/problem.h
 pass1b.o: $(srcdir)/pass1b.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
@@ -324,7 +324,7 @@ pass1b.o: $(srcdir)/pass1b.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h $(top_srcdir)/lib/support/dict.h
 pass2.o: $(srcdir)/pass2.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -338,7 +338,7 @@ pass2.o: $(srcdir)/pass2.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h $(top_srcdir)/lib/support/dict.h
 pass3.o: $(srcdir)/pass3.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -352,7 +352,7 @@ pass3.o: $(srcdir)/pass3.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 pass4.o: $(srcdir)/pass4.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -366,7 +366,7 @@ pass4.o: $(srcdir)/pass4.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 pass5.o: $(srcdir)/pass5.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -380,7 +380,7 @@ pass5.o: $(srcdir)/pass5.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/jfs_user.h $(srcdir)/e2fsck.h \
@@ -394,7 +394,7 @@ journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(srcdir)/problem.h
 recovery.o: $(srcdir)/recovery.c $(srcdir)/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -408,7 +408,7 @@ recovery.o: $(srcdir)/recovery.c $(srcdir)/jfs_user.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
 revoke.o: $(srcdir)/revoke.c $(srcdir)/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -422,7 +422,7 @@ revoke.o: $(srcdir)/revoke.c $(srcdir)/jfs_user.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
 badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
@@ -436,7 +436,7 @@ badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -449,7 +449,7 @@ util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 unix.o: $(srcdir)/unix.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/e2p/e2p.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -463,7 +463,7 @@ unix.o: $(srcdir)/unix.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h $(srcdir)/jfs_user.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/version.h
 dirinfo.o: $(srcdir)/dirinfo.c $(top_builddir)/lib/config.h \
@@ -478,7 +478,7 @@ dirinfo.o: $(srcdir)/dirinfo.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/tdb.h
 dx_dirinfo.o: $(srcdir)/dx_dirinfo.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -492,7 +492,7 @@ dx_dirinfo.o: $(srcdir)/dx_dirinfo.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 ehandler.o: $(srcdir)/ehandler.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -505,7 +505,7 @@ ehandler.o: $(srcdir)/ehandler.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 problem.o: $(srcdir)/problem.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -518,7 +518,7 @@ problem.o: $(srcdir)/problem.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h $(srcdir)/problemP.h
 message.o: $(srcdir)/message.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/support/quotaio.h \
@@ -531,7 +531,7 @@ message.o: $(srcdir)/message.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/support/profile.h $(top_builddir)/lib/support/prof_err.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 ea_refcount.o: $(srcdir)/ea_refcount.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -545,7 +545,7 @@ ea_refcount.o: $(srcdir)/ea_refcount.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 rehash.o: $(srcdir)/rehash.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -558,7 +558,7 @@ rehash.o: $(srcdir)/rehash.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h $(top_srcdir)/lib/support/sort_r.h
 readahead.o: $(srcdir)/readahead.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -572,7 +572,7 @@ readahead.o: $(srcdir)/readahead.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 region.o: $(srcdir)/region.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -585,7 +585,7 @@ region.o: $(srcdir)/region.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 sigcatcher.o: $(srcdir)/sigcatcher.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -598,7 +598,7 @@ sigcatcher.o: $(srcdir)/sigcatcher.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 logfile.o: $(srcdir)/logfile.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -611,7 +611,7 @@ logfile.o: $(srcdir)/logfile.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 quota.o: $(srcdir)/quota.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
@@ -624,7 +624,7 @@ quota.o: $(srcdir)/quota.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 extents.o: $(srcdir)/extents.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -638,7 +638,7 @@ extents.o: $(srcdir)/extents.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h
 encrypted_files.o: $(srcdir)/encrypted_files.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2fsck.h \
@@ -652,5 +652,5 @@ encrypted_files.o: $(srcdir)/encrypted_files.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(srcdir)/problem.h $(top_srcdir)/lib/ext2fs/rbtree.h
diff --git a/fuse4fs/Makefile.in b/fuse4fs/Makefile.in
index bc137a765ee2b7..6b41d1dd5ffe8d 100644
--- a/fuse4fs/Makefile.in
+++ b/fuse4fs/Makefile.in
@@ -160,7 +160,7 @@ journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
 revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -174,7 +174,7 @@ revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
 recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -188,5 +188,5 @@ recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/lib/e2p/Makefile.in b/lib/e2p/Makefile.in
index 92d9c018fe46c8..f642f5ec367c93 100644
--- a/lib/e2p/Makefile.in
+++ b/lib/e2p/Makefile.in
@@ -130,7 +130,7 @@ feature.o: $(srcdir)/feature.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h
 fgetflags.o: $(srcdir)/fgetflags.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2p.h \
@@ -173,7 +173,7 @@ ljs.o: $(srcdir)/ljs.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/e2p.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h
 mntopts.o: $(srcdir)/mntopts.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/e2p.h \
  $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h
diff --git a/lib/ext2fs/Makefile.in b/lib/ext2fs/Makefile.in
index e9a6ced244ea26..1d0991defff804 100644
--- a/lib/ext2fs/Makefile.in
+++ b/lib/ext2fs/Makefile.in
@@ -1032,7 +1032,7 @@ mkjournal.o: $(srcdir)/mkjournal.c $(top_builddir)/lib/config.h \
  $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h $(srcdir)/ext2_io.h \
  $(top_builddir)/lib/ext2fs/ext2_err.h $(srcdir)/ext2_ext_attr.h \
  $(srcdir)/hashmap.h $(srcdir)/bitops.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h
 mmp.o: $(srcdir)/mmp.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/ext2_fs.h \
  $(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/ext2fs.h \
@@ -1263,7 +1263,7 @@ debugfs.o: $(top_srcdir)/debugfs/debugfs.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/debugfs/../version.h \
  $(srcdir)/../../e2fsck/jfs_user.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h \
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h \
  $(top_srcdir)/lib/support/plausible.h
 util.o: $(top_srcdir)/debugfs/util.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ss/ss.h \
@@ -1353,7 +1353,7 @@ logdump.o: $(top_srcdir)/debugfs/logdump.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/debugfs/../misc/create_inode.h $(top_srcdir)/lib/e2p/e2p.h \
  $(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/../../e2fsck/jfs_user.h \
- $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h \
+ $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h \
  $(srcdir)/compiler.h $(srcdir)/fast_commit.h
 htree.o: $(top_srcdir)/debugfs/htree.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/debugfs/debugfs.h \
@@ -1469,14 +1469,14 @@ journal.o: $(top_srcdir)/debugfs/journal.c $(top_builddir)/lib/config.h \
  $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h $(srcdir)/ext2_io.h \
  $(top_builddir)/lib/ext2fs/ext2_err.h $(srcdir)/ext2_ext_attr.h \
  $(srcdir)/hashmap.h $(srcdir)/bitops.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h
 revoke.o: $(top_srcdir)/e2fsck/revoke.c $(top_srcdir)/e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
  $(srcdir)/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
  $(srcdir)/ext2fs.h $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
  $(srcdir)/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(srcdir)/ext2_ext_attr.h $(srcdir)/hashmap.h $(srcdir)/bitops.h \
- $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h \
+ $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h \
  $(srcdir)/compiler.h
 recovery.o: $(top_srcdir)/e2fsck/recovery.c $(top_srcdir)/e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -1484,7 +1484,7 @@ recovery.o: $(top_srcdir)/e2fsck/recovery.c $(top_srcdir)/e2fsck/jfs_user.h \
  $(srcdir)/ext2fs.h $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
  $(srcdir)/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(srcdir)/ext2_ext_attr.h $(srcdir)/hashmap.h $(srcdir)/bitops.h \
- $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h \
+ $(srcdir)/kernel-jbd.h $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h \
  $(srcdir)/compiler.h
 do_journal.o: $(top_srcdir)/debugfs/do_journal.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/debugfs/debugfs.h \
@@ -1497,7 +1497,7 @@ do_journal.o: $(top_srcdir)/debugfs/do_journal.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/debugfs/../misc/create_inode.h $(top_srcdir)/lib/e2p/e2p.h \
  $(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/kernel-jbd.h \
- $(srcdir)/jfs_compat.h $(srcdir)/kernel-list.h $(srcdir)/compiler.h \
+ $(srcdir)/jfs_compat.h $(srcdir)/../support/list.h $(srcdir)/compiler.h \
  $(top_srcdir)/debugfs/journal.h $(srcdir)/../../e2fsck/jfs_user.h
 do_orphan.o: $(top_srcdir)/debugfs/do_orphan.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(top_srcdir)/debugfs/debugfs.h \
diff --git a/misc/Makefile.in b/misc/Makefile.in
index b63a0424b19fec..ec964688acd623 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -736,7 +736,7 @@ tune2fs.o: $(srcdir)/tune2fs.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/plausible.h \
  $(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/lib/support/devname.h \
@@ -789,7 +789,7 @@ dumpe2fs.o: $(srcdir)/dumpe2fs.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/e2p/e2p.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/support/devname.h $(top_srcdir)/lib/support/nls-enable.h \
  $(top_srcdir)/lib/support/plausible.h $(top_srcdir)/version.h
 badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
@@ -812,7 +812,7 @@ util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
- $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/support/list.h \
  $(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/nls-enable.h \
  $(top_srcdir)/lib/support/devname.h $(srcdir)/util.h
 uuidgen.o: $(srcdir)/uuidgen.c $(top_builddir)/lib/config.h \
@@ -907,7 +907,7 @@ journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
 revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -921,7 +921,7 @@ revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
 recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
@@ -935,5 +935,5 @@ recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
  $(top_srcdir)/lib/support/dqblk_v2.h \
  $(top_srcdir)/lib/support/quotaio_tree.h \
  $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
- $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/ext2fs/compiler.h \
  $(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/misc/tune2fs.c b/misc/tune2fs.c
index 3db57632c88d42..ac440176351e83 100644
--- a/misc/tune2fs.c
+++ b/misc/tune2fs.c
@@ -2857,10 +2857,6 @@ static int expand_inode_table(ext2_filsys fs, unsigned long new_ino_size)
 }
 
 
-#define list_for_each_safe(pos, pnext, head) \
-	for (pos = (head)->next, pnext = pos->next; pos != (head); \
-	     pos = pnext, pnext = pos->next)
-
 static void free_blk_move_list(void)
 {
 	struct list_head *entry, *tmp;


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

* [PATCH 07/23] libsupport: add a cache
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (5 preceding siblings ...)
  2025-11-06 22:44   ` [PATCH 06/23] libsupport: port the kernel list.h to libsupport Darrick J. Wong
@ 2025-11-06 22:44   ` Darrick J. Wong
  2025-11-06 22:45   ` [PATCH 08/23] cache: disable debugging Darrick J. Wong
                     ` (15 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:44 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Reuse the cache code from xfsprogs.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h     |  139 +++++++++
 lib/support/list.h      |    7 
 lib/support/xbitops.h   |  128 ++++++++
 lib/support/Makefile.in |    8 -
 lib/support/cache.c     |  739 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1019 insertions(+), 2 deletions(-)
 create mode 100644 lib/support/cache.h
 create mode 100644 lib/support/xbitops.h
 create mode 100644 lib/support/cache.c


diff --git a/lib/support/cache.h b/lib/support/cache.h
new file mode 100644
index 00000000000000..16b17a9b7a1a51
--- /dev/null
+++ b/lib/support/cache.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2006 Silicon Graphics, Inc.
+ * All Rights Reserved.
+ */
+#ifndef __CACHE_H__
+#define __CACHE_H__
+
+/*
+ * initialisation flags
+ */
+/*
+ * xfs_db always writes changes immediately, and so we need to purge buffers
+ * when we get a buffer lookup mismatch due to reading the same block with a
+ * different buffer configuration.
+ */
+#define CACHE_MISCOMPARE_PURGE	(1 << 0)
+
+/*
+ * cache object campare return values
+ */
+enum {
+	CACHE_HIT,
+	CACHE_MISS,
+	CACHE_PURGE,
+};
+
+#define	HASH_CACHE_RATIO	8
+
+/*
+ * Cache priorities range from BASE to MAX.
+ *
+ * For prefetch support, the top half of the range starts at
+ * CACHE_PREFETCH_PRIORITY and everytime the buffer is fetched and is at or
+ * above this priority level, it is reduced to below this level (refer to
+ * libxfs_buf_get).
+ *
+ * If we have dirty nodes, we can't recycle them until they've been cleaned. To
+ * keep these out of the reclaimable lists (as there can be lots of them) give
+ * them their own priority that the shaker doesn't attempt to walk.
+ */
+
+#define CACHE_BASE_PRIORITY	0
+#define CACHE_PREFETCH_PRIORITY	8
+#define CACHE_MAX_PRIORITY	15
+#define CACHE_DIRTY_PRIORITY	(CACHE_MAX_PRIORITY + 1)
+#define CACHE_NR_PRIORITIES	CACHE_DIRTY_PRIORITY
+
+/*
+ * Simple, generic implementation of a cache (arbitrary data).
+ * Provides a hash table with a capped number of cache entries.
+ */
+
+struct cache;
+struct cache_node;
+
+typedef void *cache_key_t;
+
+typedef void (*cache_walk_t)(struct cache_node *);
+typedef struct cache_node * (*cache_node_alloc_t)(cache_key_t);
+typedef int (*cache_node_flush_t)(struct cache_node *);
+typedef void (*cache_node_relse_t)(struct cache_node *);
+typedef unsigned int (*cache_node_hash_t)(cache_key_t, unsigned int,
+					  unsigned int);
+typedef int (*cache_node_compare_t)(struct cache_node *, cache_key_t);
+typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
+typedef int (*cache_node_get_t)(struct cache_node *);
+typedef void (*cache_node_put_t)(struct cache_node *);
+
+struct cache_operations {
+	cache_node_hash_t	hash;
+	cache_node_alloc_t	alloc;
+	cache_node_flush_t	flush;
+	cache_node_relse_t	relse;
+	cache_node_compare_t	compare;
+	cache_bulk_relse_t	bulkrelse;	/* optional */
+	cache_node_get_t	get;		/* optional */
+	cache_node_put_t	put;		/* optional */
+};
+
+struct cache_hash {
+	struct list_head	ch_list;	/* hash chain head */
+	unsigned int		ch_count;	/* hash chain length */
+	pthread_mutex_t		ch_mutex;	/* hash chain mutex */
+};
+
+struct cache_mru {
+	struct list_head	cm_list;	/* MRU head */
+	unsigned int		cm_count;	/* MRU length */
+	pthread_mutex_t		cm_mutex;	/* MRU lock */
+};
+
+struct cache_node {
+	struct list_head	cn_hash;	/* hash chain */
+	struct list_head	cn_mru;		/* MRU chain */
+	unsigned int		cn_count;	/* reference count */
+	unsigned int		cn_hashidx;	/* hash chain index */
+	int			cn_priority;	/* priority, -1 = free list */
+	int			cn_old_priority;/* saved pre-dirty prio */
+	pthread_mutex_t		cn_mutex;	/* node mutex */
+};
+
+struct cache {
+	int			c_flags;	/* behavioural flags */
+	unsigned int		c_maxcount;	/* max cache nodes */
+	unsigned int		c_count;	/* count of nodes */
+	pthread_mutex_t		c_mutex;	/* node count mutex */
+	cache_node_hash_t	hash;		/* node hash function */
+	cache_node_alloc_t	alloc;		/* allocation function */
+	cache_node_flush_t	flush;		/* flush dirty data function */
+	cache_node_relse_t	relse;		/* memory free function */
+	cache_node_compare_t	compare;	/* comparison routine */
+	cache_bulk_relse_t	bulkrelse;	/* bulk release routine */
+	cache_node_get_t	get;		/* prepare cache node after get */
+	cache_node_put_t	put;		/* prepare to put cache node */
+	unsigned int		c_hashsize;	/* hash bucket count */
+	unsigned int		c_hashshift;	/* hash key shift */
+	struct cache_hash	*c_hash;	/* hash table buckets */
+	struct cache_mru	c_mrus[CACHE_DIRTY_PRIORITY + 1];
+	unsigned long long	c_misses;	/* cache misses */
+	unsigned long long	c_hits;		/* cache hits */
+	unsigned int 		c_max;		/* max nodes ever used */
+};
+
+struct cache *cache_init(int, unsigned int, const struct cache_operations *);
+void cache_destroy(struct cache *);
+void cache_walk(struct cache *, cache_walk_t);
+void cache_purge(struct cache *);
+void cache_flush(struct cache *);
+
+int cache_node_get(struct cache *, cache_key_t, struct cache_node **);
+void cache_node_put(struct cache *, struct cache_node *);
+void cache_node_set_priority(struct cache *, struct cache_node *, int);
+int cache_node_get_priority(struct cache_node *);
+int cache_node_purge(struct cache *, cache_key_t, struct cache_node *);
+void cache_report(FILE *fp, const char *, struct cache *);
+int cache_overflowed(struct cache *);
+
+#endif	/* __CACHE_H__ */
diff --git a/lib/support/list.h b/lib/support/list.h
index df6c99708e4a8e..0e00e446dd7214 100644
--- a/lib/support/list.h
+++ b/lib/support/list.h
@@ -17,6 +17,13 @@ struct list_head {
 	((type *)((char *)(ptr) - offsetof(type, member)))
 #endif
 
+static inline void list_head_destroy(struct list_head *list)
+{
+	list->next = list->prev = NULL;
+}
+
+#define list_head_init(list) INIT_LIST_HEAD(list)
+
 /*
  * Circular doubly linked list implementation.
  *
diff --git a/lib/support/xbitops.h b/lib/support/xbitops.h
new file mode 100644
index 00000000000000..78a8f2a8545f4c
--- /dev/null
+++ b/lib/support/xbitops.h
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef __BITOPS_H__
+#define __BITOPS_H__
+
+/*
+ * fls: find last bit set.
+ */
+
+static inline int fls(int x)
+{
+	int r = 32;
+
+	if (!x)
+		return 0;
+	if (!(x & 0xffff0000u)) {
+		x = (x & 0xffffu) << 16;
+		r -= 16;
+	}
+	if (!(x & 0xff000000u)) {
+		x = (x & 0xffffffu) << 8;
+		r -= 8;
+	}
+	if (!(x & 0xf0000000u)) {
+		x = (x & 0xfffffffu) << 4;
+		r -= 4;
+	}
+	if (!(x & 0xc0000000u)) {
+		x = (x & 0x3fffffffu) << 2;
+		r -= 2;
+	}
+	if (!(x & 0x80000000u)) {
+		r -= 1;
+	}
+	return r;
+}
+
+static inline int fls64(uint64_t x)
+{
+	uint32_t h = x >> 32;
+	if (h)
+		return fls(h) + 32;
+	return fls(x);
+}
+
+static inline unsigned fls_long(unsigned long l)
+{
+        if (sizeof(l) == 4)
+                return fls(l);
+        return fls64(l);
+}
+
+/*
+ * ffz: find first zero bit.
+ * Result is undefined if no zero bit exists.
+ */
+#define ffz(x)	ffs(~(x))
+
+/*
+ * XFS bit manipulation routines.  Repeated here so that some programs
+ * don't have to link in all of libxfs just to have bit manipulation.
+ */
+
+/*
+ * masks with n high/low bits set, 64-bit values
+ */
+static inline uint64_t mask64hi(int n)
+{
+	return (uint64_t)-1 << (64 - (n));
+}
+static inline uint32_t mask32lo(int n)
+{
+	return ((uint32_t)1 << (n)) - 1;
+}
+static inline uint64_t mask64lo(int n)
+{
+	return ((uint64_t)1 << (n)) - 1;
+}
+
+/* Get high bit set out of 32-bit argument, -1 if none set */
+static inline int highbit32(uint32_t v)
+{
+	return fls(v) - 1;
+}
+
+/* Get high bit set out of 64-bit argument, -1 if none set */
+static inline int highbit64(uint64_t v)
+{
+	return fls64(v) - 1;
+}
+
+/* Get low bit set out of 32-bit argument, -1 if none set */
+static inline int lowbit32(uint32_t v)
+{
+	return ffs(v) - 1;
+}
+
+/* Get low bit set out of 64-bit argument, -1 if none set */
+static inline int lowbit64(uint64_t v)
+{
+	uint32_t	w = (uint32_t)v;
+	int		n = 0;
+
+	if (w) {	/* lower bits */
+		n = ffs(w);
+	} else {	/* upper bits */
+		w = (uint32_t)(v >> 32);
+		if (w) {
+			n = ffs(w);
+			if (n)
+				n += 32;
+		}
+	}
+	return n - 1;
+}
+
+/**
+ * __rounddown_pow_of_two() - round down to nearest power of two
+ * @n: value to round down
+ */
+static inline __attribute__((const))
+unsigned long __rounddown_pow_of_two(unsigned long n)
+{
+	return 1UL << (fls_long(n) - 1);
+}
+
+#define rounddown_pow_of_two(n) __rounddown_pow_of_two(n)
+
+#endif
diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
index 6383816fd99cd4..a09814f574008c 100644
--- a/lib/support/Makefile.in
+++ b/lib/support/Makefile.in
@@ -26,7 +26,8 @@ OBJS=		bthread.o \
 		quotaio_v2.o \
 		quotaio_tree.o \
 		dict.o \
-		devname.o
+		devname.o \
+		cache.o
 
 SRCS=		$(srcdir)/argv_parse.c \
 		$(srcdir)/bthread.c \
@@ -42,7 +43,8 @@ SRCS=		$(srcdir)/argv_parse.c \
 		$(srcdir)/quotaio_tree.c \
 		$(srcdir)/quotaio_v2.c \
 		$(srcdir)/dict.c \
-		$(srcdir)/devname.c
+		$(srcdir)/devname.c \
+		$(srcdir)/cache.c
 
 LIBRARY= libsupport
 LIBDIR= support
@@ -187,3 +189,5 @@ dict.o: $(srcdir)/dict.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/dict.h
 devname.o: $(srcdir)/devname.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/devname.h $(srcdir)/nls-enable.h
+cache.o: $(srcdir)/cache.c $(top_builddir)/lib/config.h \
+ $(srcdir)/cache.h $(srcdir)/list.h $(srcdir)/xbitops.h
diff --git a/lib/support/cache.c b/lib/support/cache.c
new file mode 100644
index 00000000000000..fe04f62f262aaa
--- /dev/null
+++ b/lib/support/cache.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2006 Silicon Graphics, Inc.
+ * All Rights Reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "list.h"
+#include "cache.h"
+#include "xbitops.h"
+
+#define CACHE_DEBUG 1
+#undef CACHE_DEBUG
+#define CACHE_DEBUG 1
+#undef CACHE_ABORT
+/* #define CACHE_ABORT 1 */
+
+#define CACHE_SHAKE_COUNT	64
+
+#ifdef CACHE_DEBUG
+# include <assert.h>
+# define ASSERT(x)		assert(x)
+#endif
+
+static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);
+
+struct cache *
+cache_init(
+	int			flags,
+	unsigned int		hashsize,
+	const struct cache_operations	*cache_operations)
+{
+	struct cache *		cache;
+	unsigned int		i, maxcount;
+
+	maxcount = hashsize * HASH_CACHE_RATIO;
+
+	if (!(cache = malloc(sizeof(struct cache))))
+		return NULL;
+	if (!(cache->c_hash = calloc(hashsize, sizeof(struct cache_hash)))) {
+		free(cache);
+		return NULL;
+	}
+
+	cache->c_flags = flags;
+	cache->c_count = 0;
+	cache->c_max = 0;
+	cache->c_hits = 0;
+	cache->c_misses = 0;
+	cache->c_maxcount = maxcount;
+	cache->c_hashsize = hashsize;
+	cache->c_hashshift = fls(hashsize) - 1;
+	cache->hash = cache_operations->hash;
+	cache->alloc = cache_operations->alloc;
+	cache->flush = cache_operations->flush;
+	cache->relse = cache_operations->relse;
+	cache->compare = cache_operations->compare;
+	cache->bulkrelse = cache_operations->bulkrelse ?
+		cache_operations->bulkrelse : cache_generic_bulkrelse;
+	cache->get = cache_operations->get;
+	cache->put = cache_operations->put;
+	pthread_mutex_init(&cache->c_mutex, NULL);
+
+	for (i = 0; i < hashsize; i++) {
+		list_head_init(&cache->c_hash[i].ch_list);
+		cache->c_hash[i].ch_count = 0;
+		pthread_mutex_init(&cache->c_hash[i].ch_mutex, NULL);
+	}
+
+	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
+		list_head_init(&cache->c_mrus[i].cm_list);
+		cache->c_mrus[i].cm_count = 0;
+		pthread_mutex_init(&cache->c_mrus[i].cm_mutex, NULL);
+	}
+	return cache;
+}
+
+static void
+cache_expand(
+	struct cache *		cache)
+{
+	pthread_mutex_lock(&cache->c_mutex);
+#ifdef CACHE_DEBUG
+	fprintf(stderr, "doubling cache size to %d\n", 2 * cache->c_maxcount);
+#endif
+	cache->c_maxcount *= 2;
+	pthread_mutex_unlock(&cache->c_mutex);
+}
+
+void
+cache_walk(
+	struct cache *		cache,
+	cache_walk_t		visit)
+{
+	struct cache_hash *	hash;
+	struct list_head *	head;
+	struct list_head *	pos;
+	unsigned int		i;
+
+	for (i = 0; i < cache->c_hashsize; i++) {
+		hash = &cache->c_hash[i];
+		head = &hash->ch_list;
+		pthread_mutex_lock(&hash->ch_mutex);
+		for (pos = head->next; pos != head; pos = pos->next)
+			visit((struct cache_node *)pos);
+		pthread_mutex_unlock(&hash->ch_mutex);
+	}
+}
+
+#ifdef CACHE_ABORT
+#define cache_abort()	abort()
+#else
+#define cache_abort()	do { } while (0)
+#endif
+
+#ifdef CACHE_DEBUG
+static void
+cache_zero_check(
+	struct cache_node *	node)
+{
+	if (node->cn_count > 0) {
+		fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
+			__FUNCTION__, node->cn_count, node);
+		cache_abort();
+	}
+}
+#define cache_destroy_check(c)	cache_walk((c), cache_zero_check)
+#else
+#define cache_destroy_check(c)	do { } while (0)
+#endif
+
+void
+cache_destroy(
+	struct cache *		cache)
+{
+	unsigned int		i;
+
+	cache_destroy_check(cache);
+	for (i = 0; i < cache->c_hashsize; i++) {
+		list_head_destroy(&cache->c_hash[i].ch_list);
+		pthread_mutex_destroy(&cache->c_hash[i].ch_mutex);
+	}
+	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
+		list_head_destroy(&cache->c_mrus[i].cm_list);
+		pthread_mutex_destroy(&cache->c_mrus[i].cm_mutex);
+	}
+	pthread_mutex_destroy(&cache->c_mutex);
+	free(cache->c_hash);
+	free(cache);
+}
+
+static unsigned int
+cache_generic_bulkrelse(
+	struct cache *		cache,
+	struct list_head *	list)
+{
+	struct cache_node *	node;
+	unsigned int		count = 0;
+
+	while (!list_empty(list)) {
+		node = list_entry(list->next, struct cache_node, cn_mru);
+		pthread_mutex_destroy(&node->cn_mutex);
+		list_del_init(&node->cn_mru);
+		cache->relse(node);
+		count++;
+	}
+
+	return count;
+}
+
+/*
+ * Park unflushable nodes on their own special MRU so that cache_shake() doesn't
+ * end up repeatedly scanning them in the futile attempt to clean them before
+ * reclaim.
+ */
+static void
+cache_add_to_dirty_mru(
+	struct cache		*cache,
+	struct cache_node	*node)
+{
+	struct cache_mru	*mru = &cache->c_mrus[CACHE_DIRTY_PRIORITY];
+
+	pthread_mutex_lock(&mru->cm_mutex);
+	node->cn_old_priority = node->cn_priority;
+	node->cn_priority = CACHE_DIRTY_PRIORITY;
+	list_add(&node->cn_mru, &mru->cm_list);
+	mru->cm_count++;
+	pthread_mutex_unlock(&mru->cm_mutex);
+}
+
+/*
+ * We've hit the limit on cache size, so we need to start reclaiming nodes we've
+ * used. The MRU specified by the priority is shaken.  Returns new priority at
+ * end of the call (in case we call again). We are not allowed to reclaim dirty
+ * objects, so we have to flush them first. If flushing fails, we move them to
+ * the "dirty, unreclaimable" list.
+ *
+ * Hence we skip priorities > CACHE_MAX_PRIORITY unless "purge" is set as we
+ * park unflushable (and hence unreclaimable) buffers at these priorities.
+ * Trying to shake unreclaimable buffer lists when there is memory pressure is a
+ * waste of time and CPU and greatly slows down cache node recycling operations.
+ * Hence we only try to free them if we are being asked to purge the cache of
+ * all entries.
+ */
+static unsigned int
+cache_shake(
+	struct cache *		cache,
+	unsigned int		priority,
+	bool			purge)
+{
+	struct cache_mru	*mru;
+	struct cache_hash *	hash;
+	struct list_head	temp;
+	struct list_head *	head;
+	struct list_head *	pos;
+	struct list_head *	n;
+	struct cache_node *	node;
+	unsigned int		count;
+
+	ASSERT(priority <= CACHE_DIRTY_PRIORITY);
+	if (priority > CACHE_MAX_PRIORITY && !purge)
+		priority = 0;
+
+	mru = &cache->c_mrus[priority];
+	count = 0;
+	list_head_init(&temp);
+	head = &mru->cm_list;
+
+	pthread_mutex_lock(&mru->cm_mutex);
+	for (pos = head->prev, n = pos->prev; pos != head;
+						pos = n, n = pos->prev) {
+		node = list_entry(pos, struct cache_node, cn_mru);
+
+		if (pthread_mutex_trylock(&node->cn_mutex) != 0)
+			continue;
+
+		/* memory pressure is not allowed to release dirty objects */
+		if (cache->flush(node) && !purge) {
+			list_del(&node->cn_mru);
+			mru->cm_count--;
+			node->cn_priority = -1;
+			pthread_mutex_unlock(&node->cn_mutex);
+			cache_add_to_dirty_mru(cache, node);
+			continue;
+		}
+
+		hash = cache->c_hash + node->cn_hashidx;
+		if (pthread_mutex_trylock(&hash->ch_mutex) != 0) {
+			pthread_mutex_unlock(&node->cn_mutex);
+			continue;
+		}
+		ASSERT(node->cn_count == 0);
+		ASSERT(node->cn_priority == priority);
+		node->cn_priority = -1;
+
+		list_move(&node->cn_mru, &temp);
+		list_del_init(&node->cn_hash);
+		hash->ch_count--;
+		mru->cm_count--;
+		pthread_mutex_unlock(&hash->ch_mutex);
+		pthread_mutex_unlock(&node->cn_mutex);
+
+		count++;
+		if (!purge && count == CACHE_SHAKE_COUNT)
+			break;
+	}
+	pthread_mutex_unlock(&mru->cm_mutex);
+
+	if (count > 0) {
+		cache->bulkrelse(cache, &temp);
+
+		pthread_mutex_lock(&cache->c_mutex);
+		cache->c_count -= count;
+		pthread_mutex_unlock(&cache->c_mutex);
+	}
+
+	return (count == CACHE_SHAKE_COUNT) ? priority : ++priority;
+}
+
+/*
+ * Allocate a new hash node (updating atomic counter in the process),
+ * unless doing so will push us over the maximum cache size.
+ */
+static struct cache_node *
+cache_node_allocate(
+	struct cache *		cache,
+	cache_key_t		key)
+{
+	unsigned int		nodesfree;
+	struct cache_node *	node;
+
+	pthread_mutex_lock(&cache->c_mutex);
+	nodesfree = (cache->c_count < cache->c_maxcount);
+	if (nodesfree) {
+		cache->c_count++;
+		if (cache->c_count > cache->c_max)
+			cache->c_max = cache->c_count;
+	}
+	cache->c_misses++;
+	pthread_mutex_unlock(&cache->c_mutex);
+	if (!nodesfree)
+		return NULL;
+	node = cache->alloc(key);
+	if (node == NULL) {	/* uh-oh */
+		pthread_mutex_lock(&cache->c_mutex);
+		cache->c_count--;
+		pthread_mutex_unlock(&cache->c_mutex);
+		return NULL;
+	}
+	pthread_mutex_init(&node->cn_mutex, NULL);
+	list_head_init(&node->cn_mru);
+	node->cn_count = 1;
+	node->cn_priority = 0;
+	node->cn_old_priority = -1;
+	return node;
+}
+
+int
+cache_overflowed(
+	struct cache *		cache)
+{
+	return cache->c_maxcount == cache->c_max;
+}
+
+
+static int
+__cache_node_purge(
+	struct cache *		cache,
+	struct cache_node *	node)
+{
+	int			count;
+	struct cache_mru *	mru;
+
+	pthread_mutex_lock(&node->cn_mutex);
+	count = node->cn_count;
+	if (count != 0) {
+		pthread_mutex_unlock(&node->cn_mutex);
+		return count;
+	}
+
+	/* can't purge dirty objects */
+	if (cache->flush(node)) {
+		pthread_mutex_unlock(&node->cn_mutex);
+		return 1;
+	}
+
+	mru = &cache->c_mrus[node->cn_priority];
+	pthread_mutex_lock(&mru->cm_mutex);
+	list_del_init(&node->cn_mru);
+	mru->cm_count--;
+	pthread_mutex_unlock(&mru->cm_mutex);
+
+	pthread_mutex_unlock(&node->cn_mutex);
+	pthread_mutex_destroy(&node->cn_mutex);
+	list_del_init(&node->cn_hash);
+	cache->relse(node);
+	return 0;
+}
+
+/*
+ * Lookup in the cache hash table.  With any luck we'll get a cache
+ * hit, in which case this will all be over quickly and painlessly.
+ * Otherwise, we allocate a new node, taking care not to expand the
+ * cache beyond the requested maximum size (shrink it if it would).
+ * Returns one if hit in cache, otherwise zero.  A node is _always_
+ * returned, however.
+ */
+int
+cache_node_get(
+	struct cache *		cache,
+	cache_key_t		key,
+	struct cache_node **	nodep)
+{
+	struct cache_node *	node = NULL;
+	struct cache_hash *	hash;
+	struct cache_mru *	mru;
+	struct list_head *	head;
+	struct list_head *	pos;
+	struct list_head *	n;
+	unsigned int		hashidx;
+	int			priority = 0;
+	int			purged = 0;
+
+	hashidx = cache->hash(key, cache->c_hashsize, cache->c_hashshift);
+	hash = cache->c_hash + hashidx;
+	head = &hash->ch_list;
+
+	for (;;) {
+		pthread_mutex_lock(&hash->ch_mutex);
+		for (pos = head->next, n = pos->next; pos != head;
+						pos = n, n = pos->next) {
+			int result;
+
+			node = list_entry(pos, struct cache_node, cn_hash);
+			result = cache->compare(node, key);
+			switch (result) {
+			case CACHE_HIT:
+				break;
+			case CACHE_PURGE:
+				if ((cache->c_flags & CACHE_MISCOMPARE_PURGE) &&
+				    !__cache_node_purge(cache, node)) {
+					purged++;
+					hash->ch_count--;
+				}
+				/* FALL THROUGH */
+			case CACHE_MISS:
+				goto next_object;
+			}
+
+			/*
+			 * node found, bump node's reference count, remove it
+			 * from its MRU list, and update stats.
+			 */
+			pthread_mutex_lock(&node->cn_mutex);
+
+			if (node->cn_count == 0 && cache->get) {
+				int err = cache->get(node);
+				if (err) {
+					pthread_mutex_unlock(&node->cn_mutex);
+					goto next_object;
+				}
+			}
+			if (node->cn_count == 0) {
+				ASSERT(node->cn_priority >= 0);
+				ASSERT(!list_empty(&node->cn_mru));
+				mru = &cache->c_mrus[node->cn_priority];
+				pthread_mutex_lock(&mru->cm_mutex);
+				mru->cm_count--;
+				list_del_init(&node->cn_mru);
+				pthread_mutex_unlock(&mru->cm_mutex);
+				if (node->cn_old_priority != -1) {
+					ASSERT(node->cn_priority ==
+							CACHE_DIRTY_PRIORITY);
+					node->cn_priority = node->cn_old_priority;
+					node->cn_old_priority = -1;
+				}
+			}
+			node->cn_count++;
+
+			pthread_mutex_unlock(&node->cn_mutex);
+			pthread_mutex_unlock(&hash->ch_mutex);
+
+			pthread_mutex_lock(&cache->c_mutex);
+			cache->c_hits++;
+			pthread_mutex_unlock(&cache->c_mutex);
+
+			*nodep = node;
+			return 0;
+next_object:
+			continue;	/* what the hell, gcc? */
+		}
+		pthread_mutex_unlock(&hash->ch_mutex);
+		/*
+		 * not found, allocate a new entry
+		 */
+		node = cache_node_allocate(cache, key);
+		if (node)
+			break;
+		priority = cache_shake(cache, priority, false);
+		/*
+		 * We start at 0; if we free CACHE_SHAKE_COUNT we get
+		 * back the same priority, if not we get back priority+1.
+		 * If we exceed CACHE_MAX_PRIORITY all slots are full; grow it.
+		 */
+		if (priority > CACHE_MAX_PRIORITY) {
+			priority = 0;
+			cache_expand(cache);
+		}
+	}
+
+	node->cn_hashidx = hashidx;
+
+	/* add new node to appropriate hash */
+	pthread_mutex_lock(&hash->ch_mutex);
+	hash->ch_count++;
+	list_add(&node->cn_hash, &hash->ch_list);
+	pthread_mutex_unlock(&hash->ch_mutex);
+
+	if (purged) {
+		pthread_mutex_lock(&cache->c_mutex);
+		cache->c_count -= purged;
+		pthread_mutex_unlock(&cache->c_mutex);
+	}
+
+	*nodep = node;
+	return 1;
+}
+
+void
+cache_node_put(
+	struct cache *		cache,
+	struct cache_node *	node)
+{
+	struct cache_mru *	mru;
+
+	pthread_mutex_lock(&node->cn_mutex);
+#ifdef CACHE_DEBUG
+	if (node->cn_count < 1) {
+		fprintf(stderr, "%s: node put on refcount %u (node=%p)\n",
+				__FUNCTION__, node->cn_count, node);
+		cache_abort();
+	}
+	if (!list_empty(&node->cn_mru)) {
+		fprintf(stderr, "%s: node put on node (%p) in MRU list\n",
+				__FUNCTION__, node);
+		cache_abort();
+	}
+#endif
+	node->cn_count--;
+
+	if (node->cn_count == 0 && cache->put)
+		cache->put(node);
+	if (node->cn_count == 0) {
+		/* add unreferenced node to appropriate MRU for shaker */
+		mru = &cache->c_mrus[node->cn_priority];
+		pthread_mutex_lock(&mru->cm_mutex);
+		mru->cm_count++;
+		list_add(&node->cn_mru, &mru->cm_list);
+		pthread_mutex_unlock(&mru->cm_mutex);
+	}
+
+	pthread_mutex_unlock(&node->cn_mutex);
+}
+
+void
+cache_node_set_priority(
+	struct cache *		cache,
+	struct cache_node *	node,
+	int			priority)
+{
+	if (priority < 0)
+		priority = 0;
+	else if (priority > CACHE_MAX_PRIORITY)
+		priority = CACHE_MAX_PRIORITY;
+
+	pthread_mutex_lock(&node->cn_mutex);
+	ASSERT(node->cn_count > 0);
+	node->cn_priority = priority;
+	node->cn_old_priority = -1;
+	pthread_mutex_unlock(&node->cn_mutex);
+}
+
+int
+cache_node_get_priority(
+	struct cache_node *	node)
+{
+	int			priority;
+
+	pthread_mutex_lock(&node->cn_mutex);
+	priority = node->cn_priority;
+	pthread_mutex_unlock(&node->cn_mutex);
+
+	return priority;
+}
+
+
+/*
+ * Purge a specific node from the cache.  Reference count must be zero.
+ */
+int
+cache_node_purge(
+	struct cache *		cache,
+	cache_key_t		key,
+	struct cache_node *	node)
+{
+	struct list_head *	head;
+	struct list_head *	pos;
+	struct list_head *	n;
+	struct cache_hash *	hash;
+	int			count = -1;
+
+	hash = cache->c_hash + cache->hash(key, cache->c_hashsize,
+					   cache->c_hashshift);
+	head = &hash->ch_list;
+	pthread_mutex_lock(&hash->ch_mutex);
+	for (pos = head->next, n = pos->next; pos != head;
+						pos = n, n = pos->next) {
+		if ((struct cache_node *)pos != node)
+			continue;
+
+		count = __cache_node_purge(cache, node);
+		if (!count)
+			hash->ch_count--;
+		break;
+	}
+	pthread_mutex_unlock(&hash->ch_mutex);
+
+	if (count == 0) {
+		pthread_mutex_lock(&cache->c_mutex);
+		cache->c_count--;
+		pthread_mutex_unlock(&cache->c_mutex);
+	}
+#ifdef CACHE_DEBUG
+	if (count >= 1) {
+		fprintf(stderr, "%s: refcount was %u, not zero (node=%p)\n",
+				__FUNCTION__, count, node);
+		cache_abort();
+	}
+	if (count == -1) {
+		fprintf(stderr, "%s: purge node not found! (node=%p)\n",
+			__FUNCTION__, node);
+		cache_abort();
+	}
+#endif
+	return count == 0;
+}
+
+/*
+ * Purge all nodes from the cache.  All reference counts must be zero.
+ */
+void
+cache_purge(
+	struct cache *		cache)
+{
+	int			i;
+
+	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++)
+		cache_shake(cache, i, true);
+
+#ifdef CACHE_DEBUG
+	if (cache->c_count != 0) {
+		/* flush referenced nodes to disk */
+		cache_flush(cache);
+		fprintf(stderr, "%s: shake on cache %p left %u nodes!?\n",
+				__FUNCTION__, cache, cache->c_count);
+		cache_abort();
+	}
+#endif
+}
+
+/*
+ * Flush all nodes in the cache to disk.
+ */
+void
+cache_flush(
+	struct cache *		cache)
+{
+	struct cache_hash *	hash;
+	struct list_head *	head;
+	struct list_head *	pos;
+	struct cache_node *	node;
+	int			i;
+
+	if (!cache->flush)
+		return;
+
+	for (i = 0; i < cache->c_hashsize; i++) {
+		hash = &cache->c_hash[i];
+
+		pthread_mutex_lock(&hash->ch_mutex);
+		head = &hash->ch_list;
+		for (pos = head->next; pos != head; pos = pos->next) {
+			node = (struct cache_node *)pos;
+			pthread_mutex_lock(&node->cn_mutex);
+			cache->flush(node);
+			pthread_mutex_unlock(&node->cn_mutex);
+		}
+		pthread_mutex_unlock(&hash->ch_mutex);
+	}
+}
+
+#define	HASH_REPORT	(3 * HASH_CACHE_RATIO)
+void
+cache_report(
+	FILE		*fp,
+	const char	*name,
+	struct cache	*cache)
+{
+	int		i;
+	unsigned long	count, index, total;
+	unsigned long	hash_bucket_lengths[HASH_REPORT + 2];
+
+	if ((cache->c_hits + cache->c_misses) == 0)
+		return;
+
+	/* report cache summary */
+	fprintf(fp, "%s: %p\n"
+			"Max supported entries = %u\n"
+			"Max utilized entries = %u\n"
+			"Active entries = %u\n"
+			"Hash table size = %u\n"
+			"Hits = %llu\n"
+			"Misses = %llu\n"
+			"Hit ratio = %5.2f\n",
+			name, cache,
+			cache->c_maxcount,
+			cache->c_max,
+			cache->c_count,
+			cache->c_hashsize,
+			cache->c_hits,
+			cache->c_misses,
+			(double)cache->c_hits * 100 /
+				(cache->c_hits + cache->c_misses)
+	);
+
+	for (i = 0; i <= CACHE_MAX_PRIORITY; i++)
+		fprintf(fp, "MRU %d entries = %6u (%3u%%)\n",
+			i, cache->c_mrus[i].cm_count,
+			cache->c_mrus[i].cm_count * 100 / cache->c_count);
+
+	i = CACHE_DIRTY_PRIORITY;
+	fprintf(fp, "Dirty MRU %d entries = %6u (%3u%%)\n",
+		i, cache->c_mrus[i].cm_count,
+		cache->c_mrus[i].cm_count * 100 / cache->c_count);
+
+	/* report hash bucket lengths */
+	bzero(hash_bucket_lengths, sizeof(hash_bucket_lengths));
+
+	for (i = 0; i < cache->c_hashsize; i++) {
+		count = cache->c_hash[i].ch_count;
+		if (count > HASH_REPORT)
+			index = HASH_REPORT + 1;
+		else
+			index = count;
+		hash_bucket_lengths[index]++;
+	}
+
+	total = 0;
+	for (i = 0; i < HASH_REPORT + 1; i++) {
+		total += i * hash_bucket_lengths[i];
+		if (hash_bucket_lengths[i] == 0)
+			continue;
+		fprintf(fp, "Hash buckets with  %2d entries %6ld (%3ld%%)\n",
+			i, hash_bucket_lengths[i],
+			(i * hash_bucket_lengths[i] * 100) / cache->c_count);
+	}
+	if (hash_bucket_lengths[i])	/* last report bucket is the overflow bucket */
+		fprintf(fp, "Hash buckets with >%2d entries %6ld (%3ld%%)\n",
+			i - 1, hash_bucket_lengths[i],
+			((cache->c_count - total) * 100) / cache->c_count);
+}


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

* [PATCH 08/23] cache: disable debugging
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (6 preceding siblings ...)
  2025-11-06 22:44   ` [PATCH 07/23] libsupport: add a cache Darrick J. Wong
@ 2025-11-06 22:45   ` Darrick J. Wong
  2025-11-06 22:45   ` [PATCH 09/23] cache: use modern list iterator macros Darrick J. Wong
                     ` (14 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Not sure why debugging is turned on by default in the xfsprogs cache
code, but let's turn it off.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.c |    5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)


diff --git a/lib/support/cache.c b/lib/support/cache.c
index fe04f62f262aaa..08e0b484cca298 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -17,9 +17,8 @@
 #include "cache.h"
 #include "xbitops.h"
 
-#define CACHE_DEBUG 1
 #undef CACHE_DEBUG
-#define CACHE_DEBUG 1
+/* #define CACHE_DEBUG 1 */
 #undef CACHE_ABORT
 /* #define CACHE_ABORT 1 */
 
@@ -28,6 +27,8 @@
 #ifdef CACHE_DEBUG
 # include <assert.h>
 # define ASSERT(x)		assert(x)
+#else
+# define ASSERT(x)		do { } while (0)
 #endif
 
 static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);


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

* [PATCH 09/23] cache: use modern list iterator macros
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (7 preceding siblings ...)
  2025-11-06 22:45   ` [PATCH 08/23] cache: disable debugging Darrick J. Wong
@ 2025-11-06 22:45   ` Darrick J. Wong
  2025-11-06 22:45   ` [PATCH 10/23] cache: embed struct cache in the owner Darrick J. Wong
                     ` (13 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Use the list iterator macros from list.h.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.c |   71 +++++++++++++++++----------------------------------
 1 file changed, 24 insertions(+), 47 deletions(-)


diff --git a/lib/support/cache.c b/lib/support/cache.c
index 08e0b484cca298..d8f8231ac36d28 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -98,20 +98,18 @@ cache_expand(
 
 void
 cache_walk(
-	struct cache *		cache,
+	struct cache		*cache,
 	cache_walk_t		visit)
 {
-	struct cache_hash *	hash;
-	struct list_head *	head;
-	struct list_head *	pos;
+	struct cache_hash	*hash;
+	struct cache_node	*pos;
 	unsigned int		i;
 
 	for (i = 0; i < cache->c_hashsize; i++) {
 		hash = &cache->c_hash[i];
-		head = &hash->ch_list;
 		pthread_mutex_lock(&hash->ch_mutex);
-		for (pos = head->next; pos != head; pos = pos->next)
-			visit((struct cache_node *)pos);
+		list_for_each_entry(pos, &hash->ch_list, cn_hash)
+			visit(pos);
 		pthread_mutex_unlock(&hash->ch_mutex);
 	}
 }
@@ -218,12 +216,9 @@ cache_shake(
 	bool			purge)
 {
 	struct cache_mru	*mru;
-	struct cache_hash *	hash;
+	struct cache_hash	*hash;
 	struct list_head	temp;
-	struct list_head *	head;
-	struct list_head *	pos;
-	struct list_head *	n;
-	struct cache_node *	node;
+	struct cache_node	*node, *n;
 	unsigned int		count;
 
 	ASSERT(priority <= CACHE_DIRTY_PRIORITY);
@@ -233,13 +228,9 @@ cache_shake(
 	mru = &cache->c_mrus[priority];
 	count = 0;
 	list_head_init(&temp);
-	head = &mru->cm_list;
 
 	pthread_mutex_lock(&mru->cm_mutex);
-	for (pos = head->prev, n = pos->prev; pos != head;
-						pos = n, n = pos->prev) {
-		node = list_entry(pos, struct cache_node, cn_mru);
-
+	list_for_each_entry_safe_reverse(node, n, &mru->cm_list, cn_mru) {
 		if (pthread_mutex_trylock(&node->cn_mutex) != 0)
 			continue;
 
@@ -376,31 +367,25 @@ __cache_node_purge(
  */
 int
 cache_node_get(
-	struct cache *		cache,
+	struct cache		*cache,
 	cache_key_t		key,
-	struct cache_node **	nodep)
+	struct cache_node	**nodep)
 {
-	struct cache_node *	node = NULL;
-	struct cache_hash *	hash;
-	struct cache_mru *	mru;
-	struct list_head *	head;
-	struct list_head *	pos;
-	struct list_head *	n;
+	struct cache_hash	*hash;
+	struct cache_mru	*mru;
+	struct cache_node	*node = NULL, *n;
 	unsigned int		hashidx;
 	int			priority = 0;
 	int			purged = 0;
 
 	hashidx = cache->hash(key, cache->c_hashsize, cache->c_hashshift);
 	hash = cache->c_hash + hashidx;
-	head = &hash->ch_list;
 
 	for (;;) {
 		pthread_mutex_lock(&hash->ch_mutex);
-		for (pos = head->next, n = pos->next; pos != head;
-						pos = n, n = pos->next) {
+		list_for_each_entry_safe(node, n, &hash->ch_list, cn_hash) {
 			int result;
 
-			node = list_entry(pos, struct cache_node, cn_hash);
 			result = cache->compare(node, key);
 			switch (result) {
 			case CACHE_HIT:
@@ -568,23 +553,19 @@ cache_node_get_priority(
  */
 int
 cache_node_purge(
-	struct cache *		cache,
+	struct cache		*cache,
 	cache_key_t		key,
-	struct cache_node *	node)
+	struct cache_node	*node)
 {
-	struct list_head *	head;
-	struct list_head *	pos;
-	struct list_head *	n;
-	struct cache_hash *	hash;
+	struct cache_node	*pos, *n;
+	struct cache_hash	*hash;
 	int			count = -1;
 
 	hash = cache->c_hash + cache->hash(key, cache->c_hashsize,
 					   cache->c_hashshift);
-	head = &hash->ch_list;
 	pthread_mutex_lock(&hash->ch_mutex);
-	for (pos = head->next, n = pos->next; pos != head;
-						pos = n, n = pos->next) {
-		if ((struct cache_node *)pos != node)
+	list_for_each_entry_safe(pos, n, &hash->ch_list, cn_hash) {
+		if (pos != node)
 			continue;
 
 		count = __cache_node_purge(cache, node);
@@ -642,12 +623,10 @@ cache_purge(
  */
 void
 cache_flush(
-	struct cache *		cache)
+	struct cache		*cache)
 {
-	struct cache_hash *	hash;
-	struct list_head *	head;
-	struct list_head *	pos;
-	struct cache_node *	node;
+	struct cache_hash	*hash;
+	struct cache_node	*node;
 	int			i;
 
 	if (!cache->flush)
@@ -657,9 +636,7 @@ cache_flush(
 		hash = &cache->c_hash[i];
 
 		pthread_mutex_lock(&hash->ch_mutex);
-		head = &hash->ch_list;
-		for (pos = head->next; pos != head; pos = pos->next) {
-			node = (struct cache_node *)pos;
+		list_for_each_entry(node, &hash->ch_list, cn_hash) {
 			pthread_mutex_lock(&node->cn_mutex);
 			cache->flush(node);
 			pthread_mutex_unlock(&node->cn_mutex);


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

* [PATCH 10/23] cache: embed struct cache in the owner
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (8 preceding siblings ...)
  2025-11-06 22:45   ` [PATCH 09/23] cache: use modern list iterator macros Darrick J. Wong
@ 2025-11-06 22:45   ` Darrick J. Wong
  2025-11-06 22:45   ` [PATCH 11/23] cache: pass cache pointer to callbacks Darrick J. Wong
                     ` (12 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

It'll be easier to embed a struct cache into the object that owns the
cache rather than passing pointers around.  This is the prelude to the
next patch, which will enable cache functions to walk back to the owning
struct.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |   10 ++++++++--
 lib/support/cache.c |   38 ++++++++++++++++++++------------------
 2 files changed, 28 insertions(+), 20 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 16b17a9b7a1a51..993f1385dedcee 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -122,8 +122,14 @@ struct cache {
 	unsigned int 		c_max;		/* max nodes ever used */
 };
 
-struct cache *cache_init(int, unsigned int, const struct cache_operations *);
-void cache_destroy(struct cache *);
+static inline bool cache_initialized(const struct cache *cache)
+{
+	return cache->hash != NULL;
+}
+
+int cache_init(int flags, unsigned int size,
+	       const struct cache_operations *ops, struct cache *cache);
+void cache_destroy(struct cache *cache);
 void cache_walk(struct cache *, cache_walk_t);
 void cache_purge(struct cache *);
 void cache_flush(struct cache *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index d8f8231ac36d28..8b4f9f03c3899b 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -12,6 +12,7 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <errno.h>
 
 #include "list.h"
 #include "cache.h"
@@ -33,23 +34,18 @@
 
 static unsigned int cache_generic_bulkrelse(struct cache *, struct list_head *);
 
-struct cache *
+int
 cache_init(
 	int			flags,
 	unsigned int		hashsize,
-	const struct cache_operations	*cache_operations)
+	const struct cache_operations	*cache_operations,
+	struct cache		*cache)
 {
-	struct cache *		cache;
 	unsigned int		i, maxcount;
 
 	maxcount = hashsize * HASH_CACHE_RATIO;
 
-	if (!(cache = malloc(sizeof(struct cache))))
-		return NULL;
-	if (!(cache->c_hash = calloc(hashsize, sizeof(struct cache_hash)))) {
-		free(cache);
-		return NULL;
-	}
+	memset(cache, 0, sizeof(*cache));
 
 	cache->c_flags = flags;
 	cache->c_count = 0;
@@ -57,8 +53,6 @@ cache_init(
 	cache->c_hits = 0;
 	cache->c_misses = 0;
 	cache->c_maxcount = maxcount;
-	cache->c_hashsize = hashsize;
-	cache->c_hashshift = fls(hashsize) - 1;
 	cache->hash = cache_operations->hash;
 	cache->alloc = cache_operations->alloc;
 	cache->flush = cache_operations->flush;
@@ -70,18 +64,26 @@ cache_init(
 	cache->put = cache_operations->put;
 	pthread_mutex_init(&cache->c_mutex, NULL);
 
+	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
+		list_head_init(&cache->c_mrus[i].cm_list);
+		cache->c_mrus[i].cm_count = 0;
+		pthread_mutex_init(&cache->c_mrus[i].cm_mutex, NULL);
+	}
+
+	cache->c_hash = calloc(hashsize, sizeof(struct cache_hash));
+	if (!cache->c_hash)
+		return ENOMEM;
+
+	cache->c_hashsize = hashsize;
+	cache->c_hashshift = fls(hashsize) - 1;
+
 	for (i = 0; i < hashsize; i++) {
 		list_head_init(&cache->c_hash[i].ch_list);
 		cache->c_hash[i].ch_count = 0;
 		pthread_mutex_init(&cache->c_hash[i].ch_mutex, NULL);
 	}
 
-	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
-		list_head_init(&cache->c_mrus[i].cm_list);
-		cache->c_mrus[i].cm_count = 0;
-		pthread_mutex_init(&cache->c_mrus[i].cm_mutex, NULL);
-	}
-	return cache;
+	return 0;
 }
 
 static void
@@ -153,7 +155,7 @@ cache_destroy(
 	}
 	pthread_mutex_destroy(&cache->c_mutex);
 	free(cache->c_hash);
-	free(cache);
+	memset(cache, 0, sizeof(*cache));
 }
 
 static unsigned int


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

* [PATCH 11/23] cache: pass cache pointer to callbacks
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (9 preceding siblings ...)
  2025-11-06 22:45   ` [PATCH 10/23] cache: embed struct cache in the owner Darrick J. Wong
@ 2025-11-06 22:45   ` Darrick J. Wong
  2025-11-06 22:46   ` [PATCH 12/23] cache: pass a private data pointer through cache_walk Darrick J. Wong
                     ` (11 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:45 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Pass the cache pointer to the cache node callbacks so that subsequent
patches don't have to waste memory putting pointers to struct fuse4fs in
the cached objects.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |   12 ++++++------
 lib/support/cache.c |   21 +++++++++++----------
 2 files changed, 17 insertions(+), 16 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 993f1385dedcee..0168fdca027896 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -56,16 +56,16 @@ struct cache_node;
 
 typedef void *cache_key_t;
 
-typedef void (*cache_walk_t)(struct cache_node *);
-typedef struct cache_node * (*cache_node_alloc_t)(cache_key_t);
-typedef int (*cache_node_flush_t)(struct cache_node *);
-typedef void (*cache_node_relse_t)(struct cache_node *);
+typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn);
+typedef struct cache_node * (*cache_node_alloc_t)(struct cache *c, cache_key_t k);
+typedef int (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
+typedef void (*cache_node_relse_t)(struct cache *c, struct cache_node *cn);
 typedef unsigned int (*cache_node_hash_t)(cache_key_t, unsigned int,
 					  unsigned int);
 typedef int (*cache_node_compare_t)(struct cache_node *, cache_key_t);
 typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
-typedef int (*cache_node_get_t)(struct cache_node *);
-typedef void (*cache_node_put_t)(struct cache_node *);
+typedef int (*cache_node_get_t)(struct cache *c, struct cache_node *cn);
+typedef void (*cache_node_put_t)(struct cache *c, struct cache_node *cn);
 
 struct cache_operations {
 	cache_node_hash_t	hash;
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 8b4f9f03c3899b..2e2e36ccc3ef78 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -111,7 +111,7 @@ cache_walk(
 		hash = &cache->c_hash[i];
 		pthread_mutex_lock(&hash->ch_mutex);
 		list_for_each_entry(pos, &hash->ch_list, cn_hash)
-			visit(pos);
+			visit(cache, pos);
 		pthread_mutex_unlock(&hash->ch_mutex);
 	}
 }
@@ -125,7 +125,8 @@ cache_walk(
 #ifdef CACHE_DEBUG
 static void
 cache_zero_check(
-	struct cache_node *	node)
+	struct cache		*cache,
+	struct cache_node	*node)
 {
 	if (node->cn_count > 0) {
 		fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
@@ -170,7 +171,7 @@ cache_generic_bulkrelse(
 		node = list_entry(list->next, struct cache_node, cn_mru);
 		pthread_mutex_destroy(&node->cn_mutex);
 		list_del_init(&node->cn_mru);
-		cache->relse(node);
+		cache->relse(cache, node);
 		count++;
 	}
 
@@ -237,7 +238,7 @@ cache_shake(
 			continue;
 
 		/* memory pressure is not allowed to release dirty objects */
-		if (cache->flush(node) && !purge) {
+		if (cache->flush(cache, node) && !purge) {
 			list_del(&node->cn_mru);
 			mru->cm_count--;
 			node->cn_priority = -1;
@@ -302,7 +303,7 @@ cache_node_allocate(
 	pthread_mutex_unlock(&cache->c_mutex);
 	if (!nodesfree)
 		return NULL;
-	node = cache->alloc(key);
+	node = cache->alloc(cache, key);
 	if (node == NULL) {	/* uh-oh */
 		pthread_mutex_lock(&cache->c_mutex);
 		cache->c_count--;
@@ -341,7 +342,7 @@ __cache_node_purge(
 	}
 
 	/* can't purge dirty objects */
-	if (cache->flush(node)) {
+	if (cache->flush(cache, node)) {
 		pthread_mutex_unlock(&node->cn_mutex);
 		return 1;
 	}
@@ -355,7 +356,7 @@ __cache_node_purge(
 	pthread_mutex_unlock(&node->cn_mutex);
 	pthread_mutex_destroy(&node->cn_mutex);
 	list_del_init(&node->cn_hash);
-	cache->relse(node);
+	cache->relse(cache, node);
 	return 0;
 }
 
@@ -410,7 +411,7 @@ cache_node_get(
 			pthread_mutex_lock(&node->cn_mutex);
 
 			if (node->cn_count == 0 && cache->get) {
-				int err = cache->get(node);
+				int err = cache->get(cache, node);
 				if (err) {
 					pthread_mutex_unlock(&node->cn_mutex);
 					goto next_object;
@@ -505,7 +506,7 @@ cache_node_put(
 	node->cn_count--;
 
 	if (node->cn_count == 0 && cache->put)
-		cache->put(node);
+		cache->put(cache, node);
 	if (node->cn_count == 0) {
 		/* add unreferenced node to appropriate MRU for shaker */
 		mru = &cache->c_mrus[node->cn_priority];
@@ -640,7 +641,7 @@ cache_flush(
 		pthread_mutex_lock(&hash->ch_mutex);
 		list_for_each_entry(node, &hash->ch_list, cn_hash) {
 			pthread_mutex_lock(&node->cn_mutex);
-			cache->flush(node);
+			cache->flush(cache, node);
 			pthread_mutex_unlock(&node->cn_mutex);
 		}
 		pthread_mutex_unlock(&hash->ch_mutex);


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

* [PATCH 12/23] cache: pass a private data pointer through cache_walk
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (10 preceding siblings ...)
  2025-11-06 22:45   ` [PATCH 11/23] cache: pass cache pointer to callbacks Darrick J. Wong
@ 2025-11-06 22:46   ` Darrick J. Wong
  2025-11-06 22:46   ` [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node Darrick J. Wong
                     ` (10 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:46 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Allow cache_walk callers to pass a pointer to the callback function.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    4 ++--
 lib/support/cache.c |   10 ++++++----
 2 files changed, 8 insertions(+), 6 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 0168fdca027896..b18b6d3325e9ad 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -56,7 +56,7 @@ struct cache_node;
 
 typedef void *cache_key_t;
 
-typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn);
+typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn, void *d);
 typedef struct cache_node * (*cache_node_alloc_t)(struct cache *c, cache_key_t k);
 typedef int (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
 typedef void (*cache_node_relse_t)(struct cache *c, struct cache_node *cn);
@@ -130,7 +130,7 @@ static inline bool cache_initialized(const struct cache *cache)
 int cache_init(int flags, unsigned int size,
 	       const struct cache_operations *ops, struct cache *cache);
 void cache_destroy(struct cache *cache);
-void cache_walk(struct cache *, cache_walk_t);
+void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
 void cache_purge(struct cache *);
 void cache_flush(struct cache *);
 
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 2e2e36ccc3ef78..606acd5453cf10 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -101,7 +101,8 @@ cache_expand(
 void
 cache_walk(
 	struct cache		*cache,
-	cache_walk_t		visit)
+	cache_walk_t		visit,
+	void			*data)
 {
 	struct cache_hash	*hash;
 	struct cache_node	*pos;
@@ -111,7 +112,7 @@ cache_walk(
 		hash = &cache->c_hash[i];
 		pthread_mutex_lock(&hash->ch_mutex);
 		list_for_each_entry(pos, &hash->ch_list, cn_hash)
-			visit(cache, pos);
+			visit(cache, pos, data);
 		pthread_mutex_unlock(&hash->ch_mutex);
 	}
 }
@@ -126,7 +127,8 @@ cache_walk(
 static void
 cache_zero_check(
 	struct cache		*cache,
-	struct cache_node	*node)
+	struct cache_node	*node,
+	void			*data)
 {
 	if (node->cn_count > 0) {
 		fprintf(stderr, "%s: refcount is %u, not zero (node=%p)\n",
@@ -134,7 +136,7 @@ cache_zero_check(
 		cache_abort();
 	}
 }
-#define cache_destroy_check(c)	cache_walk((c), cache_zero_check)
+#define cache_destroy_check(c)	cache_walk((c), cache_zero_check, NULL)
 #else
 #define cache_destroy_check(c)	do { } while (0)
 #endif


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

* [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (11 preceding siblings ...)
  2025-11-06 22:46   ` [PATCH 12/23] cache: pass a private data pointer through cache_walk Darrick J. Wong
@ 2025-11-06 22:46   ` Darrick J. Wong
  2025-11-06 22:46   ` [PATCH 14/23] cache: return results of a cache flush Darrick J. Wong
                     ` (9 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:46 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Create a helper to bump the refcount of a cache node.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    1 +
 lib/support/cache.c |   57 +++++++++++++++++++++++++++++----------------------
 2 files changed, 33 insertions(+), 25 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index b18b6d3325e9ad..e8f1c82ef7869c 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -141,5 +141,6 @@ int cache_node_get_priority(struct cache_node *);
 int cache_node_purge(struct cache *, cache_key_t, struct cache_node *);
 void cache_report(FILE *fp, const char *, struct cache *);
 int cache_overflowed(struct cache *);
+struct cache_node *cache_node_grab(struct cache *cache, struct cache_node *node);
 
 #endif	/* __CACHE_H__ */
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 606acd5453cf10..49568ffa6de2e4 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -362,6 +362,35 @@ __cache_node_purge(
 	return 0;
 }
 
+/* Grab a new refcount to the cache node object.  Caller must hold cn_mutex. */
+struct cache_node *cache_node_grab(struct cache *cache, struct cache_node *node)
+{
+	struct cache_mru *mru;
+
+	if (node->cn_count == 0 && cache->get) {
+		int err = cache->get(cache, node);
+		if (err)
+			return NULL;
+	}
+	if (node->cn_count == 0) {
+		ASSERT(node->cn_priority >= 0);
+		ASSERT(!list_empty(&node->cn_mru));
+		mru = &cache->c_mrus[node->cn_priority];
+		pthread_mutex_lock(&mru->cm_mutex);
+		mru->cm_count--;
+		list_del_init(&node->cn_mru);
+		pthread_mutex_unlock(&mru->cm_mutex);
+		if (node->cn_old_priority != -1) {
+			ASSERT(node->cn_priority ==
+					CACHE_DIRTY_PRIORITY);
+			node->cn_priority = node->cn_old_priority;
+			node->cn_old_priority = -1;
+		}
+	}
+	node->cn_count++;
+	return node;
+}
+
 /*
  * Lookup in the cache hash table.  With any luck we'll get a cache
  * hit, in which case this will all be over quickly and painlessly.
@@ -377,7 +406,6 @@ cache_node_get(
 	struct cache_node	**nodep)
 {
 	struct cache_hash	*hash;
-	struct cache_mru	*mru;
 	struct cache_node	*node = NULL, *n;
 	unsigned int		hashidx;
 	int			priority = 0;
@@ -411,31 +439,10 @@ cache_node_get(
 			 * from its MRU list, and update stats.
 			 */
 			pthread_mutex_lock(&node->cn_mutex);
-
-			if (node->cn_count == 0 && cache->get) {
-				int err = cache->get(cache, node);
-				if (err) {
-					pthread_mutex_unlock(&node->cn_mutex);
-					goto next_object;
-				}
+			if (!cache_node_grab(cache, node)) {
+				pthread_mutex_unlock(&node->cn_mutex);
+				goto next_object;
 			}
-			if (node->cn_count == 0) {
-				ASSERT(node->cn_priority >= 0);
-				ASSERT(!list_empty(&node->cn_mru));
-				mru = &cache->c_mrus[node->cn_priority];
-				pthread_mutex_lock(&mru->cm_mutex);
-				mru->cm_count--;
-				list_del_init(&node->cn_mru);
-				pthread_mutex_unlock(&mru->cm_mutex);
-				if (node->cn_old_priority != -1) {
-					ASSERT(node->cn_priority ==
-							CACHE_DIRTY_PRIORITY);
-					node->cn_priority = node->cn_old_priority;
-					node->cn_old_priority = -1;
-				}
-			}
-			node->cn_count++;
-
 			pthread_mutex_unlock(&node->cn_mutex);
 			pthread_mutex_unlock(&hash->ch_mutex);
 


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

* [PATCH 14/23] cache: return results of a cache flush
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (12 preceding siblings ...)
  2025-11-06 22:46   ` [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node Darrick J. Wong
@ 2025-11-06 22:46   ` Darrick J. Wong
  2025-11-06 22:47   ` [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get Darrick J. Wong
                     ` (8 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:46 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Modify cache_flush to return whether or not there were errors whilst
flushing the cache.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    4 ++--
 lib/support/cache.c |   11 +++++++----
 2 files changed, 9 insertions(+), 6 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index e8f1c82ef7869c..8d39ca5c02a285 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -58,7 +58,7 @@ typedef void *cache_key_t;
 
 typedef void (*cache_walk_t)(struct cache *c, struct cache_node *cn, void *d);
 typedef struct cache_node * (*cache_node_alloc_t)(struct cache *c, cache_key_t k);
-typedef int (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
+typedef bool (*cache_node_flush_t)(struct cache *c, struct cache_node *cn);
 typedef void (*cache_node_relse_t)(struct cache *c, struct cache_node *cn);
 typedef unsigned int (*cache_node_hash_t)(cache_key_t, unsigned int,
 					  unsigned int);
@@ -132,7 +132,7 @@ int cache_init(int flags, unsigned int size,
 void cache_destroy(struct cache *cache);
 void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
 void cache_purge(struct cache *);
-void cache_flush(struct cache *);
+bool cache_flush(struct cache *cache);
 
 int cache_node_get(struct cache *, cache_key_t, struct cache_node **);
 void cache_node_put(struct cache *, struct cache_node *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 49568ffa6de2e4..fa07b4ad8222d2 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -631,18 +631,19 @@ cache_purge(
 }
 
 /*
- * Flush all nodes in the cache to disk.
+ * Flush all nodes in the cache to disk.  Returns true if the flush succeeded.
  */
-void
+bool
 cache_flush(
 	struct cache		*cache)
 {
 	struct cache_hash	*hash;
 	struct cache_node	*node;
 	int			i;
+	bool			still_dirty = false;
 
 	if (!cache->flush)
-		return;
+		return true;
 
 	for (i = 0; i < cache->c_hashsize; i++) {
 		hash = &cache->c_hash[i];
@@ -650,11 +651,13 @@ cache_flush(
 		pthread_mutex_lock(&hash->ch_mutex);
 		list_for_each_entry(node, &hash->ch_list, cn_hash) {
 			pthread_mutex_lock(&node->cn_mutex);
-			cache->flush(cache, node);
+			still_dirty |= cache->flush(cache, node);
 			pthread_mutex_unlock(&node->cn_mutex);
 		}
 		pthread_mutex_unlock(&hash->ch_mutex);
 	}
+
+	return !still_dirty;
 }
 
 #define	HASH_REPORT	(3 * HASH_CACHE_RATIO)


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

* [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (13 preceding siblings ...)
  2025-11-06 22:46   ` [PATCH 14/23] cache: return results of a cache flush Darrick J. Wong
@ 2025-11-06 22:47   ` Darrick J. Wong
  2025-11-06 22:47   ` [PATCH 16/23] cache: support gradual expansion Darrick J. Wong
                     ` (7 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Add a new flag to cache_node_get so that callers can specify that they
only want the cache to return an existing cache node, and not create a
new one.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    5 ++++-
 lib/support/cache.c |    7 +++++++
 2 files changed, 11 insertions(+), 1 deletion(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 8d39ca5c02a285..98b2182d49a6e0 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -134,7 +134,10 @@ void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
 void cache_purge(struct cache *);
 bool cache_flush(struct cache *cache);
 
-int cache_node_get(struct cache *, cache_key_t, struct cache_node **);
+/* don't allocate a new node */
+#define CACHE_GET_INCORE	(1U << 0)
+int cache_node_get(struct cache *c, cache_key_t key, unsigned int cgflags,
+		   struct cache_node **nodep);
 void cache_node_put(struct cache *, struct cache_node *);
 void cache_node_set_priority(struct cache *, struct cache_node *, int);
 int cache_node_get_priority(struct cache_node *);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index fa07b4ad8222d2..9da6c59b3b6391 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -403,6 +403,7 @@ int
 cache_node_get(
 	struct cache		*cache,
 	cache_key_t		key,
+	unsigned int		cgflags,
 	struct cache_node	**nodep)
 {
 	struct cache_hash	*hash;
@@ -456,6 +457,12 @@ cache_node_get(
 			continue;	/* what the hell, gcc? */
 		}
 		pthread_mutex_unlock(&hash->ch_mutex);
+
+		if (cgflags & CACHE_GET_INCORE) {
+			*nodep = NULL;
+			return 0;
+		}
+
 		/*
 		 * not found, allocate a new entry
 		 */


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

* [PATCH 16/23] cache: support gradual expansion
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (14 preceding siblings ...)
  2025-11-06 22:47   ` [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get Darrick J. Wong
@ 2025-11-06 22:47   ` Darrick J. Wong
  2025-11-06 22:47   ` [PATCH 17/23] cache: support updating maxcount and flags Darrick J. Wong
                     ` (6 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

It's probably not a good idea to expand the cache size by powers of two
beyond some random limit, so let the users figure that out if they want
to.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |   10 ++++++++++
 lib/support/cache.c |   12 ++++++++++--
 2 files changed, 20 insertions(+), 2 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 98b2182d49a6e0..ae37945c545f46 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -66,6 +66,14 @@ typedef int (*cache_node_compare_t)(struct cache_node *, cache_key_t);
 typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
 typedef int (*cache_node_get_t)(struct cache *c, struct cache_node *cn);
 typedef void (*cache_node_put_t)(struct cache *c, struct cache_node *cn);
+typedef unsigned int (*cache_node_resize_t)(const struct cache *c,
+					    unsigned int curr_size);
+
+static inline unsigned int cache_gradual_resize(const struct cache *cache,
+						unsigned int curr_size)
+{
+	return curr_size * 5 / 4;
+}
 
 struct cache_operations {
 	cache_node_hash_t	hash;
@@ -76,6 +84,7 @@ struct cache_operations {
 	cache_bulk_relse_t	bulkrelse;	/* optional */
 	cache_node_get_t	get;		/* optional */
 	cache_node_put_t	put;		/* optional */
+	cache_node_resize_t	resize;		/* optional */
 };
 
 struct cache_hash {
@@ -113,6 +122,7 @@ struct cache {
 	cache_bulk_relse_t	bulkrelse;	/* bulk release routine */
 	cache_node_get_t	get;		/* prepare cache node after get */
 	cache_node_put_t	put;		/* prepare to put cache node */
+	cache_node_resize_t	resize;		/* compute new maxcount */
 	unsigned int		c_hashsize;	/* hash bucket count */
 	unsigned int		c_hashshift;	/* hash key shift */
 	struct cache_hash	*c_hash;	/* hash table buckets */
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 9da6c59b3b6391..dbaddc1bd36d3d 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -62,6 +62,7 @@ cache_init(
 		cache_operations->bulkrelse : cache_generic_bulkrelse;
 	cache->get = cache_operations->get;
 	cache->put = cache_operations->put;
+	cache->resize = cache_operations->resize;
 	pthread_mutex_init(&cache->c_mutex, NULL);
 
 	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++) {
@@ -90,11 +91,18 @@ static void
 cache_expand(
 	struct cache *		cache)
 {
+	unsigned int		new_size = 0;
+
 	pthread_mutex_lock(&cache->c_mutex);
+	if (cache->resize)
+		new_size = cache->resize(cache, cache->c_maxcount);
+	if (new_size <= cache->c_maxcount)
+		new_size = cache->c_maxcount * 2;
 #ifdef CACHE_DEBUG
-	fprintf(stderr, "doubling cache size to %d\n", 2 * cache->c_maxcount);
+	fprintf(stderr, "increasing cache max size from %u to %u\n",
+			cache->c_maxcount, new_size);
 #endif
-	cache->c_maxcount *= 2;
+	cache->c_maxcount = new_size;
 	pthread_mutex_unlock(&cache->c_mutex);
 }
 


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

* [PATCH 17/23] cache: support updating maxcount and flags
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (15 preceding siblings ...)
  2025-11-06 22:47   ` [PATCH 16/23] cache: support gradual expansion Darrick J. Wong
@ 2025-11-06 22:47   ` Darrick J. Wong
  2025-11-06 22:47   ` [PATCH 18/23] cache: support channging flags Darrick J. Wong
                     ` (5 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

A future patchset will create a new io cache manager that uses the
hashtable in cache.c  It's desirable for fuse4fs to be able to control
the maximum number of buffers in the IO cache, so add a simple API so
that cache users can set a new max item count.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    2 ++
 lib/support/cache.c |   10 ++++++++++
 2 files changed, 12 insertions(+)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index ae37945c545f46..75e61c92a1fd35 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -144,6 +144,8 @@ void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
 void cache_purge(struct cache *);
 bool cache_flush(struct cache *cache);
 
+void cache_set_maxcount(struct cache *cache, unsigned int maxcount);
+
 /* don't allocate a new node */
 #define CACHE_GET_INCORE	(1U << 0)
 int cache_node_get(struct cache *c, cache_key_t key, unsigned int cgflags,
diff --git a/lib/support/cache.c b/lib/support/cache.c
index dbaddc1bd36d3d..e2f9e722eb2ef1 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -87,6 +87,16 @@ cache_init(
 	return 0;
 }
 
+void
+cache_set_maxcount(
+	struct cache		*cache,
+	unsigned int		maxcount)
+{
+	pthread_mutex_lock(&cache->c_mutex);
+	cache->c_maxcount = maxcount;
+	pthread_mutex_unlock(&cache->c_mutex);
+}
+
 static void
 cache_expand(
 	struct cache *		cache)


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

* [PATCH 18/23] cache: support channging flags
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (16 preceding siblings ...)
  2025-11-06 22:47   ` [PATCH 17/23] cache: support updating maxcount and flags Darrick J. Wong
@ 2025-11-06 22:47   ` Darrick J. Wong
  2025-11-06 22:48   ` [PATCH 19/23] cache: implement automatic shrinking Darrick J. Wong
                     ` (4 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:47 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Make it so that we can change the flags in use by a given cache.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    4 ++++
 lib/support/cache.c |   18 ++++++++++++++++++
 2 files changed, 22 insertions(+)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 75e61c92a1fd35..32b99b5fe733e3 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -16,6 +16,8 @@
  */
 #define CACHE_MISCOMPARE_PURGE	(1 << 0)
 
+#define CACHE_FLAGS_ALL		(CACHE_MISCOMPARE_PURGE)
+
 /*
  * cache object campare return values
  */
@@ -145,6 +147,8 @@ void cache_purge(struct cache *);
 bool cache_flush(struct cache *cache);
 
 void cache_set_maxcount(struct cache *cache, unsigned int maxcount);
+int cache_set_flag(struct cache *cache, int flags);
+int cache_clear_flag(struct cache *cache, int flags);
 
 /* don't allocate a new node */
 #define CACHE_GET_INCORE	(1U << 0)
diff --git a/lib/support/cache.c b/lib/support/cache.c
index e2f9e722eb2ef1..99044248b85d38 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -97,6 +97,24 @@ cache_set_maxcount(
 	pthread_mutex_unlock(&cache->c_mutex);
 }
 
+int
+cache_set_flag(
+	struct cache		*cache,
+	int			flags)
+{
+	cache->c_flags |= (flags & CACHE_FLAGS_ALL);
+	return 0;
+}
+
+int
+cache_clear_flag(
+	struct cache		*cache,
+	int			flags)
+{
+	cache->c_flags &= ~flags;
+	return 0;
+}
+
 static void
 cache_expand(
 	struct cache *		cache)


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

* [PATCH 19/23] cache: implement automatic shrinking
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (17 preceding siblings ...)
  2025-11-06 22:47   ` [PATCH 18/23] cache: support channging flags Darrick J. Wong
@ 2025-11-06 22:48   ` Darrick J. Wong
  2025-11-06 22:48   ` [PATCH 20/23] fuse4fs: add cache to track open files Darrick J. Wong
                     ` (3 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Shrink the cache whenever maxcount has been expanded beyond its initial
value, we release a cached object to one of the mru lists and the number
of objects sitting on the mru is enough to drop the cache count down a
level.  This enables a cache to reduce its memory consumption after a
spike in which reclamation wasn't possible.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |   20 +++++++--
 lib/support/cache.c |  119 ++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 129 insertions(+), 10 deletions(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index 32b99b5fe733e3..c7c8298c115d50 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -16,7 +16,11 @@
  */
 #define CACHE_MISCOMPARE_PURGE	(1 << 0)
 
-#define CACHE_FLAGS_ALL		(CACHE_MISCOMPARE_PURGE)
+/* Automatically shrink the cache's max_count when possible. */
+#define CACHE_AUTO_SHRINK	(1 << 1)
+
+#define CACHE_FLAGS_ALL		(CACHE_MISCOMPARE_PURGE | \
+				 CACHE_AUTO_SHRINK)
 
 /*
  * cache object campare return values
@@ -69,12 +73,18 @@ typedef unsigned int (*cache_bulk_relse_t)(struct cache *, struct list_head *);
 typedef int (*cache_node_get_t)(struct cache *c, struct cache_node *cn);
 typedef void (*cache_node_put_t)(struct cache *c, struct cache_node *cn);
 typedef unsigned int (*cache_node_resize_t)(const struct cache *c,
-					    unsigned int curr_size);
+					    unsigned int curr_size,
+					    int dir);
 
 static inline unsigned int cache_gradual_resize(const struct cache *cache,
-						unsigned int curr_size)
+						unsigned int curr_size,
+						int dir)
 {
-	return curr_size * 5 / 4;
+	if (dir < 0)
+		return curr_size * 9 / 10;
+	else if (dir > 0)
+		return curr_size * 5 / 4;
+	return curr_size;
 }
 
 struct cache_operations {
@@ -113,6 +123,7 @@ struct cache_node {
 
 struct cache {
 	int			c_flags;	/* behavioural flags */
+	unsigned int		c_orig_max;	/* original max cache nodes */
 	unsigned int		c_maxcount;	/* max cache nodes */
 	unsigned int		c_count;	/* count of nodes */
 	pthread_mutex_t		c_mutex;	/* node count mutex */
@@ -145,6 +156,7 @@ void cache_destroy(struct cache *cache);
 void cache_walk(struct cache *cache, cache_walk_t fn, void *data);
 void cache_purge(struct cache *);
 bool cache_flush(struct cache *cache);
+void cache_shrink(struct cache *cache);
 
 void cache_set_maxcount(struct cache *cache, unsigned int maxcount);
 int cache_set_flag(struct cache *cache, int flags);
diff --git a/lib/support/cache.c b/lib/support/cache.c
index 99044248b85d38..3a9e276f11af72 100644
--- a/lib/support/cache.c
+++ b/lib/support/cache.c
@@ -53,6 +53,7 @@ cache_init(
 	cache->c_hits = 0;
 	cache->c_misses = 0;
 	cache->c_maxcount = maxcount;
+	cache->c_orig_max = maxcount;
 	cache->hash = cache_operations->hash;
 	cache->alloc = cache_operations->alloc;
 	cache->flush = cache_operations->flush;
@@ -93,6 +94,7 @@ cache_set_maxcount(
 	unsigned int		maxcount)
 {
 	pthread_mutex_lock(&cache->c_mutex);
+	cache->c_orig_max = maxcount;
 	cache->c_maxcount = maxcount;
 	pthread_mutex_unlock(&cache->c_mutex);
 }
@@ -123,7 +125,7 @@ cache_expand(
 
 	pthread_mutex_lock(&cache->c_mutex);
 	if (cache->resize)
-		new_size = cache->resize(cache, cache->c_maxcount);
+		new_size = cache->resize(cache, cache->c_maxcount, 1);
 	if (new_size <= cache->c_maxcount)
 		new_size = cache->c_maxcount * 2;
 #ifdef CACHE_DEBUG
@@ -254,7 +256,8 @@ static unsigned int
 cache_shake(
 	struct cache *		cache,
 	unsigned int		priority,
-	bool			purge)
+	bool			purge,
+	unsigned int		nr_to_shake)
 {
 	struct cache_mru	*mru;
 	struct cache_hash	*hash;
@@ -302,7 +305,7 @@ cache_shake(
 		pthread_mutex_unlock(&node->cn_mutex);
 
 		count++;
-		if (!purge && count == CACHE_SHAKE_COUNT)
+		if (!purge && count == nr_to_shake)
 			break;
 	}
 	pthread_mutex_unlock(&mru->cm_mutex);
@@ -315,7 +318,7 @@ cache_shake(
 		pthread_mutex_unlock(&cache->c_mutex);
 	}
 
-	return (count == CACHE_SHAKE_COUNT) ? priority : ++priority;
+	return (count == nr_to_shake) ? priority : ++priority;
 }
 
 /*
@@ -505,7 +508,7 @@ cache_node_get(
 		node = cache_node_allocate(cache, key);
 		if (node)
 			break;
-		priority = cache_shake(cache, priority, false);
+		priority = cache_shake(cache, priority, false, CACHE_SHAKE_COUNT);
 		/*
 		 * We start at 0; if we free CACHE_SHAKE_COUNT we get
 		 * back the same priority, if not we get back priority+1.
@@ -535,12 +538,112 @@ cache_node_get(
 	return 1;
 }
 
+static unsigned int cache_mru_count(const struct cache *cache)
+{
+	const struct cache_mru	*mru = cache->c_mrus;
+	unsigned int		mru_count = 0;
+	unsigned int		i;
+
+	for (i = 0; i < CACHE_NR_PRIORITIES; i++, mru++)
+		mru_count += mru->cm_count;
+
+	return mru_count;
+}
+
+
+void cache_shrink(struct cache *cache)
+{
+	unsigned int		mru_count = 0;
+	unsigned int		threshold = 0;
+	unsigned int		priority = 0;
+	unsigned int		new_size;
+
+	pthread_mutex_lock(&cache->c_mutex);
+	/* Don't shrink below the original cache size */
+	if (cache->c_maxcount <= cache->c_orig_max)
+		goto out_unlock;
+
+	mru_count = cache_mru_count(cache);
+
+	/*
+	 * If there's not even a batch of nodes on the MRU to try to free,
+	 * don't bother with the rest.
+	 */
+	if (mru_count < CACHE_SHAKE_COUNT)
+		goto out_unlock;
+
+	/*
+	 * Figure out the next step down in size, but don't go below the
+	 * original size.
+	 */
+	if (cache->resize)
+		new_size = cache->resize(cache, cache->c_maxcount, -1);
+	else
+		new_size = cache->c_maxcount / 2;
+	if (new_size >= cache->c_maxcount)
+		goto out_unlock;
+	if (new_size < cache->c_orig_max)
+		new_size = cache->c_orig_max;
+
+	/*
+	 * If we can't purge enough nodes to get the node count below new_size,
+	 * don't resize the cache.
+	 */
+	if (cache->c_count - mru_count >= new_size)
+		goto out_unlock;
+
+#ifdef CACHE_DEBUG
+	fprintf(stderr, "decreasing cache max size from %u to %u (currently %u)\n",
+		cache->c_maxcount, new_size, cache->c_count);
+#endif
+	cache->c_maxcount = new_size;
+
+	/* Try to reduce the number of cached objects. */
+	do {
+		unsigned int new_priority;
+
+		/*
+		 * The threshold is the amount we need to purge to get c_count
+		 * below the new maxcount.  Try to free some objects off the
+		 * MRU.  Drop c_mutex because cache_shake will take it.
+		 */
+		threshold = cache->c_count - new_size;
+		pthread_mutex_unlock(&cache->c_mutex);
+
+		new_priority = cache_shake(cache, priority, false, threshold);
+
+		/* Either we made no progress or we ran out of MRU levels */
+		if (new_priority == priority ||
+		    new_priority > CACHE_MAX_PRIORITY)
+			return;
+		priority = new_priority;
+
+		pthread_mutex_lock(&cache->c_mutex);
+		/*
+		 * Someone could have walked in and changed the cache maxsize
+		 * again while we had the lock dropped.  If that happened, stop
+		 * clearing.
+		 */
+		if (cache->c_maxcount != new_size)
+			goto out_unlock;
+
+		mru_count = cache_mru_count(cache);
+		if (cache->c_count - mru_count >= new_size)
+			goto out_unlock;
+	} while (1);
+
+out_unlock:
+	pthread_mutex_unlock(&cache->c_mutex);
+	return;
+}
+
 void
 cache_node_put(
 	struct cache *		cache,
 	struct cache_node *	node)
 {
 	struct cache_mru *	mru;
+	bool was_put = false;
 
 	pthread_mutex_lock(&node->cn_mutex);
 #ifdef CACHE_DEBUG
@@ -556,6 +659,7 @@ cache_node_put(
 	}
 #endif
 	node->cn_count--;
+	was_put = (node->cn_count == 0);
 
 	if (node->cn_count == 0 && cache->put)
 		cache->put(cache, node);
@@ -569,6 +673,9 @@ cache_node_put(
 	}
 
 	pthread_mutex_unlock(&node->cn_mutex);
+
+	if (was_put && (cache->c_flags & CACHE_AUTO_SHRINK))
+		cache_shrink(cache);
 }
 
 void
@@ -660,7 +767,7 @@ cache_purge(
 	int			i;
 
 	for (i = 0; i <= CACHE_DIRTY_PRIORITY; i++)
-		cache_shake(cache, i, true);
+		cache_shake(cache, i, true, CACHE_SHAKE_COUNT);
 
 #ifdef CACHE_DEBUG
 	if (cache->c_count != 0) {


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

* [PATCH 20/23] fuse4fs: add cache to track open files
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (18 preceding siblings ...)
  2025-11-06 22:48   ` [PATCH 19/23] cache: implement automatic shrinking Darrick J. Wong
@ 2025-11-06 22:48   ` Darrick J. Wong
  2025-11-06 22:48   ` [PATCH 21/23] fuse4fs: use the orphaned inode list Darrick J. Wong
                     ` (2 subsequent siblings)
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Add our own inode cache so that we can track open files.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/cache.h |    7 +++
 fuse4fs/Makefile.in |    3 +
 fuse4fs/fuse4fs.c   |  132 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 1 deletion(-)


diff --git a/lib/support/cache.h b/lib/support/cache.h
index c7c8298c115d50..71fb9762f97866 100644
--- a/lib/support/cache.h
+++ b/lib/support/cache.h
@@ -6,6 +6,13 @@
 #ifndef __CACHE_H__
 #define __CACHE_H__
 
+/*  2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */
+#define GOLDEN_RATIO_PRIME	0x9e37fffffffc0001UL
+#ifndef CACHE_LINE_SIZE
+/* if the system didn't tell us, guess something reasonable */
+#define CACHE_LINE_SIZE		64
+#endif
+
 /*
  * initialisation flags
  */
diff --git a/fuse4fs/Makefile.in b/fuse4fs/Makefile.in
index 6b41d1dd5ffe8d..9f3547c271638f 100644
--- a/fuse4fs/Makefile.in
+++ b/fuse4fs/Makefile.in
@@ -146,7 +146,8 @@ fuse4fs.o: $(srcdir)/fuse4fs.c $(top_builddir)/lib/config.h \
  $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
  $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/ext2fsP.h \
  $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/version.h \
- $(top_srcdir)/lib/e2p/e2p.h
+ $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/cache.h \
+ $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/support/xbitops.h
 journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/../debugfs/journal.h \
  $(top_srcdir)/e2fsck/jfs_user.h $(top_srcdir)/e2fsck/e2fsck.h \
diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 7585f1ff346d84..038d6126dbfde1 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -26,6 +26,7 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 #include <ctype.h>
+#include <assert.h>
 #define FUSE_DARWIN_ENABLE_EXTENSIONS 0
 #ifdef __SET_FOB_FOR_FUSE
 # error Do not set magic value __SET_FOB_FOR_FUSE!!!!
@@ -49,6 +50,8 @@
 #include "ext2fs/ext2_fs.h"
 #include "ext2fs/ext2fsP.h"
 #include "support/bthread.h"
+#include "support/list.h"
+#include "support/cache.h"
 
 #include "../version.h"
 #include "uuid/uuid.h"
@@ -206,6 +209,7 @@ int journal_enable_debug = -1;
 #define FUSE4FS_FILE_MAGIC	(0xEF53DEAFUL)
 struct fuse4fs_file_handle {
 	unsigned long magic;
+	struct fuse4fs_inode *fi;
 	ext2_ino_t ino;
 	int open_flags;
 	int check_flags;
@@ -261,6 +265,7 @@ struct fuse4fs {
 	int timing;
 #endif
 	struct fuse_session *fuse;
+	struct cache inodes;
 };
 
 #define FUSE4FS_CHECK_HANDLE(req, fh) \
@@ -353,6 +358,115 @@ static inline int u_log2(unsigned int arg)
 	return l;
 }
 
+struct fuse4fs_inode {
+	struct cache_node	i_cnode;
+	ext2_ino_t		i_ino;
+	unsigned int		i_open_count;
+};
+
+struct fuse4fs_ikey {
+	ext2_ino_t		i_ino;
+};
+
+#define ICKEY(key)	((struct fuse4fs_ikey *)(key))
+#define ICNODE(node)	(container_of((node), struct fuse4fs_inode, i_cnode))
+
+static unsigned int
+icache_hash(cache_key_t key, unsigned int hashsize, unsigned int hashshift)
+{
+	uint64_t	hashval = ICKEY(key)->i_ino;
+	uint64_t	tmp;
+
+	tmp = hashval ^ (GOLDEN_RATIO_PRIME + hashval) / CACHE_LINE_SIZE;
+	tmp = tmp ^ ((tmp ^ GOLDEN_RATIO_PRIME) >> hashshift);
+	return tmp % hashsize;
+}
+
+static int icache_compare(struct cache_node *node, cache_key_t key)
+{
+	struct fuse4fs_inode *fi = ICNODE(node);
+	struct fuse4fs_ikey *ikey = ICKEY(key);
+
+	if (fi->i_ino == ikey->i_ino)
+		return CACHE_HIT;
+
+	return CACHE_MISS;
+}
+
+static struct cache_node *icache_alloc(struct cache *c, cache_key_t key)
+{
+	struct fuse4fs_ikey *ikey = ICKEY(key);
+	struct fuse4fs_inode *fi;
+
+	fi = calloc(1, sizeof(struct fuse4fs_inode));
+	if (!fi)
+		return NULL;
+
+	fi->i_ino = ikey->i_ino;
+	return &fi->i_cnode;
+}
+
+static bool icache_flush(struct cache *c, struct cache_node *node)
+{
+	return false;
+}
+
+static void icache_relse(struct cache *c, struct cache_node *node)
+{
+	struct fuse4fs_inode *fi = ICNODE(node);
+
+	assert(fi->i_open_count == 0);
+	free(fi);
+}
+
+static unsigned int icache_bulkrelse(struct cache *cache,
+				     struct list_head *list)
+{
+	struct cache_node *cn, *n;
+	int count = 0;
+
+	if (list_empty(list))
+		return 0;
+
+	list_for_each_entry_safe(cn, n, list, cn_mru) {
+		icache_relse(cache, cn);
+		count++;
+	}
+
+	return count;
+}
+
+static const struct cache_operations icache_ops = {
+	.hash		= icache_hash,
+	.alloc		= icache_alloc,
+	.flush		= icache_flush,
+	.relse		= icache_relse,
+	.compare	= icache_compare,
+	.bulkrelse	= icache_bulkrelse,
+	.resize		= cache_gradual_resize,
+};
+
+static errcode_t fuse4fs_iget(struct fuse4fs *ff, ext2_ino_t ino,
+			      struct fuse4fs_inode **fip)
+{
+	struct fuse4fs_ikey ikey = {
+		.i_ino = ino,
+	};
+	struct cache_node *node = NULL;
+
+	cache_node_get(&ff->inodes, &ikey, 0, &node);
+	if (!node)
+		return ENOMEM;
+
+	*fip = ICNODE(node);
+	return 0;
+}
+
+static void fuse4fs_iput(struct fuse4fs *ff, struct fuse4fs_inode *fi)
+{
+	cache_node_put(&ff->inodes, &fi->i_cnode);
+}
+
 static inline blk64_t FUSE4FS_B_TO_FSBT(const struct fuse4fs *ff, off_t pos)
 {
 	return pos >> ff->blocklog;
@@ -1137,6 +1251,11 @@ static void fuse4fs_unmount(struct fuse4fs *ff)
 	errcode_t err;
 
 	if (ff->fs) {
+		if (cache_initialized(&ff->inodes)) {
+			cache_purge(&ff->inodes);
+			cache_destroy(&ff->inodes);
+		}
+
 		uuid_unparse(ff->fs->super->s_uuid, uuid);
 		err = ext2fs_close_free(&ff->fs);
 		if (err)
@@ -1268,6 +1387,10 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff)
 		log_printf(ff, "%s %s.\n", _("mounted filesystem"), uuid);
 	}
 
+	err = cache_init(CACHE_AUTO_SHRINK, 1U << 10, &icache_ops, &ff->inodes);
+	if (err)
+		return translate_error(ff->fs, 0, err);
+
 	ff->fs->priv_data = ff;
 	ff->blocklog = u_log2(ff->fs->blocksize);
 	ff->blockmask = ff->fs->blocksize - 1;
@@ -2326,6 +2449,7 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 	if (inode.i_links_count)
 		goto write_out;
 
+
 	if (ext2fs_has_feature_ea_inode(fs->super)) {
 		ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
 		if (ret)
@@ -3282,6 +3406,13 @@ static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
 			goto out;
 	}
 
+	err = fuse4fs_iget(ff, file->ino, &file->fi);
+	if (err) {
+		ret = translate_error(fs, 0, err);
+		goto out;
+	}
+	file->fi->i_open_count++;
+
 	file->check_flags = check;
 	fuse4fs_set_handle(fp, file);
 
@@ -3470,6 +3601,7 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
 			ret = translate_error(fs, fh->ino, err);
 	}
 
+	fuse4fs_iput(ff, fh->fi);
 	fp->fh = 0;
 	fuse4fs_finish(ff, ret);
 


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

* [PATCH 21/23] fuse4fs: use the orphaned inode list
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (19 preceding siblings ...)
  2025-11-06 22:48   ` [PATCH 20/23] fuse4fs: add cache to track open files Darrick J. Wong
@ 2025-11-06 22:48   ` Darrick J. Wong
  2025-11-06 22:48   ` [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE Darrick J. Wong
  2025-11-06 22:49   ` [PATCH 23/23] fuse4fs: create incore reverse orphan list Darrick J. Wong
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Put open but unlinked files on the orphan list, and remove them when the
last open fd releases the inode.

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


diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 038d6126dbfde1..fdeef157e732d4 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -1114,6 +1114,13 @@ static int fuse4fs_inum_access(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
 		   inode_uid(inode), inode_gid(inode),
 		   ctxt->uid, ctxt->gid);
 
+	/* linked files cannot be on the unlinked list or deleted */
+	if (inode.i_dtime != 0) {
+		dbg_printf(ff, "%s: unlinked ino=%d dtime=0x%x\n",
+			   __func__, ino, inode.i_dtime);
+		return -ENOENT;
+	}
+
 	/* existence check */
 	if (mask == 0)
 		return 0;
@@ -2404,9 +2411,80 @@ static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
 	return 0;
 }
 
+static int fuse4fs_add_to_orphans(struct fuse4fs *ff, ext2_ino_t ino,
+				  struct ext2_inode_large *inode)
+{
+	ext2_filsys fs = ff->fs;
+
+	dbg_printf(ff, "%s: orphan ino=%d dtime=%d next=%d\n",
+		   __func__, ino, inode->i_dtime, fs->super->s_last_orphan);
+
+	inode->i_dtime = fs->super->s_last_orphan;
+	fs->super->s_last_orphan = ino;
+	ext2fs_mark_super_dirty(fs);
+
+	return 0;
+}
+
+static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
+				       struct ext2_inode_large *inode)
+{
+	ext2_filsys fs = ff->fs;
+	ext2_ino_t prev_orphan;
+	errcode_t err;
+
+	dbg_printf(ff, "%s: super=%d ino=%d next=%d\n",
+		   __func__, fs->super->s_last_orphan, ino, inode->i_dtime);
+
+	/* If we're lucky, the ondisk superblock points to us */
+	if (fs->super->s_last_orphan == ino) {
+		dbg_printf(ff, "%s: superblock\n", __func__);
+
+		fs->super->s_last_orphan = inode->i_dtime;
+		inode->i_dtime = 0;
+		ext2fs_mark_super_dirty(fs);
+		return 0;
+	}
+
+	/* Otherwise walk the ondisk orphan list. */
+	prev_orphan = fs->super->s_last_orphan;
+	while (prev_orphan != 0) {
+		struct ext2_inode_large orphan;
+
+		err = fuse4fs_read_inode(fs, prev_orphan, &orphan);
+		if (err)
+			return translate_error(fs, prev_orphan, err);
+
+		if (orphan.i_dtime == prev_orphan)
+			return translate_error(fs, prev_orphan,
+					       EXT2_ET_FILESYSTEM_CORRUPTED);
+
+		if (orphan.i_dtime == ino) {
+			dbg_printf(ff, "%s: prev=%d\n",
+				   __func__, prev_orphan);
+
+			orphan.i_dtime = inode->i_dtime;
+			inode->i_dtime = 0;
+
+			err = fuse4fs_write_inode(fs, prev_orphan, &orphan);
+			if (err)
+				return translate_error(fs, prev_orphan, err);
+
+			return 0;
+		}
+
+		dbg_printf(ff, "%s: orphan=%d next=%d\n",
+			   __func__, prev_orphan, orphan.i_dtime);
+		prev_orphan = orphan.i_dtime;
+	}
+
+	return translate_error(fs, ino, EXT2_ET_FILESYSTEM_CORRUPTED);
+}
+
 static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 {
 	ext2_filsys fs = ff->fs;
+	struct fuse4fs_inode *fi;
 	errcode_t err;
 	struct ext2_inode_large inode;
 	int ret = 0;
@@ -2428,7 +2506,6 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 		if (!ext2fs_dir_link_empty(EXT2_INODE(&inode)))
 			return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
 		inode.i_links_count = 0;
-		ext2fs_set_dtime(fs, EXT2_INODE(&inode));
 	} else {
 		/*
 		 * Any other file type can be hardlinked, so all we need to do
@@ -2437,8 +2514,6 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 		if (inode.i_links_count == 0)
 			return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
 		inode.i_links_count--;
-		if (!inode.i_links_count)
-			ext2fs_set_dtime(fs, EXT2_INODE(&inode));
 	}
 
 	ret = update_ctime(fs, ino, &inode);
@@ -2449,6 +2524,26 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 	if (inode.i_links_count)
 		goto write_out;
 
+	err = fuse4fs_iget(ff, ino, &fi);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	dbg_printf(ff, "%s: put ino=%d opencount=%d\n", __func__, ino,
+		   fi->i_open_count);
+
+	/*
+	 * The file is unlinked but still open; add it to the orphan list and
+	 * free it later.
+	 */
+	if (fi->i_open_count > 0) {
+		fuse4fs_iput(ff, fi);
+		ret = fuse4fs_add_to_orphans(ff, ino, &inode);
+		if (ret)
+			return ret;
+
+		goto write_out;
+	}
+	fuse4fs_iput(ff, fi);
 
 	if (ext2fs_has_feature_ea_inode(fs->super)) {
 		ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
@@ -2468,6 +2563,7 @@ static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
 			return translate_error(fs, ino, err);
 	}
 
+	ext2fs_set_dtime(fs, EXT2_INODE(&inode));
 	ext2fs_inode_alloc_stats2(fs, ino, -1,
 				  LINUX_S_ISDIR(inode.i_mode));
 
@@ -3056,6 +3152,16 @@ static void op_link(fuse_req_t req, fuse_ino_t child_fino,
 		goto out2;
 	}
 
+	/*
+	 * Linking a file back into the filesystem requires removing it from
+	 * the orphan list.
+	 */
+	if (inode.i_links_count == 0) {
+		ret = fuse4fs_remove_from_orphans(ff, child, &inode);
+		if (ret)
+			goto out2;
+	}
+
 	ext2fs_inc_nlink(fs, EXT2_INODE(&inode));
 	ret = update_ctime(fs, child, &inode);
 	if (ret)
@@ -3339,7 +3445,8 @@ static void detect_linux_executable_open(int kernel_flags, int *access_check,
 #endif /* __linux__ */
 
 static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
-			     ext2_ino_t ino, struct fuse_file_info *fp)
+			     ext2_ino_t ino,
+			     struct fuse_file_info *fp)
 {
 	ext2_filsys fs = ff->fs;
 	errcode_t err;
@@ -3415,6 +3522,8 @@ static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
 
 	file->check_flags = check;
 	fuse4fs_set_handle(fp, file);
+	dbg_printf(ff, "%s: ino=%d fh=%p opencount=%d\n", __func__, ino, file,
+		   file->fi->i_open_count);
 
 out:
 	if (ret)
@@ -3431,6 +3540,8 @@ static void op_open(fuse_req_t req, fuse_ino_t fino, struct fuse_file_info *fp)
 
 	FUSE4FS_CHECK_CONTEXT(req);
 	FUSE4FS_CONVERT_FINO(req, &ino, fino);
+	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
+
 	fuse4fs_start(ff);
 	ret = fuse4fs_open_file(ff, ctxt, ino, fp);
 	fuse4fs_finish(ff, ret);
@@ -3579,6 +3690,55 @@ static void op_write(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
 		fuse_reply_err(req, -ret);
 }
 
+static int fuse4fs_free_unlinked(struct fuse4fs *ff, ext2_ino_t ino)
+{
+	struct ext2_inode_large inode;
+	ext2_filsys fs = ff->fs;
+	errcode_t err;
+	int ret = 0;
+
+	err = fuse4fs_read_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	if (inode.i_links_count > 0)
+		return 0;
+
+	dbg_printf(ff, "%s: ino=%d links=%d\n", __func__, ino,
+		   inode.i_links_count);
+
+	if (ext2fs_has_feature_ea_inode(fs->super)) {
+		ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
+		if (ret)
+			return ret;
+	}
+
+	/* Nobody holds this file; free its blocks! */
+	err = ext2fs_free_ext_attr(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(&inode))) {
+		err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), NULL,
+				   0, ~0ULL);
+		if (err)
+			return translate_error(fs, ino, err);
+	}
+
+	ret = fuse4fs_remove_from_orphans(ff, ino, &inode);
+	if (ret)
+		return ret;
+
+	ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+	ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
+
+	err = fuse4fs_write_inode(fs, ino, &inode);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	return 0;
+}
+
 static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
 		       struct fuse_file_info *fp)
 {
@@ -3590,9 +3750,21 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
 
 	FUSE4FS_CHECK_CONTEXT(req);
 	FUSE4FS_CHECK_HANDLE(req, fh);
-	dbg_printf(ff, "%s: ino=%d\n", __func__, fh->ino);
+	dbg_printf(ff, "%s: ino=%d fh=%p opencount=%u\n",
+		   __func__, fh->ino, fh, fh->fi->i_open_count);
+
 	fs = fuse4fs_start(ff);
 
+	/*
+	 * If the file is no longer open and is unlinked, free it, which
+	 * removes it from the ondisk list.
+	 */
+	if (--fh->fi->i_open_count == 0) {
+		ret = fuse4fs_free_unlinked(ff, fh->ino);
+		if (ret)
+			goto out_iput;
+	}
+
 	if ((fp->flags & O_SYNC) &&
 	    fuse4fs_is_writeable(ff) &&
 	    (fh->open_flags & EXT2_FILE_WRITE)) {
@@ -3601,6 +3773,7 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),
 			ret = translate_error(fs, fh->ino, err);
 	}
 
+out_iput:
 	fuse4fs_iput(ff, fh->fi);
 	fp->fh = 0;
 	fuse4fs_finish(ff, ret);


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

* [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (20 preceding siblings ...)
  2025-11-06 22:48   ` [PATCH 21/23] fuse4fs: use the orphaned inode list Darrick J. Wong
@ 2025-11-06 22:48   ` Darrick J. Wong
  2025-11-06 22:49   ` [PATCH 23/23] fuse4fs: create incore reverse orphan list Darrick J. Wong
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:48 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Allow creation of O_TMPFILE files now that we know how to use the
unlinked list.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 fuse4fs/fuse4fs.c |   93 ++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 67 insertions(+), 26 deletions(-)


diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index fdeef157e732d4..7db09ed93fdf1f 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -1056,22 +1056,25 @@ static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
 
 /* Test for append permission */
 #define A_OK	16
+/* Test for linked file */
+#define L_OK	32
 
 static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
 				 const struct ext2_inode *inode, int mask)
 {
-	EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
+	EXT2FS_BUILD_BUG_ON(((A_OK | L_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)) && !fuse4fs_is_writeable(ff))
 		return -EROFS;
 
-	dbg_printf(ff, "access ino=%d mask=e%s%s%s%s iflags=0x%x\n",
+	dbg_printf(ff, "access ino=%d mask=e%s%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" : ""),
+		   (mask & L_OK ? "l" : ""),
 		   inode->i_flags);
 
 	/* is immutable? */
@@ -1104,21 +1107,31 @@ static int fuse4fs_inum_access(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
 		return translate_error(fs, ino, err);
 	perms = inode.i_mode & 0777;
 
-	dbg_printf(ff, "access ino=%d mask=e%s%s%s%s perms=0%o iflags=0x%x "
+	dbg_printf(ff, "access ino=%d mask=e%s%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" : ""),
+		   (mask & L_OK ? "l" : ""),
 		   perms, inode.i_flags,
 		   inode_uid(inode), inode_gid(inode),
 		   ctxt->uid, ctxt->gid);
 
-	/* linked files cannot be on the unlinked list or deleted */
-	if (inode.i_dtime != 0) {
-		dbg_printf(ff, "%s: unlinked ino=%d dtime=0x%x\n",
-			   __func__, ino, inode.i_dtime);
-		return -ENOENT;
+	if (mask & L_OK) {
+		/* linked files cannot be on the unlinked list or deleted */
+		if (inode.i_dtime != 0) {
+			dbg_printf(ff, "%s: unlinked ino=%d dtime=0x%x\n",
+				   __func__, ino, inode.i_dtime);
+			return -ENOENT;
+		}
+	} else {
+		/* unlinked files cannot be deleted */
+		if (inode.i_dtime >= fs->super->s_inodes_count) {
+			dbg_printf(ff, "%s: deleted ino=%d dtime=0x%x\n",
+				   __func__, ino, inode.i_dtime);
+			return -ENOENT;
+		}
 	}
 
 	/* existence check */
@@ -3445,7 +3458,7 @@ static void detect_linux_executable_open(int kernel_flags, int *access_check,
 #endif /* __linux__ */
 
 static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
-			     ext2_ino_t ino,
+			     ext2_ino_t ino, bool linked,
 			     struct fuse_file_info *fp)
 {
 	ext2_filsys fs = ff->fs;
@@ -3475,6 +3488,9 @@ static int fuse4fs_open_file(struct fuse4fs *ff, const struct fuse_ctx *ctxt,
 		break;
 	}
 
+	if (linked)
+		check |= L_OK;
+
 	/*
 	 * If the caller wants to truncate the file, we need to ask for full
 	 * write access even if the caller claims to be appending.
@@ -3543,7 +3559,7 @@ static void op_open(fuse_req_t req, fuse_ino_t fino, struct fuse_file_info *fp)
 	dbg_printf(ff, "%s: ino=%d\n", __func__, ino);
 
 	fuse4fs_start(ff);
-	ret = fuse4fs_open_file(ff, ctxt, ino, fp);
+	ret = fuse4fs_open_file(ff, ctxt, ino, true, fp);
 	fuse4fs_finish(ff, ret);
 
 	if (ret)
@@ -4452,22 +4468,28 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
 		goto out2;
 	}
 
-	dbg_printf(ff, "%s: creating dir=%d name='%s' child=%d\n",
-		   __func__, parent, name, child);
-	err = ext2fs_link(fs, parent, name, child,
-			  filetype | EXT2FS_LINK_EXPAND);
-	if (err) {
-		ret = translate_error(fs, parent, err);
-		goto out2;
+	if (name) {
+		dbg_printf(ff, "%s: creating dir=%d name='%s' child=%d\n",
+			   __func__, parent, name, child);
+
+		err = ext2fs_link(fs, parent, name, child,
+				  filetype | EXT2FS_LINK_EXPAND);
+		if (err) {
+			ret = translate_error(fs, parent, err);
+			goto out2;
+		}
+
+		ret = update_mtime(fs, parent, NULL);
+		if (ret)
+			goto out2;
+	} else {
+		dbg_printf(ff, "%s: creating dir=%d tempfile=%d\n",
+			   __func__, parent, child);
 	}
 
-	ret = update_mtime(fs, parent, NULL);
-	if (ret)
-		goto out2;
-
 	memset(&inode, 0, sizeof(inode));
 	inode.i_mode = mode;
-	inode.i_links_count = 1;
+	inode.i_links_count = name ? 1 : 0;
 	fuse4fs_set_extra_isize(ff, child, &inode);
 	fuse4fs_set_uid(&inode, ctxt->uid);
 	fuse4fs_set_gid(&inode, gid);
@@ -4485,6 +4507,12 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
 		ext2fs_extent_free(handle);
 	}
 
+	if (!name) {
+		ret = fuse4fs_add_to_orphans(ff, child, &inode);
+		if (ret)
+			goto out2;
+	}
+
 	err = ext2fs_write_new_inode(fs, child, EXT2_INODE(&inode));
 	if (err) {
 		ret = translate_error(fs, child, err);
@@ -4506,13 +4534,15 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
 		goto out2;
 
 	fp->flags &= ~O_TRUNC;
-	ret = fuse4fs_open_file(ff, ctxt, child, fp);
+	ret = fuse4fs_open_file(ff, ctxt, child, name != NULL, fp);
 	if (ret)
 		goto out2;
 
-	ret = fuse4fs_dirsync_flush(ff, parent, NULL);
-	if (ret)
-		goto out2;
+	if (name) {
+		ret = fuse4fs_dirsync_flush(ff, parent, NULL);
+		if (ret)
+			goto out2;
+	}
 
 	ret = fuse4fs_stat_inode(ff, child, NULL, &fstat);
 	if (ret)
@@ -4527,6 +4557,14 @@ static void op_create(fuse_req_t req, fuse_ino_t fino, const char *name,
 		fuse_reply_create(req, &fstat.entry, fp);
 }
 
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+static void op_tmpfile(fuse_req_t req, fuse_ino_t fino, mode_t mode,
+		       struct fuse_file_info *fp)
+{
+	op_create(req, fino, NULL, mode, fp);
+}
+#endif
+
 enum fuse4fs_time_action {
 	TA_NOW,		/* set to current time */
 	TA_OMIT,	/* do not set timestamp */
@@ -5521,6 +5559,9 @@ static struct fuse_lowlevel_ops fs_ops = {
 	.fsyncdir = op_fsync,
 	.access = op_access,
 	.create = op_create,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17)
+	.tmpfile = op_tmpfile,
+#endif
 	.bmap = op_bmap,
 #ifdef SUPERFLUOUS
 	.lock = op_lock,


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

* [PATCH 23/23] fuse4fs: create incore reverse orphan list
  2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
                     ` (21 preceding siblings ...)
  2025-11-06 22:48   ` [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE Darrick J. Wong
@ 2025-11-06 22:49   ` Darrick J. Wong
  22 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-06 22:49 UTC (permalink / raw)
  To: tytso; +Cc: linux-ext4

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

Create an incore orphan list so that removing open unlinked inodes
doesn't take forever.

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


diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
index 7db09ed93fdf1f..609e23bd916cc0 100644
--- a/fuse4fs/fuse4fs.c
+++ b/fuse4fs/fuse4fs.c
@@ -358,10 +358,20 @@ static inline int u_log2(unsigned int arg)
 	return l;
 }
 
+/* inode is not on unlinked list */
+#define FUSE4FS_NULL_INO	((ext2_ino_t)~0ULL)
+
 struct fuse4fs_inode {
 	struct cache_node	i_cnode;
 	ext2_ino_t		i_ino;
 	unsigned int		i_open_count;
+
+	/*
+	 * FUSE4FS_NULL_INO: inode is not on the orphan list
+	 * 0: inode is the first on the orphan list
+	 * otherwise: inode is in the middle of the list
+	 */
+	ext2_ino_t		i_prev_orphan;
 };
 
 struct fuse4fs_ikey {
@@ -403,12 +413,15 @@ static struct cache_node *icache_alloc(struct cache *c, cache_key_t key)
 		return NULL;
 
 	fi->i_ino = ikey->i_ino;
+	fi->i_prev_orphan = FUSE4FS_NULL_INO;
 	return &fi->i_cnode;
 }
 
 static bool icache_flush(struct cache *c, struct cache_node *node)
 {
-	return false;
+	struct fuse4fs_inode *fi = ICNODE(node);
+
+	return fi->i_prev_orphan != FUSE4FS_NULL_INO;
 }
 
 static void icache_relse(struct cache *c, struct cache_node *node)
@@ -2428,10 +2441,31 @@ static int fuse4fs_add_to_orphans(struct fuse4fs *ff, ext2_ino_t ino,
 				  struct ext2_inode_large *inode)
 {
 	ext2_filsys fs = ff->fs;
+	struct fuse4fs_inode *fi;
+	ext2_ino_t orphan_ino = fs->super->s_last_orphan;
+	errcode_t err;
 
 	dbg_printf(ff, "%s: orphan ino=%d dtime=%d next=%d\n",
 		   __func__, ino, inode->i_dtime, fs->super->s_last_orphan);
 
+	/* Make the first orphan on the list point back to us */
+	if (orphan_ino != 0) {
+		err = fuse4fs_iget(ff, orphan_ino, &fi);
+		if (err)
+			return translate_error(fs, orphan_ino, err);
+
+		fi->i_prev_orphan = ino;
+		fuse4fs_iput(ff, fi);
+	}
+
+	/* Add ourselves to the head of the orphan list */
+	err = fuse4fs_iget(ff, ino, &fi);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	fi->i_prev_orphan = 0;
+	fuse4fs_iput(ff, fi);
+
 	inode->i_dtime = fs->super->s_last_orphan;
 	fs->super->s_last_orphan = ino;
 	ext2fs_mark_super_dirty(fs);
@@ -2439,24 +2473,158 @@ static int fuse4fs_add_to_orphans(struct fuse4fs *ff, ext2_ino_t ino,
 	return 0;
 }
 
+/*
+ * Given the orphan list excerpt: prev_orphan -> ino -> next_orphan, set
+ * next_orphan's backpointer to ino's backpointer (prev_orphan), having removed
+ * ino from the orphan list.
+ */
+static int fuse4fs_update_next_orphan_backlink(struct fuse4fs *ff,
+					       ext2_ino_t prev_orphan,
+					       ext2_ino_t ino,
+					       ext2_ino_t next_orphan)
+{
+	struct fuse4fs_inode *fi;
+	errcode_t err;
+	int ret = 0;
+
+	err = fuse4fs_iget(ff, next_orphan, &fi);
+	if (err)
+		return translate_error(ff->fs, next_orphan, err);
+
+	dbg_printf(ff, "%s: ino=%d cached next=%d nextprev=%d prev=%d\n",
+		   __func__, ino, next_orphan, fi->i_prev_orphan,
+		   prev_orphan);
+
+	if (fi->i_prev_orphan != ino) {
+		ret = translate_error(ff->fs, next_orphan,
+				      EXT2_ET_FILESYSTEM_CORRUPTED);
+		goto out_iput;
+	}
+
+	fi->i_prev_orphan = prev_orphan;
+out_iput:
+	fuse4fs_iput(ff, fi);
+	return ret;
+}
+
+/*
+ * Remove ino from the orphan list the fast way.  Returns 1 for success, 0 if
+ * it didn't do anything, or a negative errno.
+ */
+static int fuse4fs_fast_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
+					    struct ext2_inode_large *inode)
+{
+	struct ext2_inode_large orphan;
+	ext2_filsys fs = ff->fs;
+	struct fuse4fs_inode *fi;
+	ext2_ino_t prev_orphan;
+	ext2_ino_t next_orphan = 0;
+	errcode_t err;
+	int ret = 0;
+
+	err = fuse4fs_iget(ff, ino, &fi);
+	if (err)
+		return translate_error(fs, ino, err);
+
+	prev_orphan = fi->i_prev_orphan;
+	switch (prev_orphan) {
+	case 0:
+		/* First inode in the list */
+		dbg_printf(ff, "%s: ino=%d cached superblock\n", __func__, ino);
+
+		fs->super->s_last_orphan = inode->i_dtime;
+		next_orphan = inode->i_dtime;
+		inode->i_dtime = 0;
+		ext2fs_mark_super_dirty(fs);
+		fi->i_prev_orphan = FUSE4FS_NULL_INO;
+		break;
+	case FUSE4FS_NULL_INO:
+		/* unknown */
+		dbg_printf(ff, "%s: ino=%d broken list??\n", __func__, ino);
+		ret = 0;
+		goto out_iput;
+	default:
+		/* We're in the middle of the list */
+		err = fuse4fs_read_inode(fs, prev_orphan, &orphan);
+		if (err) {
+			ret = translate_error(fs, prev_orphan, err);
+			goto out_iput;
+		}
+
+		dbg_printf(ff,
+ "%s: ino=%d cached prev=%d prevnext=%d next=%d\n",
+			   __func__, ino, prev_orphan, orphan.i_dtime,
+			   inode->i_dtime);
+
+		if (orphan.i_dtime != ino) {
+			ret = translate_error(fs, prev_orphan,
+					      EXT2_ET_FILESYSTEM_CORRUPTED);
+			goto out_iput;
+		}
+
+		fi->i_prev_orphan = FUSE4FS_NULL_INO;
+		orphan.i_dtime = inode->i_dtime;
+		next_orphan = inode->i_dtime;
+		inode->i_dtime = 0;
+
+		err = fuse4fs_write_inode(fs, prev_orphan, &orphan);
+		if (err) {
+			ret = translate_error(fs, prev_orphan, err);
+			goto out_iput;
+		}
+
+		break;
+	}
+
+	/*
+	 * Make the next orphaned inode point back to the our own previous list
+	 * entry
+	 */
+	if (next_orphan != 0) {
+		ret = fuse4fs_update_next_orphan_backlink(ff, prev_orphan, ino,
+							  next_orphan);
+		if (ret)
+			goto out_iput;
+	}
+	ret = 1;
+
+out_iput:
+	fuse4fs_iput(ff, fi);
+	return ret;
+}
+
 static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
 				       struct ext2_inode_large *inode)
 {
 	ext2_filsys fs = ff->fs;
 	ext2_ino_t prev_orphan;
+	ext2_ino_t next_orphan;
 	errcode_t err;
+	int ret;
 
 	dbg_printf(ff, "%s: super=%d ino=%d next=%d\n",
 		   __func__, fs->super->s_last_orphan, ino, inode->i_dtime);
 
-	/* If we're lucky, the ondisk superblock points to us */
+	/*
+	 * Fast way: use the incore list, which doesn't include any orphans
+	 * that were already on the superblock when we mounted.
+	 */
+	ret = fuse4fs_fast_remove_from_orphans(ff, ino, inode);
+	if (ret < 0)
+		return ret;
+	if (ret == 1)
+		return 0;
+
+	/* Slow way: If we're lucky, the ondisk superblock points to us */
 	if (fs->super->s_last_orphan == ino) {
 		dbg_printf(ff, "%s: superblock\n", __func__);
 
+		next_orphan = inode->i_dtime;
 		fs->super->s_last_orphan = inode->i_dtime;
 		inode->i_dtime = 0;
 		ext2fs_mark_super_dirty(fs);
-		return 0;
+		return fuse4fs_update_next_orphan_backlink(ff, 0, ino,
+							   next_orphan);
 	}
 
 	/* Otherwise walk the ondisk orphan list. */
@@ -2476,6 +2644,7 @@ static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
 			dbg_printf(ff, "%s: prev=%d\n",
 				   __func__, prev_orphan);
 
+			next_orphan = inode->i_dtime;
 			orphan.i_dtime = inode->i_dtime;
 			inode->i_dtime = 0;
 
@@ -2483,7 +2652,8 @@ static int fuse4fs_remove_from_orphans(struct fuse4fs *ff, ext2_ino_t ino,
 			if (err)
 				return translate_error(fs, prev_orphan, err);
 
-			return 0;
+			return fuse4fs_update_next_orphan_backlink(ff,
+					prev_orphan, ino, next_orphan);
 		}
 
 		dbg_printf(ff, "%s: orphan=%d next=%d\n",


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

* Re: [PATCH 04/23] fuse4fs: namespace some helpers
  2025-11-06 22:44   ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
@ 2025-11-07  8:09     ` Amir Goldstein
  2025-11-08  0:25       ` Darrick J. Wong
  0 siblings, 1 reply; 84+ messages in thread
From: Amir Goldstein @ 2025-11-07  8:09 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: tytso, linux-ext4

On Thu, Nov 06, 2025 at 02:44:07PM -0800, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
> 
> Prepend "fuse4fs_" to all helper functions that take a struct fuse4fs
> object pointer.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
>  fuse4fs/fuse4fs.c |  177 +++++++++++++++++++++++++++--------------------------
>  1 file changed, 90 insertions(+), 87 deletions(-)
> 
> 
> diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
> index daf22e0fe7fde5..2ef5ad60163639 100644
> --- a/fuse4fs/fuse4fs.c
> +++ b/fuse4fs/fuse4fs.c
> @@ -2,6 +2,7 @@
>   * fuse4fs.c - FUSE low-level server for e2fsprogs.
>   *
>   * Copyright (C) 2014-2025 Oracle.
> + * Copyright (C) 2025 CTERA Networks.

I think this belongs to next patch :)

Thanks,
Amir.

>   *
>   * %Begin-Header%
>   * This file may be redistributed under the terms of the GNU Public
> @@ -852,7 +853,7 @@ static int ext2_file_type(unsigned int mode)
>  	return 0;
>  }
>  
> -static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> +static int fuse4fs_can_allocate(struct fuse4fs *ff, blk64_t num)
>  {
>  	ext2_filsys fs = ff->fs;
>  	blk64_t reserved;
> @@ -879,21 +880,22 @@ static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
>  	return ext2fs_free_blocks_count(fs->super) > reserved + num;
>  }
>  
> -static int fuse4fs_is_writeable(struct fuse4fs *ff)
> +static int fuse4fs_is_writeable(const struct fuse4fs *ff)
>  {
>  	return ff->opstate == F4OP_WRITABLE &&
>  		(ff->fs->super->s_error_count == 0);
>  }
>  
> -static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
> +static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
> +				       const struct fuse_context *ctxt)
>  {
>  	if (ff->fakeroot)
>  		return 1;
>  	return ctxt->uid == 0;
>  }
>  
> -static inline int want_check_owner(struct fuse4fs *ff,
> -				   struct fuse_context *ctxt)
> +static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
> +					   const struct fuse_context *ctxt)
>  {
>  	/*
>  	 * The kernel is responsible for access control, so we allow anything
> @@ -901,14 +903,14 @@ static inline int want_check_owner(struct fuse4fs *ff,
>  	 */
>  	if (ff->kernel)
>  		return 0;
> -	return !is_superuser(ff, ctxt);
> +	return !fuse4fs_is_superuser(ff, ctxt);
>  }
>  
>  /* Test for append permission */
>  #define A_OK	16
>  
> -static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> -			       const struct ext2_inode *inode, int mask)
> +static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> +				 const struct ext2_inode *inode, int mask)
>  {
>  	EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
>  
> @@ -936,7 +938,7 @@ static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
>  	return 0;
>  }
>  
> -static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> +static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
>  {
>  	struct fuse_context *ctxt = fuse_get_context();
>  	ext2_filsys fs = ff->fs;
> @@ -968,7 +970,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
>  	if (mask == 0)
>  		return 0;
>  
> -	ret = check_iflags_access(ff, ino, &inode, mask);
> +	ret = fuse4fs_iflags_access(ff, ino, &inode, mask);
>  	if (ret)
>  		return ret;
>  
> @@ -977,7 +979,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
>  		return 0;
>  
>  	/* Figure out what root's allowed to do */
> -	if (is_superuser(ff, ctxt)) {
> +	if (fuse4fs_is_superuser(ff, ctxt)) {
>  		/* Non-file access always ok */
>  		if (!LINUX_S_ISREG(inode.i_mode))
>  			return 0;
> @@ -1783,8 +1785,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
>  	return ret;
>  }
>  
> -static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> -		      void **value, size_t *value_len)
> +static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
> +			    const char *name, void **value, size_t *value_len)
>  {
>  	ext2_filsys fs = ff->fs;
>  	struct ext2_xattr_handle *h;
> @@ -1814,8 +1816,8 @@ static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
>  	return ret;
>  }
>  
> -static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> -		      void *value, size_t valuelen)
> +static int fuse4fs_setxattr(struct fuse4fs *ff, ext2_ino_t ino,
> +			    const char *name, void *value, size_t valuelen)
>  {
>  	ext2_filsys fs = ff->fs;
>  	struct ext2_xattr_handle *h;
> @@ -1845,8 +1847,8 @@ static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
>  	return ret;
>  }
>  
> -static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> -				  ext2_ino_t child, mode_t mode)
> +static int fuse4fs_propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> +					  ext2_ino_t child, mode_t mode)
>  {
>  	void *def;
>  	size_t deflen;
> @@ -1855,8 +1857,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
>  	if (!ff->acl || S_ISDIR(mode))
>  		return 0;
>  
> -	ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> -			 &deflen);
> +	ret = fuse4fs_getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> +			       &deflen);
>  	switch (ret) {
>  	case -ENODATA:
>  	case -ENOENT:
> @@ -1868,7 +1870,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
>  		return ret;
>  	}
>  
> -	ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
> +	ret = fuse4fs_setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def,
> +			       deflen);
>  	ext2fs_free_mem(&def);
>  	return ret;
>  }
> @@ -1997,7 +2000,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
>  	*node_name = 0;
>  
>  	fs = fuse4fs_start(ff);
> -	if (!fs_can_allocate(ff, 2)) {
> +	if (!fuse4fs_can_allocate(ff, 2)) {
>  		ret = -ENOSPC;
>  		goto out2;
>  	}
> @@ -2009,7 +2012,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -2079,7 +2082,7 @@ 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, inode.i_mode);
> +	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
>  	if (ret)
>  		goto out2;
>  
> @@ -2127,7 +2130,7 @@ static int op_mkdir(const char *path, mode_t mode)
>  	*node_name = 0;
>  
>  	fs = fuse4fs_start(ff);
> -	if (!fs_can_allocate(ff, 1)) {
> +	if (!fuse4fs_can_allocate(ff, 1)) {
>  		ret = -ENOSPC;
>  		goto out2;
>  	}
> @@ -2139,7 +2142,7 @@ static int op_mkdir(const char *path, mode_t mode)
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -2212,7 +2215,7 @@ static int op_mkdir(const char *path, mode_t mode)
>  		goto out3;
>  	}
>  
> -	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> +	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
>  	if (ret)
>  		goto out3;
>  
> @@ -2253,7 +2256,7 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
>  		base_name = filename;
>  	}
>  
> -	ret = check_inum_access(ff, dir, W_OK);
> +	ret = fuse4fs_inum_access(ff, dir, W_OK);
>  	if (ret) {
>  		free(filename);
>  		return ret;
> @@ -2275,8 +2278,8 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
>  	return 0;
>  }
>  
> -static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> -			    struct ext2_inode_large *inode)
> +static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> +				    struct ext2_inode_large *inode)
>  {
>  	ext2_filsys fs = ff->fs;
>  	struct ext2_xattr_handle *h;
> @@ -2320,7 +2323,7 @@ static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
>  	return 0;
>  }
>  
> -static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> +static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
>  {
>  	ext2_filsys fs = ff->fs;
>  	errcode_t err;
> @@ -2366,7 +2369,7 @@ static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
>  		goto write_out;
>  
>  	if (ext2fs_has_feature_ea_inode(fs->super)) {
> -		ret = remove_ea_inodes(ff, ino, &inode);
> +		ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
>  		if (ret)
>  			return ret;
>  	}
> @@ -2407,7 +2410,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
>  		goto out;
>  	}
>  
> -	ret = check_inum_access(ff, ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, ino, W_OK);
>  	if (ret)
>  		goto out;
>  
> @@ -2415,7 +2418,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
>  	if (ret)
>  		goto out;
>  
> -	ret = remove_inode(ff, ino);
> +	ret = fuse4fs_remove_inode(ff, ino);
>  	if (ret)
>  		goto out;
>  
> @@ -2483,7 +2486,7 @@ static int __op_rmdir(struct fuse4fs *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);
> +	ret = fuse4fs_inum_access(ff, child, W_OK);
>  	if (ret)
>  		goto out;
>  
> @@ -2502,7 +2505,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
>  		goto out;
>  	}
>  
> -	ret = check_inum_access(ff, rds.parent, W_OK);
> +	ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
>  	if (ret)
>  		goto out;
>  
> @@ -2514,7 +2517,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
>  	ret = fuse4fs_unlink(ff, path, &parent);
>  	if (ret)
>  		goto out;
> -	ret = remove_inode(ff, child);
> +	ret = fuse4fs_remove_inode(ff, child);
>  	if (ret)
>  		goto out;
>  
> @@ -2587,7 +2590,7 @@ static int op_symlink(const char *src, const char *dest)
>  	*node_name = 0;
>  
>  	fs = fuse4fs_start(ff);
> -	if (!fs_can_allocate(ff, 1)) {
> +	if (!fuse4fs_can_allocate(ff, 1)) {
>  		ret = -ENOSPC;
>  		goto out2;
>  	}
> @@ -2599,7 +2602,7 @@ static int op_symlink(const char *src, const char *dest)
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -2746,7 +2749,7 @@ static int op_rename(const char *from, const char *to,
>  	FUSE4FS_CHECK_CONTEXT(ff);
>  	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
>  	fs = fuse4fs_start(ff);
> -	if (!fs_can_allocate(ff, 5)) {
> +	if (!fuse4fs_can_allocate(ff, 5)) {
>  		ret = -ENOSPC;
>  		goto out;
>  	}
> @@ -2772,12 +2775,12 @@ static int op_rename(const char *from, const char *to,
>  		goto out;
>  	}
>  
> -	ret = check_inum_access(ff, from_ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, from_ino, W_OK);
>  	if (ret)
>  		goto out;
>  
>  	if (to_ino) {
> -		ret = check_inum_access(ff, to_ino, W_OK);
> +		ret = fuse4fs_inum_access(ff, to_ino, W_OK);
>  		if (ret)
>  			goto out;
>  	}
> @@ -2815,7 +2818,7 @@ static int op_rename(const char *from, const char *to,
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, from_dir_ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -2840,7 +2843,7 @@ static int op_rename(const char *from, const char *to,
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, to_dir_ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -2992,7 +2995,7 @@ static int op_link(const char *src, const char *dest)
>  	*node_name = 0;
>  
>  	fs = fuse4fs_start(ff);
> -	if (!fs_can_allocate(ff, 2)) {
> +	if (!fuse4fs_can_allocate(ff, 2)) {
>  		ret = -ENOSPC;
>  		goto out2;
>  	}
> @@ -3005,7 +3008,7 @@ static int op_link(const char *src, const char *dest)
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -3021,7 +3024,7 @@ static int op_link(const char *src, const char *dest)
>  		goto out2;
>  	}
>  
> -	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> +	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -3066,7 +3069,7 @@ static int op_link(const char *src, const char *dest)
>  }
>  
>  /* Obtain group ids of the process that sent us a command(?) */
> -static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> +static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
>  {
>  	ext2_filsys fs = ff->fs;
>  	errcode_t err;
> @@ -3111,8 +3114,8 @@ static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
>   * 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)
> +static int fuse4fs_in_file_group(struct fuse_context *ctxt,
> +				 const struct ext2_inode_large *inode)
>  {
>  	struct fuse4fs *ff = fuse4fs_get();
>  	gid_t *gids = NULL;
> @@ -3124,7 +3127,7 @@ static int in_file_group(struct fuse_context *ctxt,
>  	if (ctxt->gid == gid)
>  		return 1;
>  
> -	ret = get_req_groups(ff, &gids, &nr_gids);
> +	ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
>  	if (ret == -ENOENT) {
>  		/* magic return code for "could not get caller group info" */
>  		return 0;
> @@ -3167,11 +3170,11 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
>  		goto out;
>  	}
>  
> -	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> +	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
>  	if (ret)
>  		goto out;
>  
> -	if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> +	if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
>  		ret = -EPERM;
>  		goto out;
>  	}
> @@ -3181,8 +3184,8 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
>  	 * of the user's groups, but FUSE only tells us about the primary
>  	 * group.
>  	 */
> -	if (!is_superuser(ff, ctxt)) {
> -		ret = in_file_group(ctxt, &inode);
> +	if (!fuse4fs_is_superuser(ff, ctxt)) {
> +		ret = fuse4fs_in_file_group(ctxt, &inode);
>  		if (ret < 0)
>  			goto out;
>  
> @@ -3236,14 +3239,14 @@ 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);
> +	ret = fuse4fs_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. */
> -		if (want_check_owner(ff, ctxt) &&
> +		if (fuse4fs_want_check_owner(ff, ctxt) &&
>  		    !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
>  			ret = -EPERM;
>  			goto out;
> @@ -3253,7 +3256,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
>  
>  	if (group != (gid_t) ~0) {
>  		/* Only root or the owner get to change GID. */
> -		if (want_check_owner(ff, ctxt) &&
> +		if (fuse4fs_want_check_owner(ff, ctxt) &&
>  		    inode_uid(inode) != ctxt->uid) {
>  			ret = -EPERM;
>  			goto out;
> @@ -3363,7 +3366,7 @@ static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
>  		goto out;
>  	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
>  
> -	ret = check_inum_access(ff, ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, ino, W_OK);
>  	if (ret)
>  		goto out;
>  
> @@ -3445,7 +3448,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
>  	}
>  	dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
>  
> -	ret = check_inum_access(ff, file->ino, check);
> +	ret = fuse4fs_inum_access(ff, file->ino, check);
>  	if (ret) {
>  		/*
>  		 * In a regular (Linux) fs driver, the kernel will open
> @@ -3457,7 +3460,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
>  		 * also employ undocumented hacks (see above).
>  		 */
>  		if (check == R_OK) {
> -			ret = check_inum_access(ff, file->ino, X_OK);
> +			ret = fuse4fs_inum_access(ff, file->ino, X_OK);
>  			if (ret)
>  				goto out;
>  			check = X_OK;
> @@ -3568,7 +3571,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
>  		goto out;
>  	}
>  
> -	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> +	if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
>  		ret = -ENOSPC;
>  		goto out;
>  	}
> @@ -3768,11 +3771,11 @@ 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(ff, ino, R_OK);
> +	ret = fuse4fs_inum_access(ff, ino, R_OK);
>  	if (ret)
>  		goto out;
>  
> -	ret = __getxattr(ff, ino, key, &ptr, &plen);
> +	ret = fuse4fs_getxattr(ff, ino, key, &ptr, &plen);
>  	if (ret)
>  		goto out;
>  
> @@ -3838,7 +3841,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(ff, ino, R_OK);
> +	ret = fuse4fs_inum_access(ff, ino, R_OK);
>  	if (ret)
>  		goto out;
>  
> @@ -3919,7 +3922,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(ff, ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, ino, W_OK);
>  	if (ret == -EACCES) {
>  		ret = -EPERM;
>  		goto out;
> @@ -4008,7 +4011,7 @@ static int op_removexattr(const char *path, const char *key)
>  		goto out;
>  	}
>  
> -	if (!fs_can_allocate(ff, 1)) {
> +	if (!fuse4fs_can_allocate(ff, 1)) {
>  		ret = -ENOSPC;
>  		goto out;
>  	}
> @@ -4020,7 +4023,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(ff, ino, W_OK);
> +	ret = fuse4fs_inum_access(ff, ino, W_OK);
>  	if (ret)
>  		goto out;
>  
> @@ -4207,7 +4210,7 @@ static int op_access(const char *path, int mask)
>  		goto out;
>  	}
>  
> -	ret = check_inum_access(ff, ino, mask);
> +	ret = fuse4fs_inum_access(ff, ino, mask);
>  	if (ret)
>  		goto out;
>  
> @@ -4247,7 +4250,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
>  	*node_name = 0;
>  
>  	fs = fuse4fs_start(ff);
> -	if (!fs_can_allocate(ff, 1)) {
> +	if (!fuse4fs_can_allocate(ff, 1)) {
>  		ret = -ENOSPC;
>  		goto out2;
>  	}
> @@ -4259,7 +4262,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
>  		goto out2;
>  	}
>  
> -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
>  	if (ret)
>  		goto out2;
>  
> @@ -4326,7 +4329,7 @@ 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, inode.i_mode);
> +	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
>  	if (ret)
>  		goto out2;
>  
> @@ -4374,7 +4377,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
>  	 */
>  	if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
>  		access |= A_OK;
> -	ret = check_inum_access(ff, ino, access);
> +	ret = fuse4fs_inum_access(ff, ino, access);
>  	if (ret)
>  		goto out;
>  
> @@ -4459,7 +4462,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
>  	if (err)
>  		return translate_error(fs, fh->ino, err);
>  
> -	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> +	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
>  		return -EPERM;
>  
>  	ret = set_iflags(&inode, flags);
> @@ -4508,7 +4511,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
>  	if (err)
>  		return translate_error(fs, fh->ino, err);
>  
> -	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> +	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
>  		return -EPERM;
>  
>  	inode.i_generation = generation;
> @@ -4633,7 +4636,7 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
>  	if (err)
>  		return translate_error(fs, fh->ino, err);
>  
> -	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> +	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
>  		return -EPERM;
>  
>  	ret = set_xflags(&inode, fsx->fsx_xflags);
> @@ -4762,7 +4765,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
>  	struct fuse_context *ctxt = fuse_get_context();
>  	ext2_filsys fs = ff->fs;
>  
> -	if (!is_superuser(ff, ctxt))
> +	if (!fuse4fs_is_superuser(ff, ctxt))
>  		return -EPERM;
>  
>  	err_printf(ff, "%s.\n", _("shut down requested"));
> @@ -4884,7 +4887,7 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
>  		   (unsigned long long)len,
>  		   (unsigned long long)start,
>  		   (unsigned long long)end);
> -	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> +	if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
>  		return -ENOSPC;
>  
>  	err = fuse4fs_read_inode(fs, fh->ino, &inode);
> @@ -4927,9 +4930,9 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
>  	return err;
>  }
>  
> -static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> -				    struct ext2_inode_large *inode,
> -				    off_t offset, off_t len, char **buf)
> +static errcode_t fuse4fs_zero_middle(struct fuse4fs *ff, ext2_ino_t ino,
> +				     struct ext2_inode_large *inode,
> +				     off_t offset, off_t len, char **buf)
>  {
>  	ext2_filsys fs = ff->fs;
>  	blk64_t blk;
> @@ -4963,9 +4966,9 @@ static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
>  	return io_channel_write_blk64(fs->io, blk, 1, *buf);
>  }
>  
> -static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
> -				  struct ext2_inode_large *inode, off_t offset,
> -				  int clean_before, char **buf)
> +static errcode_t fuse4fs_zero_edge(struct fuse4fs *ff, ext2_ino_t ino,
> +				   struct ext2_inode_large *inode, off_t offset,
> +				   int clean_before, char **buf)
>  {
>  	ext2_filsys fs = ff->fs;
>  	blk64_t blk;
> @@ -5056,13 +5059,13 @@ static int fuse4fs_punch_range(struct fuse4fs *ff,
>  
>  	/* Zero everything before the first block and after the last block */
>  	if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
> -		err = clean_block_middle(ff, fh->ino, &inode, offset,
> +		err = fuse4fs_zero_middle(ff, fh->ino, &inode, offset,
>  					 len, &buf);
>  	else {
> -		err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
> +		err = fuse4fs_zero_edge(ff, fh->ino, &inode, offset, 0, &buf);
>  		if (!err)
> -			err = clean_block_edge(ff, fh->ino, &inode,
> -					       offset + len, 1, &buf);
> +			err = fuse4fs_zero_edge(ff, fh->ino, &inode,
> +						offset + len, 1, &buf);
>  	}
>  	if (buf)
>  		ext2fs_free_mem(&buf);
> 

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

* Re: [PATCH 04/23] fuse4fs: namespace some helpers
  2025-11-07  8:09     ` Amir Goldstein
@ 2025-11-08  0:25       ` Darrick J. Wong
  0 siblings, 0 replies; 84+ messages in thread
From: Darrick J. Wong @ 2025-11-08  0:25 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: tytso, linux-ext4

On Fri, Nov 07, 2025 at 09:09:58AM +0100, Amir Goldstein wrote:
> On Thu, Nov 06, 2025 at 02:44:07PM -0800, Darrick J. Wong wrote:
> > From: Darrick J. Wong <djwong@kernel.org>
> > 
> > Prepend "fuse4fs_" to all helper functions that take a struct fuse4fs
> > object pointer.
> > 
> > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > ---
> >  fuse4fs/fuse4fs.c |  177 +++++++++++++++++++++++++++--------------------------
> >  1 file changed, 90 insertions(+), 87 deletions(-)
> > 
> > 
> > diff --git a/fuse4fs/fuse4fs.c b/fuse4fs/fuse4fs.c
> > index daf22e0fe7fde5..2ef5ad60163639 100644
> > --- a/fuse4fs/fuse4fs.c
> > +++ b/fuse4fs/fuse4fs.c
> > @@ -2,6 +2,7 @@
> >   * fuse4fs.c - FUSE low-level server for e2fsprogs.
> >   *
> >   * Copyright (C) 2014-2025 Oracle.
> > + * Copyright (C) 2025 CTERA Networks.
> 
> I think this belongs to next patch :)

Moved; thanks for the feedback!

--D

> Thanks,
> Amir.
> 
> >   *
> >   * %Begin-Header%
> >   * This file may be redistributed under the terms of the GNU Public
> > @@ -852,7 +853,7 @@ static int ext2_file_type(unsigned int mode)
> >  	return 0;
> >  }
> >  
> > -static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> > +static int fuse4fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	blk64_t reserved;
> > @@ -879,21 +880,22 @@ static int fs_can_allocate(struct fuse4fs *ff, blk64_t num)
> >  	return ext2fs_free_blocks_count(fs->super) > reserved + num;
> >  }
> >  
> > -static int fuse4fs_is_writeable(struct fuse4fs *ff)
> > +static int fuse4fs_is_writeable(const struct fuse4fs *ff)
> >  {
> >  	return ff->opstate == F4OP_WRITABLE &&
> >  		(ff->fs->super->s_error_count == 0);
> >  }
> >  
> > -static inline int is_superuser(struct fuse4fs *ff, struct fuse_context *ctxt)
> > +static inline int fuse4fs_is_superuser(struct fuse4fs *ff,
> > +				       const struct fuse_context *ctxt)
> >  {
> >  	if (ff->fakeroot)
> >  		return 1;
> >  	return ctxt->uid == 0;
> >  }
> >  
> > -static inline int want_check_owner(struct fuse4fs *ff,
> > -				   struct fuse_context *ctxt)
> > +static inline int fuse4fs_want_check_owner(struct fuse4fs *ff,
> > +					   const struct fuse_context *ctxt)
> >  {
> >  	/*
> >  	 * The kernel is responsible for access control, so we allow anything
> > @@ -901,14 +903,14 @@ static inline int want_check_owner(struct fuse4fs *ff,
> >  	 */
> >  	if (ff->kernel)
> >  		return 0;
> > -	return !is_superuser(ff, ctxt);
> > +	return !fuse4fs_is_superuser(ff, ctxt);
> >  }
> >  
> >  /* Test for append permission */
> >  #define A_OK	16
> >  
> > -static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> > -			       const struct ext2_inode *inode, int mask)
> > +static int fuse4fs_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> > +				 const struct ext2_inode *inode, int mask)
> >  {
> >  	EXT2FS_BUILD_BUG_ON((A_OK & (R_OK | W_OK | X_OK | F_OK)) != 0);
> >  
> > @@ -936,7 +938,7 @@ static int check_iflags_access(struct fuse4fs *ff, ext2_ino_t ino,
> >  	return 0;
> >  }
> >  
> > -static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> > +static int fuse4fs_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> >  {
> >  	struct fuse_context *ctxt = fuse_get_context();
> >  	ext2_filsys fs = ff->fs;
> > @@ -968,7 +970,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> >  	if (mask == 0)
> >  		return 0;
> >  
> > -	ret = check_iflags_access(ff, ino, &inode, mask);
> > +	ret = fuse4fs_iflags_access(ff, ino, &inode, mask);
> >  	if (ret)
> >  		return ret;
> >  
> > @@ -977,7 +979,7 @@ static int check_inum_access(struct fuse4fs *ff, ext2_ino_t ino, int mask)
> >  		return 0;
> >  
> >  	/* Figure out what root's allowed to do */
> > -	if (is_superuser(ff, ctxt)) {
> > +	if (fuse4fs_is_superuser(ff, ctxt)) {
> >  		/* Non-file access always ok */
> >  		if (!LINUX_S_ISREG(inode.i_mode))
> >  			return 0;
> > @@ -1783,8 +1785,8 @@ static int op_readlink(const char *path, char *buf, size_t len)
> >  	return ret;
> >  }
> >  
> > -static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> > -		      void **value, size_t *value_len)
> > +static int fuse4fs_getxattr(struct fuse4fs *ff, ext2_ino_t ino,
> > +			    const char *name, void **value, size_t *value_len)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	struct ext2_xattr_handle *h;
> > @@ -1814,8 +1816,8 @@ static int __getxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> >  	return ret;
> >  }
> >  
> > -static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> > -		      void *value, size_t valuelen)
> > +static int fuse4fs_setxattr(struct fuse4fs *ff, ext2_ino_t ino,
> > +			    const char *name, void *value, size_t valuelen)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	struct ext2_xattr_handle *h;
> > @@ -1845,8 +1847,8 @@ static int __setxattr(struct fuse4fs *ff, ext2_ino_t ino, const char *name,
> >  	return ret;
> >  }
> >  
> > -static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> > -				  ext2_ino_t child, mode_t mode)
> > +static int fuse4fs_propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> > +					  ext2_ino_t child, mode_t mode)
> >  {
> >  	void *def;
> >  	size_t deflen;
> > @@ -1855,8 +1857,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> >  	if (!ff->acl || S_ISDIR(mode))
> >  		return 0;
> >  
> > -	ret = __getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> > -			 &deflen);
> > +	ret = fuse4fs_getxattr(ff, parent, XATTR_NAME_POSIX_ACL_DEFAULT, &def,
> > +			       &deflen);
> >  	switch (ret) {
> >  	case -ENODATA:
> >  	case -ENOENT:
> > @@ -1868,7 +1870,8 @@ static int propagate_default_acls(struct fuse4fs *ff, ext2_ino_t parent,
> >  		return ret;
> >  	}
> >  
> > -	ret = __setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def, deflen);
> > +	ret = fuse4fs_setxattr(ff, child, XATTR_NAME_POSIX_ACL_DEFAULT, def,
> > +			       deflen);
> >  	ext2fs_free_mem(&def);
> >  	return ret;
> >  }
> > @@ -1997,7 +2000,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> >  	*node_name = 0;
> >  
> >  	fs = fuse4fs_start(ff);
> > -	if (!fs_can_allocate(ff, 2)) {
> > +	if (!fuse4fs_can_allocate(ff, 2)) {
> >  		ret = -ENOSPC;
> >  		goto out2;
> >  	}
> > @@ -2009,7 +2012,7 @@ static int op_mknod(const char *path, mode_t mode, dev_t dev)
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> > +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -2079,7 +2082,7 @@ 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, inode.i_mode);
> > +	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -2127,7 +2130,7 @@ static int op_mkdir(const char *path, mode_t mode)
> >  	*node_name = 0;
> >  
> >  	fs = fuse4fs_start(ff);
> > -	if (!fs_can_allocate(ff, 1)) {
> > +	if (!fuse4fs_can_allocate(ff, 1)) {
> >  		ret = -ENOSPC;
> >  		goto out2;
> >  	}
> > @@ -2139,7 +2142,7 @@ static int op_mkdir(const char *path, mode_t mode)
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> > +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -2212,7 +2215,7 @@ static int op_mkdir(const char *path, mode_t mode)
> >  		goto out3;
> >  	}
> >  
> > -	ret = propagate_default_acls(ff, parent, child, inode.i_mode);
> > +	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> >  	if (ret)
> >  		goto out3;
> >  
> > @@ -2253,7 +2256,7 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
> >  		base_name = filename;
> >  	}
> >  
> > -	ret = check_inum_access(ff, dir, W_OK);
> > +	ret = fuse4fs_inum_access(ff, dir, W_OK);
> >  	if (ret) {
> >  		free(filename);
> >  		return ret;
> > @@ -2275,8 +2278,8 @@ static int fuse4fs_unlink(struct fuse4fs *ff, const char *path,
> >  	return 0;
> >  }
> >  
> > -static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> > -			    struct ext2_inode_large *inode)
> > +static int fuse4fs_remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> > +				    struct ext2_inode_large *inode)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	struct ext2_xattr_handle *h;
> > @@ -2320,7 +2323,7 @@ static int remove_ea_inodes(struct fuse4fs *ff, ext2_ino_t ino,
> >  	return 0;
> >  }
> >  
> > -static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> > +static int fuse4fs_remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	errcode_t err;
> > @@ -2366,7 +2369,7 @@ static int remove_inode(struct fuse4fs *ff, ext2_ino_t ino)
> >  		goto write_out;
> >  
> >  	if (ext2fs_has_feature_ea_inode(fs->super)) {
> > -		ret = remove_ea_inodes(ff, ino, &inode);
> > +		ret = fuse4fs_remove_ea_inodes(ff, ino, &inode);
> >  		if (ret)
> >  			return ret;
> >  	}
> > @@ -2407,7 +2410,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
> >  		goto out;
> >  	}
> >  
> > -	ret = check_inum_access(ff, ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, ino, W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -2415,7 +2418,7 @@ static int __op_unlink(struct fuse4fs *ff, const char *path)
> >  	if (ret)
> >  		goto out;
> >  
> > -	ret = remove_inode(ff, ino);
> > +	ret = fuse4fs_remove_inode(ff, ino);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -2483,7 +2486,7 @@ static int __op_rmdir(struct fuse4fs *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);
> > +	ret = fuse4fs_inum_access(ff, child, W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -2502,7 +2505,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> >  		goto out;
> >  	}
> >  
> > -	ret = check_inum_access(ff, rds.parent, W_OK);
> > +	ret = fuse4fs_inum_access(ff, rds.parent, W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -2514,7 +2517,7 @@ static int __op_rmdir(struct fuse4fs *ff, const char *path)
> >  	ret = fuse4fs_unlink(ff, path, &parent);
> >  	if (ret)
> >  		goto out;
> > -	ret = remove_inode(ff, child);
> > +	ret = fuse4fs_remove_inode(ff, child);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -2587,7 +2590,7 @@ static int op_symlink(const char *src, const char *dest)
> >  	*node_name = 0;
> >  
> >  	fs = fuse4fs_start(ff);
> > -	if (!fs_can_allocate(ff, 1)) {
> > +	if (!fuse4fs_can_allocate(ff, 1)) {
> >  		ret = -ENOSPC;
> >  		goto out2;
> >  	}
> > @@ -2599,7 +2602,7 @@ static int op_symlink(const char *src, const char *dest)
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> > +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -2746,7 +2749,7 @@ static int op_rename(const char *from, const char *to,
> >  	FUSE4FS_CHECK_CONTEXT(ff);
> >  	dbg_printf(ff, "%s: renaming %s to %s\n", __func__, from, to);
> >  	fs = fuse4fs_start(ff);
> > -	if (!fs_can_allocate(ff, 5)) {
> > +	if (!fuse4fs_can_allocate(ff, 5)) {
> >  		ret = -ENOSPC;
> >  		goto out;
> >  	}
> > @@ -2772,12 +2775,12 @@ static int op_rename(const char *from, const char *to,
> >  		goto out;
> >  	}
> >  
> > -	ret = check_inum_access(ff, from_ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, from_ino, W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> >  	if (to_ino) {
> > -		ret = check_inum_access(ff, to_ino, W_OK);
> > +		ret = fuse4fs_inum_access(ff, to_ino, W_OK);
> >  		if (ret)
> >  			goto out;
> >  	}
> > @@ -2815,7 +2818,7 @@ static int op_rename(const char *from, const char *to,
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, from_dir_ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, from_dir_ino, W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -2840,7 +2843,7 @@ static int op_rename(const char *from, const char *to,
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, to_dir_ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, to_dir_ino, W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -2992,7 +2995,7 @@ static int op_link(const char *src, const char *dest)
> >  	*node_name = 0;
> >  
> >  	fs = fuse4fs_start(ff);
> > -	if (!fs_can_allocate(ff, 2)) {
> > +	if (!fuse4fs_can_allocate(ff, 2)) {
> >  		ret = -ENOSPC;
> >  		goto out2;
> >  	}
> > @@ -3005,7 +3008,7 @@ static int op_link(const char *src, const char *dest)
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> > +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -3021,7 +3024,7 @@ static int op_link(const char *src, const char *dest)
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > +	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -3066,7 +3069,7 @@ static int op_link(const char *src, const char *dest)
> >  }
> >  
> >  /* Obtain group ids of the process that sent us a command(?) */
> > -static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> > +static int fuse4fs_get_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	errcode_t err;
> > @@ -3111,8 +3114,8 @@ static int get_req_groups(struct fuse4fs *ff, gid_t **gids, size_t *nr_gids)
> >   * 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)
> > +static int fuse4fs_in_file_group(struct fuse_context *ctxt,
> > +				 const struct ext2_inode_large *inode)
> >  {
> >  	struct fuse4fs *ff = fuse4fs_get();
> >  	gid_t *gids = NULL;
> > @@ -3124,7 +3127,7 @@ static int in_file_group(struct fuse_context *ctxt,
> >  	if (ctxt->gid == gid)
> >  		return 1;
> >  
> > -	ret = get_req_groups(ff, &gids, &nr_gids);
> > +	ret = fuse4fs_get_groups(ff, &gids, &nr_gids);
> >  	if (ret == -ENOENT) {
> >  		/* magic return code for "could not get caller group info" */
> >  		return 0;
> > @@ -3167,11 +3170,11 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
> >  		goto out;
> >  	}
> >  
> > -	ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> > +	ret = fuse4fs_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > -	if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> > +	if (fuse4fs_want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
> >  		ret = -EPERM;
> >  		goto out;
> >  	}
> > @@ -3181,8 +3184,8 @@ static int op_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
> >  	 * of the user's groups, but FUSE only tells us about the primary
> >  	 * group.
> >  	 */
> > -	if (!is_superuser(ff, ctxt)) {
> > -		ret = in_file_group(ctxt, &inode);
> > +	if (!fuse4fs_is_superuser(ff, ctxt)) {
> > +		ret = fuse4fs_in_file_group(ctxt, &inode);
> >  		if (ret < 0)
> >  			goto out;
> >  
> > @@ -3236,14 +3239,14 @@ 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);
> > +	ret = fuse4fs_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. */
> > -		if (want_check_owner(ff, ctxt) &&
> > +		if (fuse4fs_want_check_owner(ff, ctxt) &&
> >  		    !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
> >  			ret = -EPERM;
> >  			goto out;
> > @@ -3253,7 +3256,7 @@ static int op_chown(const char *path, uid_t owner, gid_t group,
> >  
> >  	if (group != (gid_t) ~0) {
> >  		/* Only root or the owner get to change GID. */
> > -		if (want_check_owner(ff, ctxt) &&
> > +		if (fuse4fs_want_check_owner(ff, ctxt) &&
> >  		    inode_uid(inode) != ctxt->uid) {
> >  			ret = -EPERM;
> >  			goto out;
> > @@ -3363,7 +3366,7 @@ static int op_truncate(const char *path, off_t len, struct fuse_file_info *fi)
> >  		goto out;
> >  	dbg_printf(ff, "%s: ino=%d len=%jd\n", __func__, ino, (intmax_t) len);
> >  
> > -	ret = check_inum_access(ff, ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, ino, W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -3445,7 +3448,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
> >  	}
> >  	dbg_printf(ff, "%s: ino=%d\n", __func__, file->ino);
> >  
> > -	ret = check_inum_access(ff, file->ino, check);
> > +	ret = fuse4fs_inum_access(ff, file->ino, check);
> >  	if (ret) {
> >  		/*
> >  		 * In a regular (Linux) fs driver, the kernel will open
> > @@ -3457,7 +3460,7 @@ static int __op_open(struct fuse4fs *ff, const char *path,
> >  		 * also employ undocumented hacks (see above).
> >  		 */
> >  		if (check == R_OK) {
> > -			ret = check_inum_access(ff, file->ino, X_OK);
> > +			ret = fuse4fs_inum_access(ff, file->ino, X_OK);
> >  			if (ret)
> >  				goto out;
> >  			check = X_OK;
> > @@ -3568,7 +3571,7 @@ static int op_write(const char *path EXT2FS_ATTR((unused)),
> >  		goto out;
> >  	}
> >  
> > -	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> > +	if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len))) {
> >  		ret = -ENOSPC;
> >  		goto out;
> >  	}
> > @@ -3768,11 +3771,11 @@ 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(ff, ino, R_OK);
> > +	ret = fuse4fs_inum_access(ff, ino, R_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > -	ret = __getxattr(ff, ino, key, &ptr, &plen);
> > +	ret = fuse4fs_getxattr(ff, ino, key, &ptr, &plen);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -3838,7 +3841,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(ff, ino, R_OK);
> > +	ret = fuse4fs_inum_access(ff, ino, R_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -3919,7 +3922,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(ff, ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, ino, W_OK);
> >  	if (ret == -EACCES) {
> >  		ret = -EPERM;
> >  		goto out;
> > @@ -4008,7 +4011,7 @@ static int op_removexattr(const char *path, const char *key)
> >  		goto out;
> >  	}
> >  
> > -	if (!fs_can_allocate(ff, 1)) {
> > +	if (!fuse4fs_can_allocate(ff, 1)) {
> >  		ret = -ENOSPC;
> >  		goto out;
> >  	}
> > @@ -4020,7 +4023,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(ff, ino, W_OK);
> > +	ret = fuse4fs_inum_access(ff, ino, W_OK);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -4207,7 +4210,7 @@ static int op_access(const char *path, int mask)
> >  		goto out;
> >  	}
> >  
> > -	ret = check_inum_access(ff, ino, mask);
> > +	ret = fuse4fs_inum_access(ff, ino, mask);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -4247,7 +4250,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> >  	*node_name = 0;
> >  
> >  	fs = fuse4fs_start(ff);
> > -	if (!fs_can_allocate(ff, 1)) {
> > +	if (!fuse4fs_can_allocate(ff, 1)) {
> >  		ret = -ENOSPC;
> >  		goto out2;
> >  	}
> > @@ -4259,7 +4262,7 @@ static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
> >  		goto out2;
> >  	}
> >  
> > -	ret = check_inum_access(ff, parent, A_OK | W_OK);
> > +	ret = fuse4fs_inum_access(ff, parent, A_OK | W_OK);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -4326,7 +4329,7 @@ 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, inode.i_mode);
> > +	ret = fuse4fs_propagate_default_acls(ff, parent, child, inode.i_mode);
> >  	if (ret)
> >  		goto out2;
> >  
> > @@ -4374,7 +4377,7 @@ static int op_utimens(const char *path, const struct timespec ctv[2],
> >  	 */
> >  	if (ctv[0].tv_nsec == UTIME_NOW && ctv[1].tv_nsec == UTIME_NOW)
> >  		access |= A_OK;
> > -	ret = check_inum_access(ff, ino, access);
> > +	ret = fuse4fs_inum_access(ff, ino, access);
> >  	if (ret)
> >  		goto out;
> >  
> > @@ -4459,7 +4462,7 @@ static int ioctl_setflags(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> >  	if (err)
> >  		return translate_error(fs, fh->ino, err);
> >  
> > -	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > +	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> >  		return -EPERM;
> >  
> >  	ret = set_iflags(&inode, flags);
> > @@ -4508,7 +4511,7 @@ static int ioctl_setversion(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> >  	if (err)
> >  		return translate_error(fs, fh->ino, err);
> >  
> > -	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > +	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> >  		return -EPERM;
> >  
> >  	inode.i_generation = generation;
> > @@ -4633,7 +4636,7 @@ static int ioctl_fssetxattr(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> >  	if (err)
> >  		return translate_error(fs, fh->ino, err);
> >  
> > -	if (want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> > +	if (fuse4fs_want_check_owner(ff, ctxt) && inode_uid(inode) != ctxt->uid)
> >  		return -EPERM;
> >  
> >  	ret = set_xflags(&inode, fsx->fsx_xflags);
> > @@ -4762,7 +4765,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, struct fuse4fs_file_handle *fh,
> >  	struct fuse_context *ctxt = fuse_get_context();
> >  	ext2_filsys fs = ff->fs;
> >  
> > -	if (!is_superuser(ff, ctxt))
> > +	if (!fuse4fs_is_superuser(ff, ctxt))
> >  		return -EPERM;
> >  
> >  	err_printf(ff, "%s.\n", _("shut down requested"));
> > @@ -4884,7 +4887,7 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
> >  		   (unsigned long long)len,
> >  		   (unsigned long long)start,
> >  		   (unsigned long long)end);
> > -	if (!fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> > +	if (!fuse4fs_can_allocate(ff, FUSE4FS_B_TO_FSB(ff, len)))
> >  		return -ENOSPC;
> >  
> >  	err = fuse4fs_read_inode(fs, fh->ino, &inode);
> > @@ -4927,9 +4930,9 @@ static int fuse4fs_allocate_range(struct fuse4fs *ff,
> >  	return err;
> >  }
> >  
> > -static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> > -				    struct ext2_inode_large *inode,
> > -				    off_t offset, off_t len, char **buf)
> > +static errcode_t fuse4fs_zero_middle(struct fuse4fs *ff, ext2_ino_t ino,
> > +				     struct ext2_inode_large *inode,
> > +				     off_t offset, off_t len, char **buf)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	blk64_t blk;
> > @@ -4963,9 +4966,9 @@ static errcode_t clean_block_middle(struct fuse4fs *ff, ext2_ino_t ino,
> >  	return io_channel_write_blk64(fs->io, blk, 1, *buf);
> >  }
> >  
> > -static errcode_t clean_block_edge(struct fuse4fs *ff, ext2_ino_t ino,
> > -				  struct ext2_inode_large *inode, off_t offset,
> > -				  int clean_before, char **buf)
> > +static errcode_t fuse4fs_zero_edge(struct fuse4fs *ff, ext2_ino_t ino,
> > +				   struct ext2_inode_large *inode, off_t offset,
> > +				   int clean_before, char **buf)
> >  {
> >  	ext2_filsys fs = ff->fs;
> >  	blk64_t blk;
> > @@ -5056,13 +5059,13 @@ static int fuse4fs_punch_range(struct fuse4fs *ff,
> >  
> >  	/* Zero everything before the first block and after the last block */
> >  	if (FUSE4FS_B_TO_FSBT(ff, offset) == FUSE4FS_B_TO_FSBT(ff, offset + len))
> > -		err = clean_block_middle(ff, fh->ino, &inode, offset,
> > +		err = fuse4fs_zero_middle(ff, fh->ino, &inode, offset,
> >  					 len, &buf);
> >  	else {
> > -		err = clean_block_edge(ff, fh->ino, &inode, offset, 0, &buf);
> > +		err = fuse4fs_zero_edge(ff, fh->ino, &inode, offset, 0, &buf);
> >  		if (!err)
> > -			err = clean_block_edge(ff, fh->ino, &inode,
> > -					       offset + len, 1, &buf);
> > +			err = fuse4fs_zero_edge(ff, fh->ino, &inode,
> > +						offset + len, 1, &buf);
> >  	}
> >  	if (buf)
> >  		ext2fs_free_mem(&buf);
> > 

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

end of thread, other threads:[~2025-11-08  0:25 UTC | newest]

Thread overview: 84+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-06 22:14 [PATCHBOMB 1.48] fuse2fs: new features, new server Darrick J. Wong
2025-11-06 22:27 ` [PATCHSET 1/9] fuse2fs: fix locking problems Darrick J. Wong
2025-11-06 22:30   ` [PATCH 1/4] libext2fs: add POSIX advisory locking to the unix IO manager Darrick J. Wong
2025-11-06 22:30   ` [PATCH 2/4] fuse2fs: try to lock filesystem image files before using them Darrick J. Wong
2025-11-06 22:30   ` [PATCH 3/4] fuse2fs: quiet down write-protect warning Darrick J. Wong
2025-11-06 22:31   ` [PATCH 4/4] fuse2fs: try to grab block device O_EXCL repeatedly Darrick J. Wong
2025-11-06 22:28 ` [PATCHSET 2/9] fuse2fs: add some easy new features Darrick J. Wong
2025-11-06 22:31   ` [PATCH 01/19] libext2fs: initialize htree when expanding directory Darrick J. Wong
2025-11-06 22:31   ` [PATCH 02/19] libext2fs: create link count adjustment helpers for dir_nlink Darrick J. Wong
2025-11-06 22:31   ` [PATCH 03/19] libext2fs: fix ext2fs_mmp_update Darrick J. Wong
2025-11-06 22:32   ` [PATCH 04/19] libext2fs: refactor aligned MMP buffer allocation Darrick J. Wong
2025-11-06 22:32   ` [PATCH 05/19] libext2fs: always use ext2fs_mmp_get_mem to allocate fs->mmp_buf Darrick J. Wong
2025-11-06 22:32   ` [PATCH 06/19] fuse2fs: check root directory while mounting Darrick J. Wong
2025-11-06 22:32   ` [PATCH 07/19] fuse2fs: read bitmaps asynchronously during initialization Darrick J. Wong
2025-11-06 22:33   ` [PATCH 08/19] fuse2fs: use file handles when possible Darrick J. Wong
2025-11-06 22:33   ` [PATCH 09/19] fuse2fs: implement dir seeking Darrick J. Wong
2025-11-06 22:33   ` [PATCH 10/19] fuse2fs: implement readdirplus Darrick J. Wong
2025-11-06 22:34   ` [PATCH 11/19] fuse2fs: implement dirsync mode Darrick J. Wong
2025-11-06 22:34   ` [PATCH 12/19] fuse2fs: only flush O_SYNC files on close Darrick J. Wong
2025-11-06 22:34   ` [PATCH 13/19] fuse2fs: improve want_extra_isize handling Darrick J. Wong
2025-11-06 22:34   ` [PATCH 14/19] fuse2fs: cache symlink targets in the kernel Darrick J. Wong
2025-11-06 22:35   ` [PATCH 15/19] fuse2fs: constrain worker thread count Darrick J. Wong
2025-11-06 22:35   ` [PATCH 16/19] fuse2fs: improve error handling behaviors Darrick J. Wong
2025-11-06 22:35   ` [PATCH 17/19] fuse2fs: fix link count overflows on dir_nlink filesystems Darrick J. Wong
2025-11-06 22:35   ` [PATCH 18/19] libsupport: add background thread manager Darrick J. Wong
2025-11-06 22:36   ` [PATCH 19/19] fuse2fs: implement MMP updates Darrick J. Wong
2025-11-06 22:28 ` [PATCHSET 3/9] fuse2fs: clean up operation startup Darrick J. Wong
2025-11-06 22:36   ` [PATCH 1/9] fuse2fs: rework FUSE2FS_CHECK_CONTEXT not to rely on global_fs Darrick J. Wong
2025-11-06 22:36   ` [PATCH 2/9] fuse2fs: rework checking file handles Darrick J. Wong
2025-11-06 22:36   ` [PATCH 3/9] fuse2fs: rework fallocate file handle extraction Darrick J. Wong
2025-11-06 22:37   ` [PATCH 4/9] fuse2fs: consolidate file handle checking in op_ioctl Darrick J. Wong
2025-11-06 22:37   ` [PATCH 5/9] fuse2fs: move fs assignment closer to locking the bfl Darrick J. Wong
2025-11-06 22:37   ` [PATCH 6/9] fuse2fs: clean up operation startup Darrick J. Wong
2025-11-06 22:37   ` [PATCH 7/9] fuse2fs: clean up operation completion Darrick J. Wong
2025-11-06 22:38   ` [PATCH 8/9] fuse2fs: clean up more boilerplate Darrick J. Wong
2025-11-06 22:38   ` [PATCH 9/9] fuse2fs: collect runtime of various operations Darrick J. Wong
2025-11-06 22:28 ` [PATCHSET 4/9] fuse2fs: refactor unmount code Darrick J. Wong
2025-11-06 22:38   ` [PATCH 1/3] fuse2fs: get rid of the global_fs variable Darrick J. Wong
2025-11-06 22:39   ` [PATCH 2/3] fuse2fs: hoist lockfile code Darrick J. Wong
2025-11-06 22:39   ` [PATCH 3/3] fuse2fs: hoist unmount code from main Darrick J. Wong
2025-11-06 22:28 ` [PATCHSET 5/9] fuse2fs: refactor mount code Darrick J. Wong
2025-11-06 22:39   ` [PATCH 1/3] fuse2fs: split filesystem mounting into helper functions Darrick J. Wong
2025-11-06 22:39   ` [PATCH 2/3] fuse2fs: register as an IO flusher thread Darrick J. Wong
2025-11-06 22:40   ` [PATCH 3/3] fuse2fs: adjust OOM killer score if possible Darrick J. Wong
2025-11-06 22:29 ` [PATCHSET 6/9] fuse2fs: improve operation tracing Darrick J. Wong
2025-11-06 22:40   ` [PATCH 1/4] fuse2fs: hook library error message printing Darrick J. Wong
2025-11-06 22:40   ` [PATCH 2/4] fuse2fs: print the function name in error messages, not the file name Darrick J. Wong
2025-11-06 22:40   ` [PATCH 3/4] fuse2fs: improve tracing for file range operations Darrick J. Wong
2025-11-06 22:41   ` [PATCH 4/4] fuse2fs: record thread id in debug trace data Darrick J. Wong
2025-11-06 22:29 ` [PATCHSET 7/9] fuse2fs: better tracking of writable state Darrick J. Wong
2025-11-06 22:41   ` [PATCH 1/3] fuse2fs: pass a struct fuse2fs to fs_writeable Darrick J. Wong
2025-11-06 22:41   ` [PATCH 2/3] fuse2fs: track our own writable state Darrick J. Wong
2025-11-06 22:41   ` [PATCH 3/3] fuse2fs: enable the shutdown ioctl Darrick J. Wong
2025-11-06 22:29 ` [PATCHSET 8/9] fuse2fs: upgrade to libfuse 3.17 Darrick J. Wong
2025-11-06 22:42   ` [PATCH 1/4] fuse2fs: bump library version Darrick J. Wong
2025-11-06 22:42   ` [PATCH 2/4] fuse2fs: wrap the fuse_set_feature_flag helper for older libfuse Darrick J. Wong
2025-11-06 22:42   ` [PATCH 3/4] fuse2fs: disable nfs exports Darrick J. Wong
2025-11-06 22:43   ` [PATCH 4/4] fuse2fs: drop fuse 2.x support code Darrick J. Wong
2025-11-06 22:30 ` [PATCHSET 9/9] fuse4fs: fork a low level fuse server Darrick J. Wong
2025-11-06 22:43   ` [PATCH 01/23] fuse2fs: separate libfuse3 and fuse2fs detection in configure Darrick J. Wong
2025-11-06 22:43   ` [PATCH 02/23] fuse2fs: start porting fuse2fs to lowlevel libfuse API Darrick J. Wong
2025-11-06 22:43   ` [PATCH 03/23] debian: create new package for fuse4fs Darrick J. Wong
2025-11-06 22:44   ` [PATCH 04/23] fuse4fs: namespace some helpers Darrick J. Wong
2025-11-07  8:09     ` Amir Goldstein
2025-11-08  0:25       ` Darrick J. Wong
2025-11-06 22:44   ` [PATCH 05/23] fuse4fs: convert to low level API Darrick J. Wong
2025-11-06 22:44   ` [PATCH 06/23] libsupport: port the kernel list.h to libsupport Darrick J. Wong
2025-11-06 22:44   ` [PATCH 07/23] libsupport: add a cache Darrick J. Wong
2025-11-06 22:45   ` [PATCH 08/23] cache: disable debugging Darrick J. Wong
2025-11-06 22:45   ` [PATCH 09/23] cache: use modern list iterator macros Darrick J. Wong
2025-11-06 22:45   ` [PATCH 10/23] cache: embed struct cache in the owner Darrick J. Wong
2025-11-06 22:45   ` [PATCH 11/23] cache: pass cache pointer to callbacks Darrick J. Wong
2025-11-06 22:46   ` [PATCH 12/23] cache: pass a private data pointer through cache_walk Darrick J. Wong
2025-11-06 22:46   ` [PATCH 13/23] cache: add a helper to grab a new refcount for a cache_node Darrick J. Wong
2025-11-06 22:46   ` [PATCH 14/23] cache: return results of a cache flush Darrick J. Wong
2025-11-06 22:47   ` [PATCH 15/23] cache: add a "get only if incore" flag to cache_node_get Darrick J. Wong
2025-11-06 22:47   ` [PATCH 16/23] cache: support gradual expansion Darrick J. Wong
2025-11-06 22:47   ` [PATCH 17/23] cache: support updating maxcount and flags Darrick J. Wong
2025-11-06 22:47   ` [PATCH 18/23] cache: support channging flags Darrick J. Wong
2025-11-06 22:48   ` [PATCH 19/23] cache: implement automatic shrinking Darrick J. Wong
2025-11-06 22:48   ` [PATCH 20/23] fuse4fs: add cache to track open files Darrick J. Wong
2025-11-06 22:48   ` [PATCH 21/23] fuse4fs: use the orphaned inode list Darrick J. Wong
2025-11-06 22:48   ` [PATCH 22/23] fuse4fs: implement FUSE_TMPFILE Darrick J. Wong
2025-11-06 22:49   ` [PATCH 23/23] fuse4fs: create incore reverse orphan list 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).