e2fsprogs: containerize ext4 for safer operation
PATCH v6 1/1 e2fsprogs: containerize ext4 for safer operation
Hi everyone,
This is the sole remaining part of the gigantic patchset to enable mounting ext4 filesystems as a systemd-contained fuse server instead of in the kernel. The libfuse parts have now been merged upstream, which means that fuse4fs can now run as a non-root user, with no privilege, and no access to the network or hardware, etc. The only connection to the outside is an ephemeral AF_UNIX socket. The mount helper program the other end is a helper program that acquires resources and calls fsmount().
Why would you want to do that? Most filesystem drivers are seriously vulnerable to metadata parsing attacks, as syzbot has shown repeatedly over almost a decade of its existence. Faulty code can lead to total kernel compromise, and I think there's a very strong incentive to move all that parsing out to userspace where we can containerize the fuse server process. Runtime filesystem metadata parsing is no longer a privileged (== risky) operation.
The consequences of a crashed driver is a dead mount, instead of a crashed or corrupt OS kernel.
Note that contained fuse filesystem servers are no faster than regular fuse. The containerization code only requires changes to libfuse and is ready to go today.
Note that I threw in a couple more patchsets to improve the caching behavior of libext2fs for better performance; and the ability to watch for memory pressure complaints from the kernel so that we can drop our own cache in response to memory pressure.
--Darrick
Unreviewed patches in this patchbomb:
[PATCHSET 1/4] libext2fs: fix some missed fsync calls
[PATCH 1/3] libext2fs: always fsync the device when flushing the [PATCH 2/3] libext2fs: always fsync the device when closing the unix [PATCH 3/3] libext2fs: only fsync the unix fd if we wrote to the
[PATCHSET v6 2/4] fuse4fs: run servers as a contained service
[PATCH 01/10] libext2fs: make it possible to extract the fd from an [PATCH 02/10] libext2fs: fix checking for valid fds in mmp.c [PATCH 03/10] unix_io: allow passing /dev/fd/XXX paths to the unixfd [PATCH 04/10] libext2fs: fix MMP code to work with unixfd IO manager [PATCH 05/10] libext2fs: bump libfuse API version to 3.19 [PATCH 06/10] fuse4fs: hoist some code out of fuse4fs_main [PATCH 07/10] fuse4fs: enable safe service mode
[PATCH 08/10] fuse4fs: set proc title when in fuse service mode [PATCH 09/10] fuse4fs: make MMP work correctly in safe service mode [PATCH 10/10] debian: update packaging for fuse4fs service
[PATCHSET v6 3/4] fuse2fs: improve block and inode caching
[PATCH 1/6] libsupport: add caching IO manager
[PATCH 2/6] iocache: add the actual buffer cache
[PATCH 3/6] iocache: bump buffer mru priority every 50 accesses [PATCH 4/6] fuse2fs: enable caching IO manager
[PATCH 5/6] fuse2fs: increase inode cache size
[PATCH 6/6] libext2fs: improve caching for inodes
[PATCHSET v6 4/4] fuse4fs: reclaim buffer cache under memory pressure
[PATCH 1/4] libsupport: add pressure stall monitor
[PATCH 2/4] fuse2fs: only reclaim buffer cache when there is memory [PATCH 3/4] fuse4fs: enable memory pressure monitoring with service [PATCH 4/4] fuse2fs: flush dirty metadata periodically
PATCH 1/4 libext2fs: fix some missed fsync calls
Hi all,
Fix a few places (like device closing) where we really ought to tell the block device to flush whatever's dirty to disk, even if we've failed to flush all our cached buffers out to disk.
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=libext2fs-flushing-fixes
---
Commits in this patchset:
* libext2fs: always fsync the device when flushing the cache
* libext2fs: always fsync the device when closing the unix IO manager
* libext2fs: only fsync the unix fd if we wrote to the device
--- lib/ext2fs/unix_io.c | 83 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 14 deletions(-)
PATCH 1/3 libext2fs: always fsync the device when flushing the cache
From: Darrick J. Wong <[email protected]>
When we're flushing the unix IO manager's buffer cache, we should always fsync the block device, because something could have written to the block device -- either the buffer cache itself, or a direct write. Regardless, the callers all want all dirtied regions to be persisted to stable media.
--- lib/ext2fs/unix_io.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
lib/ext2fs/unix_io.c +2 -1
@@ -1531,7 +1531,8 @@ static errcode_t unix_flush(io_channel channel) retval =flush_cached_blocks (channel, data,0 );#endif #ifdef HAVE_FSYNC -if (!retval &&fsync (data->dev) !=0 ) +/* always fsync the device, even if flushing our own cache failed */ +if (fsync (data->dev) !=0 && !retval)return errno;#endif return retval;
PATCH 2/3 libext2fs: always fsync the device when closing the unix IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
unix_close is the last chance that libext2fs has to report write failures to users. Although it's likely that ext2fs_close already called ext2fs_flush and told the IO manager to flush, we could do one more sync before we close the file descriptor. Also don't override the fsync's errno with the close's errno.
--- lib/ext2fs/unix_io.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
lib/ext2fs/unix_io.c +7 -1
@@ -1213,10 +1213,16 @@ static errcode_t unix_close(io_channel channel)#ifndef NO_IO_CACHE retval =flush_cached_blocks (channel, data,0 );#endif +#ifdef HAVE_FSYNC +/* always fsync the device, even if flushing our own cache failed */ +if (fsync (data->dev) !=0 && !retval) + retval = errno; +#endif unix_funlock (channel); -if (channel->manager != unixfd_io_manager &&close (data->dev) <0 ) +if (channel->manager != unixfd_io_manager &&close (data->dev) <0 && + !retval) retval = errno;free_cache (data);free (data->cache);
PATCH 3/3 libext2fs: only fsync the unix fd if we wrote to the device
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
As an optimization, only fsync the block device fd if we tried to write to the io channel.
--- lib/ext2fs/unix_io.c | 86 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 19 deletions(-)
lib/ext2fs/unix_io.c +67 -19
@@ -132,10 +132,13 @@ struct unix_cache {#define WRITE_DIRECT_SIZE 4/* Must be smaller than CACHE_SIZE */ #define READ_DIRECT_SIZE 4/* Should be smaller than CACHE_SIZE */ +#define UNIX_STATE_DIRTY (1U << 0)/* device needs fsyncing */ +struct unix_private_data {int magic;int dev;int flags; +unsigned int state;/* UNIX_STATE_* */ int align;int access_time;int unix_flock_flags;
@@ -1198,10 +1201,65 @@ static errcode_t unix_open(const char *name, int flags,return unix_open_channel (name, fd, flags, channel, unix_io_manager); } +#ifdef HAVE_FSYNC +static void mark_dirty (io_channel channel) +{ +struct unix_private_data *data = + (struct unix_private_data *) channel->private_data; + +mutex_lock (data,CACHE_MTX ); + data->state |=UNIX_STATE_DIRTY ; +mutex_unlock (data,CACHE_MTX ); +} + +static errcode_t maybe_fsync (io_channel channel,int force_fsync) +{ +struct unix_private_data *data = + (struct unix_private_data *) channel->private_data; +int need_fsync; +errcode_t retval =0 ; + +#ifndef NO_IO_CACHE + retval =flush_cached_blocks (channel, data,0 ); +#endif + +mutex_lock (data,CACHE_MTX ); + need_fsync = force_fsync || (data->state &UNIX_STATE_DIRTY ); + data->state &= ~UNIX_STATE_DIRTY ; +mutex_unlock (data,CACHE_MTX ); + +if (need_fsync &&fsync (data->dev) !=0 ) { +if (!retval) + retval = errno; + } +if (retval) { +/* redirty because writeback failed */ +mark_dirty (channel); +return retval; + } + +return 0 ; +} +#else +# define mark_dirty (...) ((void)0) + +static errcode_t maybe_fsync (io_channel channel,int force_fsync) +{ +struct unix_private_data *data = + (struct unix_private_data *) channel->private_data; +errcode_t retval =0 ; + +#ifndef NO_IO_CACHE + retval =flush_cached_blocks (channel, data,0 ); +#endif +return retval; +} +#endif +static errcode_t unix_close (io_channel channel) {struct unix_private_data *data; -errcode_t retval =0 ; +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL ); data = (struct unix_private_data *) channel->private_data;
@@ -1210,14 +1268,7 @@ static errcode_t unix_close(io_channel channel)if (--channel->refcount >0 )return 0 ; -#ifndef NO_IO_CACHE - retval =flush_cached_blocks (channel, data,0 ); -#endif -#ifdef HAVE_FSYNC -/* always fsync the device, even if flushing our own cache failed */ -if (fsync (data->dev) !=0 && !retval) - retval = errno; -#endif + retval =maybe_fsync (channel,1 );unix_funlock (channel);
@@ -1388,6 +1439,8 @@ static errcode_t unix_write_blk64(io_channel channel, unsigned long long block, data = (struct unix_private_data *) channel->private_data;EXT2_CHECK_MAGIC (data,EXT2_ET_MAGIC_UNIX_IO_CHANNEL ); +mark_dirty (channel); +#ifdef NO_IO_CACHE return raw_write_blk (channel, data, block, count, buf,0 );#else
@@ -1512,6 +1565,8 @@ static errcode_t unix_write_byte(io_channel channel, unsigned long offset,if (lseek (data->dev, offset + data->offset,SEEK_SET ) <0 )return errno; +mark_dirty (channel); + actual =write (data->dev, buf, size);if (actual <0 )return errno;
@@ -1527,21 +1582,12 @@ static errcode_t unix_write_byte(io_channel channel, unsigned long offset,static errcode_t unix_flush (io_channel channel) {struct unix_private_data *data; -errcode_t retval =0 ;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 ); -#ifndef NO_IO_CACHE - retval =flush_cached_blocks (channel, data,0 ); -#endif -#ifdef HAVE_FSYNC -/* always fsync the device, even if flushing our own cache failed */ -if (fsync (data->dev) !=0 && !retval) -return errno; -#endif -return retval; +return maybe_fsync (channel,0 ); } static errcode_t unix_set_option(io_channel channel, const char *option,
@@ -1653,6 +1699,7 @@ static errcode_t unix_discard(io_channel channel, unsigned long long block, }return errno; } +mark_dirty (channel);return 0 ; unimplemented:return EXT2_ET_UNIMPLEMENTED ;
@@ -1734,6 +1781,7 @@ static errcode_t unix_zeroout(io_channel channel, unsigned long long block, }return errno; } +mark_dirty (channel);return 0 ; unimplemented:return EXT2_ET_UNIMPLEMENTED ;
PATCH v6 2/4 fuse4fs: run servers as a contained service
In reply to Darrick J. WongHi all,
This series packages the newly created fuse4fs server into a systemd socket service. This service can be used by the "mount.service" helper in libfuse to implement untrusted unprivileged mounts.
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-service-container
---
Commits in this patchset:
* libext2fs: make it possible to extract the fd from an IO manager
* libext2fs: fix checking for valid fds in mmp.c
* unix_io: allow passing /dev/fd/XXX paths to the unixfd IO manager
* libext2fs: fix MMP code to work with unixfd IO manager
* libext2fs: bump libfuse API version to 3.19
* fuse4fs: hoist some code out of fuse4fs_main
* fuse4fs: enable safe service mode
* fuse4fs: set proc title when in fuse service mode
* fuse4fs: make MMP work correctly in safe service mode
* debian: update packaging for fuse4fs service
--- lib/ext2fs/ext2_io.h | 4 lib/ext2fs/ext2fs.h | 1 lib/ext2fs/ext2fsP.h | 4 MCONFIG.in | 2 configure | 303 ++++++++++++++++++++++++++- configure.ac | 131 +++++++++++ debian/e2fsprogs.install | 7 + debian/fuse4fs.install | 3 debian/libext2fs2t64.symbols | 1 debian/rules | 3 fuse4fs/Makefile.in | 42 +++- fuse4fs/fuse4fs.c | 479 ++++++++++++++++++++++++++++++++++++------ fuse4fs/fuse4fs.socket.in | 17 + fuse4fs/[email protected] | 102 +++++++++ lib/config.h.in | 12 + lib/ext2fs/io_manager.c | 8 + lib/ext2fs/mmp.c | 101 +++++++++ lib/ext2fs/openfs.c | 1 lib/ext2fs/unix_io.c | 50 ++++ util/subst.conf.in | 3 20 files changed, 1177 insertions(+), 97 deletions(-) mode change 100644 => 100755 debian/fuse4fs.install create mode 100644 fuse4fs/fuse4fs.socket.in create mode 100644 fuse4fs/[email protected]
PATCH 1/10 libext2fs: make it possible to extract the fd from an IO manager
From: Darrick J. Wong <[email protected]>
Make it so that we can extract the fd from an open IO manager. This will be used in subsequent patches to register the open block device with the fuse iomap kernel driver.
--- lib/ext2fs/ext2_io.h | 4 +++- debian/libext2fs2t64.symbols | 1 + lib/ext2fs/io_manager.c | 8 ++++++++ lib/ext2fs/unix_io.c | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-)
lib/ext2fs/ext2_io.h +3 -1
@@ -103,7 +103,8 @@ struct struct_io_manager {errcode_t (*zeroout)(io_channel channel,unsigned long long block,unsigned long long count);errcode_t (*flock)(io_channel channel,unsigned int flock_flags); -long reserved[13 ]; +errcode_t (*get_fd)(io_channel channel,int *fd); +long reserved[12 ]; };#define IO_FLAG_RW 0x0001
@@ -155,6 +156,7 @@ extern errcode_t io_channel_cache_readahead(io_channel io, 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); +extern errcode_t io_channel_get_fd (io_channel io,int *fd);#ifdef _WIN32/* windows_io.c */
debian/libext2fs2t64.symbols +1 -0
@@ -701,6 +701,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER# io_channel_discard@Base 1.42 io_channel_flock@Base 1.47.99 io_channel_funlock@Base 1.47.99 + io_channel_get_fd@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
lib/ext2fs/io_manager.c +8 -0
@@ -166,3 +166,11 @@ errcode_t io_channel_funlock(io_channel io)return io->manager->flock (io,0 ); } + +errcode_t io_channel_get_fd (io_channel io,int *fd) +{ +if (!io->manager->get_fd) +return EXT2_ET_OP_NOT_SUPPORTED ; + +return io->manager->get_fd (io, fd); +}
lib/ext2fs/unix_io.c +20 -0
@@ -1786,6 +1786,24 @@ static errcode_t unix_zeroout(io_channel channel, unsigned long long block, unimplemented:return EXT2_ET_UNIMPLEMENTED ; } + +static errcode_t unix_get_fd (io_channel channel,int *fd) +{ +struct unix_private_data *data; + +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->offset) { + *fd =-1 ; +return EINVAL ; + } + + *fd = data->dev; +return 0 ; +} +#if __GNUC_PREREQ (4 ,6 )#pragma GCC diagnostic pop#endif
@@ -1808,6 +1826,7 @@ static struct struct_io_manager struct_unix_manager = { .cache_readahead = unix_cache_readahead, .zeroout = unix_zeroout, .flock = unix_flock, + .get_fd = unix_get_fd, };io_manager unix_io_manager = &struct_unix_manager;
@@ -1830,6 +1849,7 @@ static struct struct_io_manager struct_unixfd_manager = { .cache_readahead = unix_cache_readahead, .zeroout = unix_zeroout, .flock = unix_flock, + .get_fd = unix_get_fd, };io_manager unixfd_io_manager = &struct_unixfd_manager;
PATCH 2/10 libext2fs: fix checking for valid fds in mmp.c
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
File descriptors are non-negative numbers, which means that 0 is a valid fd. Fix the code to be consistent with Unix behaviors.
--- lib/ext2fs/mmp.c | 6 +++--- lib/ext2fs/openfs.c | 1 + 2 files changed, 4 insertions(+), 3 deletions(-)
lib/ext2fs/mmp.c +3 -3
@@ -59,11 +59,11 @@ errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)return EXT2_ET_MMP_BAD_BLOCK ;/* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking -* mmp_fd <= 0 is OK to validate that the fd is valid. This opens its +* mmp_fd < 0 is OK to validate that the fd is valid. This opens its * own fd to read the MMP block to ensure that it is using O_DIRECT, * regardless of how the io_manager is doing reads, to avoid caching of * the MMP block by the io_manager or the VM. It needs to be fresh. */ -if (fs->mmp_fd <=0 ) { +if (fs->mmp_fd <0 ) {struct stat st;int flags =O_RDONLY |O_DIRECT ;
@@ -427,7 +427,7 @@ errcode_t ext2fs_mmp_stop(ext2_filsys fs) retval =ext2fs_mmp_write (fs, fs->super->s_mmp_block, fs->mmp_cmp); mmp_error: -if (fs->mmp_fd >0 ) { +if (fs->mmp_fd >=0 ) {close (fs->mmp_fd); fs->mmp_fd =-1 ; }
lib/ext2fs/openfs.c +1 -0
@@ -148,6 +148,7 @@ errcode_t ext2fs_open2(const char *name, const char *io_options,/* don't overwrite sb backups unless flag is explicitly cleared */ fs->flags |=EXT2_FLAG_MASTER_SB_ONLY ; fs->umask =022 ; + fs->mmp_fd =-1 ; time_env =ext2fs_safe_getenv ("SOURCE_DATE_EPOCH" );if (time_env) {
PATCH 3/10 unix_io: allow passing /dev/fd/XXX paths to the unixfd IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Commit 4ccf9e4fe165cf created a "unixfd" IO manager that allows someone to choose the unixfd IO manager and then mount a filesystem from an existing file descriptor by passing a string with the fd number as the "device" name to ext2fs_open().
That was an unfortunate choice of naming, however, because that could be mistaken for a relative path to a file whose name is an integer number. Let's improve this by allowing callers to pass /dev/fd/XX as the filesystem device name. The upcoming fuse4fs service patches will employ this method to open a filesystem on a block device fd passed into the secure container from a mount helper.
--- lib/ext2fs/unix_io.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-)
lib/ext2fs/unix_io.c +29 -1
@@ -67,6 +67,7 @@#ifdef HAVE_SYS_FILE_H #include <sys/file.h> #endif +#include <limits.h> #if defined(__linux__) && defined(_IO) && !defined(BLKROGET )#define BLKROGET _IO(0x12, 94)/* Get read-only status (0 = read_write). */
@@ -1148,13 +1149,40 @@ static errcode_t unix_open_channel(const char *name, int fd,return retval; } +#define DEV_FD_PATH "/dev/fd/" +#define DEV_FD_PATHLEN (sizeof(DEV_FD_PATH) - 1) + +static int possible_unixfd_pathname (const char *path) +{ +return strncmp (DEV_FD_PATH , path,DEV_FD_PATHLEN ) ==0 ; +} +static errcode_t unixfd_open (const char *str_fd,int flags,io_channel *channel) {int fd;int fd_flags; - fd =atoi (str_fd); +/* +* The caller should provide a path in the form "/dev/fd/XX", +* but the shorthand form "XX" is allowed for legacy reasons. +*/ +if (possible_unixfd_pathname (str_fd)) { +char *endptr; +long maybe_fd; + + errno =0 ; + maybe_fd =strtol (str_fd +DEV_FD_PATHLEN , &endptr,10 ); +if (errno) +return errno; +if (*endptr !=0 ) +return EINVAL ; +if (maybe_fd <0 || maybe_fd >INT_MAX ) +return EINVAL ; + fd = maybe_fd; + }else { + fd =atoi (str_fd); + }#if defined(HAVE_FCNTL ) fd_flags =fcntl (fd,F_GETFL );if (fd_flags ==-1 )
PATCH 4/10 libext2fs: fix MMP code to work with unixfd IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
The MMP code wants to be able to read and write the MMP block directly to storage so that the pagecache does not get in the way. This is critical for correct operation of MMP, because it is guarding against two cluster nodes trying to change the filesystem at the same time.
Unfortunately there's no convenient way to tell an IO manager to perform a particular IO in directio mode, so the MMP code open()s the filesystem source device a second time so that it can set O_DIRECT and maintain its own file position independently of the IO channel. This is a gross layering violation.
For unprivileged containerized fuse4fs, we're going to have a privileged mount helper pass us the fd to the block device, so we'll be using the unixfd IO manager. The enhanced security posture provided by the service definition file (minimal /dev) means that we cannot reopen the source device. In this case, MMP can only duplicate the fd and use the IO channel carefully.
Fix this (sort of) by detecting the unixfd IO manager and duplicating the open fd if it's in use. This adds a requirement that the unixfd originally be opened in O_DIRECT mode if the filesystem is on a block device, but that's the best we can do here.
--- lib/ext2fs/ext2fs.h | 1 + lib/ext2fs/ext2fsP.h | 4 ++ lib/ext2fs/mmp.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++- lib/ext2fs/unix_io.c | 2 + 4 files changed, 100 insertions(+), 2 deletions(-)
lib/ext2fs/ext2fs.h +1 -0
@@ -225,6 +225,7 @@ typedef struct ext2_file *ext2_file_t;* Internal flags for use by the ext2fs library only */ #define EXT2_FLAG2_USE_FAKE_TIME 0x000000001 +#define EXT2_FLAG2_MMP_USE_IOCHANNEL 0x000000002/* * Special flag in the ext2 inode i_flag field that means that this is
lib/ext2fs/ext2fsP.h +4 -0
@@ -218,3 +218,7 @@ errcode_t ext2fs_remove_exit_fn(ext2_exit_fn fn, void *data); (sizeof(array) / sizeof(array[0]))#define EXT2FS_BUILD_BUG_ON (cond) ((void)sizeof(char[1 - 2*!!(cond)])) + +#ifndef _WIN32 +int possible_unixfd_pathname (const char *path); +#endif
lib/ext2fs/mmp.c +94 -1
134 lines not on this page · raw message
lib/ext2fs/unix_io.c +1 -1
@@ -1152,7 +1152,7 @@ static errcode_t unix_open_channel(const char *name, int fd,#define DEV_FD_PATH "/dev/fd/"#define DEV_FD_PATHLEN (sizeof(DEV_FD_PATH) - 1) -static int possible_unixfd_pathname (const char *path) +int possible_unixfd_pathname (const char *path) {return strncmp (DEV_FD_PATH , path,DEV_FD_PATHLEN ) ==0 ; }
PATCH 5/10 libext2fs: bump libfuse API version to 3.19
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
The fuse service container API is only available in 3.19, so we need to bump FUSE_USE_VERSION up from 3.14 to 3.19.
--- configure | 8 ++++---- configure.ac | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-)
configure +4 -4
@@ -14604,14 +14604,14 @@ fi if test -n "$FUSE_LIB" then - FUSE_USE_VERSION=314 + FUSE_USE_VERSION=319 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" | sed "$as_sed_sh"` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#define _FILE_OFFSET_BITS 64 -#define FUSE_USE_VERSION 314 +#define FUSE_USE_VERSION 319 " if eval test \"x\$"$as_ac_Header"\" = x"yes" then :
@@ -14646,7 +14646,7 @@ printf %s "checking for lowlevel interface in libfuse... " >&6; } #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse_lowlevel.h> int
@@ -14826,7 +14826,7 @@ printf %s "checking for cache_readdir support in libfuse... " >&6; } #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse.h> int
configure.ac +5 -5
@@ -1384,17 +1384,17 @@ 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 libfuse ABI. Currently we link against the libfuse 3.19 ABI (hence 319) dnl if test -n "$FUSE_LIB" then - FUSE_USE_VERSION=314 + FUSE_USE_VERSION=319 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]) +#define FUSE_USE_VERSION 319]) fi if test -n "$FUSE_USE_VERSION" then
@@ -1413,7 +1413,7 @@ then [ AC_LANG_PROGRAM([[ #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse_lowlevel.h> ]], [[ struct fuse_lowlevel_ops fs_ops = { };
@@ -1515,7 +1515,7 @@ then [ AC_LANG_PROGRAM([[ #define _GNU_SOURCE #define _FILE_OFFSET_BITS 64 - #define FUSE_USE_VERSION 314 + #define FUSE_USE_VERSION 319 #include <fuse.h> ]], [[ struct fuse_file_info fs_ops = {
PATCH 6/10 fuse4fs: hoist some code out of fuse4fs_main
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
In the next patch, we're going to create a separate fuse4fs_main function when we're running in service mode. Hoist into separate helpers the code that will be shared between the two functions.
--- fuse4fs/fuse4fs.c | 95 +++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 46 deletions(-)
fuse4fs/fuse4fs.c +49 -46
@@ -6055,47 +6055,64 @@ 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) +static int fuse4fs_create_session (struct fuse4fs *ff,struct fuse_args *args, +struct fuse_cmdline_opts *opts) { -struct fuse_cmdline_opts opts; -struct fuse_session *se; -struct fuse_loop_config *loop_config =NULL ; -int ret =0 ; - -if (fuse_parse_cmdline (args, &opts) !=0 ) { - ret =1 ; - goto out; - } -if (ff->debug) - opts.debug = true; + opts->debug = true; -if (opts.show_help) { +if (opts->show_help) {fuse_cmdline_help (); - ret =0 ; - goto out_free_opts; +return 0 ; } -if (opts.show_version) { +if (opts->show_version) {printf ("FUSE library version %s\n" ,fuse_pkgversion ()); - ret =0 ; - goto out_free_opts; +return 0 ; } -if (!opts.mountpoint) { +if (!opts->mountpoint) {fprintf (stderr,"error: no mountpoint specified\n" ); - ret =2 ; - goto out_free_opts; +return 2 ; } - se =fuse_session_new (args, &fs_ops,sizeof (fs_ops), ff); -if (se ==NULL ) { - ret =3 ; - goto out_free_opts; + ff->fuse =fuse_session_new (args, &fs_ops,sizeof (fs_ops), ff); +return ff->fuse ?0 :3 ; +} + +static int fuse4fs_event_loop (struct fuse4fs *ff, +struct fuse_loop_config *loop_config, +const struct fuse_cmdline_opts *opts) +{ +/* +* 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 ); + +return fuse_session_loop_mt (ff->fuse, loop_config) ==0 ?0 :8 ; +} + +static int fuse4fs_main (struct fuse_args *args,struct fuse4fs *ff) +{ +struct fuse_cmdline_opts opts; +struct fuse_loop_config *loop_config =NULL ; +int ret; + +if (fuse_parse_cmdline (args, &opts) !=0 ) { + ret =1 ; + goto out; } - ff->fuse = se; -if (fuse_session_mount (se, opts.mountpoint) !=0 ) { + ret =fuse4fs_create_session (ff, args, &opts); +if (ret || !ff->fuse) + goto out_free_opts; + +if (fuse_session_mount (ff->fuse, opts.mountpoint) !=0 ) { ret =4 ; goto out_destroy_session; }
@@ -6115,7 +6132,7 @@ static int fuse4fs_main(struct fuse_args *args, struct fuse4fs *ff)close (ff->logfd); ff->logfd =-1 ; -if (fuse_set_signal_handlers (se) !=0 ) { +if (fuse_set_signal_handlers (ff->fuse) !=0 ) { ret =6 ; goto out_unmount; }
@@ -6126,30 +6143,16 @@ static int fuse4fs_main(struct fuse_args *args, struct fuse4fs *ff) 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 ); + ret =fuse4fs_event_loop (ff, loop_config, &opts); -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); +fuse_remove_signal_handlers (ff->fuse); out_unmount: -fuse_session_unmount (se); +fuse_session_unmount (ff->fuse); out_destroy_session: +fuse_session_destroy (ff->fuse); ff->fuse =NULL ; -fuse_session_destroy (se); out_free_opts:free (opts.mountpoint); out:
PATCH 7/10 fuse4fs: enable safe service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Make it possible to run fuse4fs as a safe systemd service, wherein the fuse server only has access to the fds that we pass in.
--- MCONFIG.in | 2 configure | 186 +++++++++++++++++++++++++++++++ configure.ac | 108 ++++++++++++++++++ fuse4fs/Makefile.in | 40 ++++++- fuse4fs/fuse4fs.c | 254 +++++++++++++++++++++++++++++++++++++++++-- fuse4fs/fuse4fs.socket.in | 17 +++ fuse4fs/[email protected] | 102 +++++++++++++++++ lib/config.h.in | 6 + util/subst.conf.in | 3 + 9 files changed, 703 insertions(+), 15 deletions(-) create mode 100644 fuse4fs/fuse4fs.socket.in create mode 100644 fuse4fs/[email protected]
MCONFIG.in +2 -0
@@ -42,6 +42,8 @@ HAVE_CROND = @have_crond@ CROND_DIR = @crond_dir@ HAVE_SYSTEMD = @have_systemd@ SYSTEMD_SYSTEM_UNIT_DIR = @systemd_system_unit_dir@ +HAVE_FUSE_SERVICE = @have_fuse_service@ +HAVE_FUSE4FS_SERVICE = @have_fuse4fs_service@ @SET_MAKE@
configure +185 -1
228 lines not on this page · raw message
configure.ac +107 -1
126 lines not on this page · raw message
fuse4fs/Makefile.in +36 -4
@@ -17,6 +17,13 @@ UMANPAGES= @FUSE4FS_CMT@UPROGS+=fuse4fs @FUSE4FS_CMT@UMANPAGES+=fuse4fs.1 +ifeq ($(HAVE_FUSE4FS_SERVICE),yes) +SERVICE_FILES += fuse4fs.socket [email protected] +INSTALLDIRS_TGT += installdirs-systemd +INSTALL_TGT += install-systemd +UNINSTALL_TGT += uninstall-systemd +endif + FUSE4FS_OBJS= fuse4fs.o journal.o recovery.o revoke.o PROFILED_FUSE4FS_OJBS= profiled/fuse4fs.o profiled/journal.o \
@@ -54,7 +61,7 @@ DEPEND_CFLAGS = -I$(top_srcdir)/e2fsck @PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $< all:: profiled $(SPROGS) $(UPROGS) $(USPROGS) $(SMANPAGES) $(UMANPAGES) \ - $(FMANPAGES) $(LPROGS) + $(FMANPAGES) $(LPROGS) $(SERVICE_FILES) all-static::
@@ -71,6 +78,14 @@ fuse4fs: $(FUSE4FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \ $(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \ $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P) +%.socket: %.socket.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + +%.service: %.service.in $(DEP_SUBSTITUTE) + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $< $@ + journal.o: $(srcdir)/../debugfs/journal.c $(E) " CC $<" $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
@@ -93,11 +108,15 @@ fuse4fs.1: $(DEP_SUBSTITUTE) $(srcdir)/fuse4fs.1.in $(E) " SUBST $@" $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/fuse4fs.1.in fuse4fs.1 -installdirs: +installdirs: $(INSTALLDIRS_TGT) $(E) " MKDIR_P $(bindir) $(man1dir)" $(Q) $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) -install: all $(UMANPAGES) installdirs +installdirs-systemd: + $(E) " MKDIR_P $(SYSTEMD_SYSTEM_UNIT_DIR)" + $(Q) $(MKDIR_P) $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR) + +install: all $(UMANPAGES) installdirs $(INSTALL_TGT) $(Q) for i in $(UPROGS); do \ $(ES) " INSTALL $(bindir)/$$i"; \ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(bindir)/$$i; \
@@ -110,13 +129,19 @@ install: all $(UMANPAGES) installdirs $(INSTALL_DATA) $$i $(DESTDIR)$(man1dir)/$$i; \ done +install-systemd: $(SERVICE_FILES) installdirs-systemd + $(Q) for i in $(SERVICE_FILES); do \ + $(ES) " INSTALL_DATA $(SYSTEMD_SYSTEM_UNIT_DIR)/$$i"; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/$$i; \ + done + install-strip: install $(Q) for i in $(UPROGS); do \ $(E) " STRIP $(bindir)/$$i"; \ $(STRIP) $(DESTDIR)$(bindir)/$$i; \ done -uninstall: +uninstall: $(UNINSTALL_TGT) for i in $(UPROGS); do \ $(RM) -f $(DESTDIR)$(bindir)/$$i; \ done
@@ -124,9 +149,16 @@ uninstall: $(RM) -f $(DESTDIR)$(man1dir)/$$i; \ done +uninstall-systemd: + for i in $(SERVICE_FILES); do \ + $(RM) -f $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/$$i; \ + done + clean:: $(RM) -f $(UPROGS) $(UMANPAGES) profile.h \ fuse4fs.profiled \ + $(SERVICE_FILES) \ + fuse4fs.socket \ profiled/*.o \#* *.s *.o *.a *~ core gmon.out mostlyclean: clean
fuse4fs/fuse4fs.c +245 -9
356 lines not on this page · raw message
fuse4fs/fuse4fs.socket.in new file +17 -0
@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2025-2026 Oracle. All Rights Reserved. +# Author: Darrick J. Wong <[email protected]> +[Unit] +Description=Socket for ext4 Service + +[Socket] +ListenSequentialPacket=@fuse_service_socket_dir@/ext2 +ListenSequentialPacket=@fuse_service_socket_dir@/ext3 +ListenSequentialPacket=@fuse_service_socket_dir@/ext4 +Accept=yes +SocketMode=@fuse_service_socket_perms@ +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target
fuse4fs/[email protected] new file +102 -0
102 lines not on this page · raw message
lib/config.h.in +6 -0
@@ -142,6 +142,9 @@ /* Define to 1 if you have the 'ftruncate64' function. */ #undef HAVE_FTRUNCATE64 +/* Define to 1 if fuse4fs should be built with fuse service support */ +#undef HAVE_FUSE4FS_SERVICE + /* Define to 1 if fuse supports cache_readdir */ #undef HAVE_FUSE_CACHE_READDIR
@@ -151,6 +154,9 @@ /* Define to 1 if fuse supports lowlevel API */ #undef HAVE_FUSE_LOWLEVEL +/* Define to 1 if fuse supports service */ +#undef HAVE_FUSE_SERVICE + /* Define to 1 if you have the 'futimes' function. */ #undef HAVE_FUTIMES
util/subst.conf.in +3 -0
@@ -24,3 +24,6 @@ root_bindir @root_bindir@ libdir @libdir@ $exec_prefix @exec_prefix@ pkglibexecdir @libexecdir@/e2fsprogs +bindir @bindir@ +fuse_service_socket_dir @fuse_service_socket_dir@ +fuse_service_socket_perms @fuse_service_socket_perms@
PATCH 8/10 fuse4fs: set proc title when in fuse service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
When in fuse service mode, set the process title so that we can identify fuse servers by mount arguments. When the service ends, amend the title again to say that we're cleaning up. This is done to make ps aux a bit more communicative as to what is going on.
--- configure | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 13 ++++++ fuse4fs/Makefile.in | 2 - fuse4fs/fuse4fs.c | 47 ++++++++++++++++++++++ lib/config.h.in | 6 +++ 5 files changed, 176 insertions(+), 1 deletion(-)
configure +109 -0
@@ -696,6 +696,7 @@ gcc_ranlib gcc_ar UNI_DIFF_OPTS SEM_INIT_LIB +LIBBSD_LIB FUSE4FS_CMT FUSE2FS_CMT fuse_service_socket_perms
@@ -15022,6 +15023,114 @@ printf "%s\n" "#define HAVE_FUSE_CACHE_READDIR 1" >>confdefs.h fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle in -lbsd" >&5 +printf %s "checking for setproctitle in -lbsd... " >&6; } +if test ${ac_cv_lib_bsd_setproctitle+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lbsd $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. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char setproctitle (void); +int +main (void) +{ +return setproctitle (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_bsd_setproctitle=yes +else case e in #( + e) ac_cv_lib_bsd_setproctitle=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_setproctitle" >&5 +printf "%s\n" "$ac_cv_lib_bsd_setproctitle" >&6; } +if test "x$ac_cv_lib_bsd_setproctitle" = xyes +then : + LIBBSD_LIB=-lbsd +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle_init in -lbsd" >&5 +printf %s "checking for setproctitle_init in -lbsd... " >&6; } +if test ${ac_cv_lib_bsd_setproctitle_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lbsd $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. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char setproctitle_init (void); +int +main (void) +{ +return setproctitle_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_bsd_setproctitle_init=yes +else case e in #( + e) ac_cv_lib_bsd_setproctitle_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_setproctitle_init" >&5 +printf "%s\n" "$ac_cv_lib_bsd_setproctitle_init" >&6; } +if test "x$ac_cv_lib_bsd_setproctitle_init" = xyes +then : + LIBBSD_LIB=-lbsd +fi + + +if test "$ac_cv_lib_bsd_setproctitle" = yes ; then + +printf "%s\n" "#define HAVE_SETPROCTITLE 1" >>confdefs.h + +fi +if test "$ac_cv_lib_bsd_setproctitle_init" = yes ; then + +printf "%s\n" "#define HAVE_SETPROCTITLE_INIT 1" >>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
configure.ac +13 -0
@@ -1622,6 +1622,19 @@ then [Define to 1 if fuse supports cache_readdir]) fi +dnl +dnl see if setproctitle exists +dnl +AC_CHECK_LIB(bsd, setproctitle, [LIBBSD_LIB=-lbsd]) +AC_CHECK_LIB(bsd, setproctitle_init, [LIBBSD_LIB=-lbsd]) +AC_SUBST(LIBBSD_LIB) +if test "$ac_cv_lib_bsd_setproctitle" = yes ; then + AC_DEFINE(HAVE_SETPROCTITLE, 1, [Define to 1 if setproctitle present in libbsd]) +fi +if test "$ac_cv_lib_bsd_setproctitle_init" = yes ; then + AC_DEFINE(HAVE_SETPROCTITLE_INIT, 1, [Define to 1 if setproctitle_init present in libbsd]) +fi + dnl dnl see if PR_SET_IO_FLUSHER exists dnl
fuse4fs/Makefile.in +1 -1
@@ -76,7 +76,7 @@ fuse4fs: $(FUSE4FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \ $(E) " LD $@" $(Q) $(CC) $(ALL_LDFLAGS) -o fuse4fs $(FUSE4FS_OBJS) $(LIBS) \ $(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \ - $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P) + $(CLOCK_GETTIME_LIB) $(SYSLIBS) $(LIBS_E2P) @LIBBSD_LIB@ %.socket: %.socket.in $(DEP_SUBSTITUTE) $(E) " SUBST $@"
fuse4fs/fuse4fs.c +47 -0
77 lines not on this page · raw message
lib/config.h.in +6 -0
@@ -379,6 +379,12 @@ /* Define to 1 if you have the 'setmntent' function. */ #undef HAVE_SETMNTENT +/* Define to 1 if setproctitle present in libbsd */ +#undef HAVE_SETPROCTITLE + +/* Define to 1 if setproctitle_init present in libbsd */ +#undef HAVE_SETPROCTITLE_INIT + /* Define to 1 if you have the 'setresgid' function. */ #undef HAVE_SETRESGID
PATCH 9/10 fuse4fs: make MMP work correctly in safe service mode
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Normally, the libext2fs MMP code open()s a complete separate file descriptor to read and write the MMP block so that it can have its own private open file with its own access mode and file position. However, if the unixfd IO manager is in use, it will reuse the io channel, which means that MMP and the unixfd share the same open file and hence the access mode and file position.
MMP requires directio access to block devices so that changes are immediately visible on other nodes. Therefore, we need the IO channel (and thus the filesystem) to be running in directio mode if MMP is in use.
To make this work correctly with the sole unixfd IO manager user (fuse4fs in unprivileged service mode), we must set O_DIRECT on the bdev fd and mount the filesystem in directio mode.
--- fuse4fs/fuse4fs.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-)
fuse4fs/fuse4fs.c +48 -3
@@ -1423,12 +1423,57 @@ static int fuse4fs_service_get_config(struct fuse4fs *ff) }static errcode_t fuse4fs_service_openfs (struct fuse4fs *ff,char *options, -int flags) +int *flags) { +struct stat statbuf;char path[64 ]; +errcode_t retval; +int ret; + ret =fstat (ff->bdev_fd, &statbuf); +if (ret) +return errno; + +/* +* Open the filesystem with SKIP_MMP so that we can find out if the +* filesystem actually has MMP. +*/ snprintf (path,sizeof (path),"/dev/fd/%d" , ff->bdev_fd); -return ext2fs_open2 (path, options, flags,0 ,0 , unixfd_io_manager, + retval =ext2fs_open2 (path, options, *flags |EXT2_FLAG_SKIP_MMP ,0 ,0 , + unixfd_io_manager, &ff->fs); +if (retval) +return retval; + +/* +* If the fs doesn't have MMP then we're good to go. Otherwise close +* the filesystem so that we can reopen it with MMP enabled. +*/ +if (!ext2fs_has_feature_mmp (ff->fs->super)) +return 0 ; + + retval =ext2fs_close_free (&ff->fs); +if (retval) +return retval; + +/* +* If the filesystem is not on a regular file, MMP will share the same +* fd as the unixfd IO channel. We need to set O_DIRECT on the bdev_fd +* and open the filesystem in directio mode. +*/ +if (!S_ISREG (statbuf.st_mode)) { +int fflags =fcntl (ff->bdev_fd,F_GETFL ); + +if (!(fflags &O_DIRECT )) { + ret =fcntl (ff->bdev_fd,F_SETFL , fflags |O_DIRECT ); +if (ret) +return EXT2_ET_MMP_OPEN_DIRECT ; + } + + ff->directio =1 ; + *flags |=EXT2_FLAG_DIRECT_IO ; + } + +return ext2fs_open2 (path, options, *flags,0 ,0 , unixfd_io_manager, &ff->fs); }#else
@@ -1568,7 +1613,7 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff) deadline =init_deadline (FUSE4FS_OPEN_TIMEOUT ); do { if (fuse4fs_is_service(ff)) - err = fuse4fs_service_openfs(ff, options, flags); + err = fuse4fs_service_openfs(ff, options, &flags); else err = ext2fs_open2(ff->device, options, flags, 0, 0, unix_io_manager, &ff->fs);
PATCH 10/10 debian: update packaging for fuse4fs service
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Update the Debian packaging code so that we can create fuse4fs service containers.
--- debian/e2fsprogs.install | 7 ++++++- debian/fuse4fs.install | 3 +++ debian/rules | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) mode change 100644 => 100755 debian/fuse4fs.install
debian/e2fsprogs.install +6 -1
@@ -50,4 +50,9 @@ usr/share/man/man8/resize2fs.8 usr/share/man/man8/tune2fs.8 etc [linux-any] ${deb_udevudevdir}/rules.d -[linux-any] ${deb_systemdsystemunitdir} +[linux-any] ${deb_systemdsystemunitdir}/[email protected] +[linux-any] ${deb_systemdsystemunitdir}/[email protected] +[linux-any] ${deb_systemdsystemunitdir}/e2scrub_all.service +[linux-any] ${deb_systemdsystemunitdir}/e2scrub_all.timer +[linux-any] ${deb_systemdsystemunitdir}/[email protected] +[linux-any] ${deb_systemdsystemunitdir}/e2scrub_reap.service
debian/fuse4fs.install +3 -0
@@ -1,2 +1,5 @@ +#!/usr/bin/dh-exec usr/bin/fuse4fs usr/share/man/man1/fuse4fs.1 +[linux-any] ${deb_systemdsystemunitdir}/fuse4fs.socket +[linux-any] ${deb_systemdsystemunitdir}/[email protected]
debian/rules +3 -0
@@ -173,6 +173,9 @@ override_dh_installinfo: ifneq ($(DEB_HOST_ARCH_OS), hurd) override_dh_installsystemd: dh_installsystemd -p e2fsprogs --no-restart-after-upgrade --no-stop-on-upgrade e2scrub_all.timer e2scrub_reap.service +ifeq ($(SKIP_FUSE4FS),) + dh_installsystemd -p fuse4fs fuse4fs.socket +endif endif override_dh_makeshlibs:
PATCH v6 3/4 fuse2fs: improve block and inode caching
In reply to Darrick J. WongHi all,
This series ports the libext2fs inode cache to the new cache.c hashtable code that was added for fuse4fs unlinked file support and improves on the UNIX I/O manager's block cache by adding a new I/O manager that does its own caching. Now we no longer have statically sized buffer caching for the two fuse servers.
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-caching
---
Commits in this patchset:
* libsupport: add caching IO manager
* iocache: add the actual buffer cache
* iocache: bump buffer mru priority every 50 accesses
* fuse2fs: enable caching IO manager
* fuse2fs: increase inode cache size
* libext2fs: improve caching for inodes
--- lib/ext2fs/ext2fsP.h | 13 + lib/support/cache.h | 1 lib/support/iocache.h | 17 + debugfs/Makefile.in | 8 e2fsck/Makefile.in | 12 - fuse4fs/Makefile.in | 10 - fuse4fs/fuse4fs.c | 15 + lib/ext2fs/Makefile.in | 69 ++-- lib/ext2fs/inline_data.c | 4 lib/ext2fs/inode.c | 215 ++++++++++--- lib/ext2fs/io_manager.c | 3 lib/support/Makefile.in | 6 lib/support/cache.c | 16 + lib/support/iocache.c | 751 ++++++++++++++++++++++++++++++++++++++++++++++ misc/Makefile.in | 11 - misc/fuse2fs.c | 11 + resize/Makefile.in | 11 - tests/fuzz/Makefile.in | 4 tests/progs/Makefile.in | 4 19 files changed, 1057 insertions(+), 124 deletions(-) create mode 100644 lib/support/iocache.h create mode 100644 lib/support/iocache.c
PATCH 1/6 libsupport: add caching IO manager
From: Darrick J. Wong <[email protected]>
Start creating a caching IO manager so that we can have better caching of metadata blocks in fuse2fs. For now it's just a passthrough cache.
--- lib/support/iocache.h | 17 +++ lib/ext2fs/io_manager.c | 3 lib/support/Makefile.in | 6 + lib/support/iocache.c | 304 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 lib/support/iocache.h create mode 100644 lib/support/iocache.c
lib/support/iocache.h new file +17 -0
@@ -0,0 +1,17 @@ +/* +* iocache.h - IO cache +* +* Copyright (C) 2025-2026 Oracle. +* +* %Begin-Header% +* This file may be redistributed under the terms of the GNU Public +* License. +* %End-Header% +*/ +#ifndef __IOCACHE_H__ +#define __IOCACHE_H__ + +errcode_t iocache_set_backing_manager (io_manager manager); +extern io_manager iocache_io_manager; + +#endif /* __IOCACHE_H__ */
lib/ext2fs/io_manager.c +3 -0
@@ -16,9 +16,12 @@#if HAVE_SYS_TYPES_H #include <sys/types.h> #endif +#include <stdbool.h> #include "ext2_fs.h" #include "ext2fs.h" +#include "support/list.h" +#include "support/cache.h" errcode_t io_channel_set_options (io_channel channel,const char *opts) {
lib/support/Makefile.in +5 -1
@@ -15,6 +15,7 @@ all:: OBJS= bthread.o \ cstring.o \ + iocache.o \ mkquota.o \ plausible.o \ profile.o \
@@ -46,7 +47,8 @@ SRCS= $(srcdir)/argv_parse.c \ $(srcdir)/thread.c \ $(srcdir)/dict.c \ $(srcdir)/devname.c \ - $(srcdir)/cache.c + $(srcdir)/cache.c \ + $(srcdir)/iocache.c LIBRARY= libsupport LIBDIR= support
@@ -200,3 +202,5 @@ 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 \ $(top_builddir)/lib/dirpaths.h $(srcdir)/list.h $(srcdir)/cache.h +iocache.o: $(srcdir)/iocache.c $(top_builddir)/lib/config.h \ + $(srcdir)/iocache.h $(srcdir)/cache.h $(srcdir)/list.h
lib/support/iocache.c new file +304 -0
304 lines not on this page · raw message
PATCH 2/6 iocache: add the actual buffer cache
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Wire up buffer caching into our new caching IO manager.
--- lib/support/iocache.c | 482 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 460 insertions(+), 22 deletions(-)
lib/support/iocache.c +460 -22
@@ -9,46 +9,287 @@* %End-Header% */ #include "config.h" +#include <assert.h> +#include <stdbool.h> +#include <pthread.h> +#include <unistd.h> +#include <limits.h> #include "ext2fs/ext2_fs.h" #include "ext2fs/ext2fs.h" #include "ext2fs/ext2fsP.h" #include "support/iocache.h" +#include "support/list.h" +#include "support/cache.h" #define IOCACHE_IO_CHANNEL_MAGIC 0x424F5254/* BORT */ static io_manager iocache_backing_manager; +static inline uint64_t B_TO_FSBT (io_channel channel,uint64_t number) { +return number / channel->block_size; +} + +static inline uint64_t B_TO_FSB (io_channel channel,uint64_t number) { +return (number + channel->block_size -1 ) / channel->block_size; +} +struct iocache_private_data {int magic; -io_channel real; +io_channel real;/* lower level io channel */ +io_channel channel;/* cache channel */ +struct cache cache; +pthread_mutex_t stats_lock; +struct struct_io_stats io_stats; +unsigned long long write_errors; }; +#define IOCACHEDATA (cache) \ + (container_of(cache, struct iocache_private_data, cache)) +static struct iocache_private_data *IOCACHE (io_channel channel) {return (struct iocache_private_data *)channel->private_data; } -static errcode_t iocache_read_error (io_channel channel,unsigned long block, -int count,void *data,size_t size, -int actual_bytes_read,errcode_t error) +struct iocache_buf { +struct cache_node node; +struct list_head list; +blk64_t block; +void *buf; +errcode_t write_error; +unsigned int uptodate:1 ; +unsigned int dirty:1 ; +}; + +static inline void iocache_buf_lock (struct iocache_buf *ubuf) +{ +pthread_mutex_lock (&ubuf->node.cn_mutex); +} + +static inline void iocache_buf_unlock (struct iocache_buf *ubuf) +{ +pthread_mutex_unlock (&ubuf->node.cn_mutex); +} + +struct iocache_key { +blk64_t block; +}; + +#define IOKEY (key) ((struct iocache_key *)(key)) +#define IOBUF (node) (container_of((node), struct iocache_buf, node)) + +static unsigned int +iocache_hash (cache_key_t key,unsigned int hashsize,unsigned int hashshift) +{ +uint64_t hashval =IOKEY (key)->block; +uint64_t tmp; + + tmp = hashval ^ (GOLDEN_RATIO_PRIME + hashval) /CACHE_LINE_SIZE ; + tmp = tmp ^ ((tmp ^GOLDEN_RATIO_PRIME ) >> hashshift); +return tmp % hashsize; +} + +static int iocache_compare (struct cache_node *node,cache_key_t key) +{ +struct iocache_buf *ubuf =IOBUF (node); +struct iocache_key *ukey =IOKEY (key); + +if (ubuf->block == ukey->block) +return CACHE_HIT ; + +return CACHE_MISS ; +} + +static struct cache_node *iocache_alloc_node (struct cache *cache, +cache_key_t key) +{ +struct iocache_private_data *data =IOCACHEDATA (cache); +struct iocache_key *ukey =IOKEY (key); +struct iocache_buf *ubuf; +errcode_t retval; + + retval =ext2fs_get_mem (sizeof (struct iocache_buf ), &ubuf); +if (retval) +return NULL ; +memset (ubuf,0 ,sizeof (*ubuf)); + + retval =io_channel_alloc_buf (data->channel,0 , &ubuf->buf); +if (retval) { +free (ubuf); +return NULL ; + } +memset (ubuf->buf,0 , data->channel->block_size); + +INIT_LIST_HEAD (&ubuf->list); + ubuf->block = ukey->block; +return &ubuf->node; +} + +static bool iocache_flush_node (struct cache *cache,struct cache_node *node) +{ +struct iocache_private_data *data =IOCACHEDATA (cache); +struct iocache_buf *ubuf =IOBUF (node); +errcode_t retval; + +if (ubuf->dirty) { + retval =io_channel_write_blk64 (data->real, ubuf->block,1 , + ubuf->buf); +if (retval) { + ubuf->write_error = retval; + data->write_errors++; + }else { + ubuf->dirty =0 ; + ubuf->write_error =0 ; + } + } + +return ubuf->dirty; +} + +static void iocache_relse (struct cache *cache,struct cache_node *node) +{ +struct iocache_buf *ubuf =IOBUF (node); + +ext2fs_free_mem (&ubuf->buf); +ext2fs_free_mem (&ubuf); +} + +static unsigned int iocache_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) { +iocache_relse (cache, cn); + count++; + } + +return count; +} + +/* Flush all dirty buffers in the cache to disk. */ +static errcode_t iocache_flush_cache (struct iocache_private_data *data) +{ +return cache_flush (&data->cache) ?0 :EIO ; +} + +/* Flush all dirty buffers in this range of the cache to disk. */ +static errcode_t iocache_flush_range (struct iocache_private_data *data, +blk64_t block,uint64_t count) +{ +uint64_t i; +bool still_dirty = false; + +for (i =0 ; i < count; i++) { +struct iocache_key ukey = { + .block = block + i, + }; +struct cache_node *node; + +cache_node_get (&data->cache, &ukey,CACHE_GET_INCORE , + &node); +if (!node) +continue ; + +/* cache_flush holds cn_mutex across the node flush */ +pthread_mutex_unlock (&node->cn_mutex); + still_dirty |=iocache_flush_node (&data->cache, node); +pthread_mutex_unlock (&node->cn_mutex); + +cache_node_put (&data->cache, node); + } + +return still_dirty ?EIO :0 ; +} + +static void iocache_add_list (struct cache *cache,struct cache_node *node, +void *data) +{ +struct iocache_buf *ubuf =IOBUF (node); +struct list_head *list = data; + +assert (node->cn_count ==0 || node->cn_count ==1 ); + +iocache_buf_lock (ubuf); +cache_node_grab (cache, node); +list_add_tail (&ubuf->list, list); +iocache_buf_unlock (ubuf); +} + +static void iocache_invalidate_bufs (struct iocache_private_data *data, +struct list_head *list) +{ +struct iocache_buf *ubuf, *n; + +list_for_each_entry_safe (ubuf, n, list, list) { +struct iocache_key ukey = { + .block = ubuf->block, + }; + +assert (ubuf->node.cn_count ==1 ); + +iocache_buf_lock (ubuf); + ubuf->dirty =0 ; +list_del_init (&ubuf->list); +iocache_buf_unlock (ubuf); + +cache_node_put (&data->cache, &ubuf->node); +cache_node_purge (&data->cache, &ukey, &ubuf->node); + } +} + +/* +* Remove all blocks from the cache. Dirty contents are discarded. Buffer +* refcounts must be zero! +*/ +static void iocache_invalidate_cache (struct iocache_private_data *data) { -io_channel iocache_channel = channel->app_data; +LIST_HEAD (list); -return iocache_channel->read_error (iocache_channel, block, count, data, - size, actual_bytes_read, error); +cache_walk (&data->cache, iocache_add_list, &list); +iocache_invalidate_bufs (data, &list); } -static errcode_t iocache_write_error (io_channel channel,unsigned long block, -int count,const void *data,size_t size, -int actual_bytes_written, -errcode_t error) +/* +* Remove a range of blocks from the cache. Dirty contents are discarded. +* Buffer refcounts must be zero! +*/ +static void iocache_invalidate_range (struct iocache_private_data *data, +blk64_t block,uint64_t count) { -io_channel iocache_channel = channel->app_data; +LIST_HEAD (list); +uint64_t i; -return iocache_channel->write_error (iocache_channel, block, count, data, - size, actual_bytes_written, error); +for (i =0 ; i < count; i++) { +struct iocache_key ukey = { + .block = block + i, + }; +struct cache_node *node; + +cache_node_get (&data->cache, &ukey,CACHE_GET_INCORE , + &node); +if (node) { +iocache_add_list (&data->cache, node, &list); +cache_node_put (&data->cache, node); + } + } +iocache_invalidate_bufs (data, &list); } +static const struct cache_operations iocache_ops = { + .hash = iocache_hash, + .alloc = iocache_alloc_node, + .flush = iocache_flush_node, + .relse = iocache_relse, + .compare = iocache_compare, + .bulkrelse = iocache_bulkrelse, + .resize = cache_gradual_resize, +}; +static errcode_t iocache_open (const char *name,int flags,io_channel *channel) {io_channel io =NULL ;
@@ -65,6 +306,9 @@ static errcode_t iocache_open(const char *name, int flags, io_channel *channel)if (retval)return retval; +/* disable any static cache in the lower io manager */ +io_channel_set_options (real,"cache=off" ); + retval =ext2fs_get_mem (sizeof (struct struct_io_channel ), &io);if (retval) goto out_backing;
@@ -76,12 +320,19 @@ static errcode_t iocache_open(const char *name, int flags, io_channel *channel) goto out_channel;memset (data,0 ,sizeof (struct iocache_private_data )); data->magic =IOCACHE_IO_CHANNEL_MAGIC ; + data->io_stats.num_fields =4 ; + data->channel = io; io->manager = iocache_io_manager; retval =ext2fs_get_mem (strlen (name) +1 , &io->name);if (retval) goto out_data; + retval =cache_init (CACHE_AUTO_SHRINK ,1U <<10 , &iocache_ops, + &data->cache); +if (retval) + goto out_name; +strcpy (io->name, name); io->private_data = data; io->block_size = real->block_size;
@@ -91,12 +342,14 @@ static errcode_t iocache_open(const char *name, int flags, io_channel *channel) io->flags = real->flags; data->real = real; real->app_data = io; - real->read_error = iocache_read_error; - real->write_error = iocache_write_error; + +pthread_mutex_init (&data->stats_lock,NULL ); *channel = io;return 0 ; +out_name: +ext2fs_free_mem (&io->name); out_data:ext2fs_free_mem (&data); out_channel:
@@ -116,6 +369,10 @@ static errcode_t iocache_close(io_channel channel)if (--channel->refcount >0 )return 0 ; +pthread_mutex_destroy (&data->stats_lock); +cache_flush (&data->cache); +cache_purge (&data->cache); +cache_destroy (&data->cache);if (data->real) retval =io_channel_close (data->real);ext2fs_free_mem (&channel->private_data);
@@ -134,6 +391,11 @@ static errcode_t iocache_set_blksize(io_channel channel, int blksize)EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); + retval =iocache_flush_cache (data); +if (retval) +return retval; +iocache_invalidate_cache (data); + retval =io_channel_set_blksize (data->real, blksize);if (retval)return retval;
@@ -145,21 +407,34 @@ static errcode_t iocache_set_blksize(io_channel channel, int blksize)static errcode_t iocache_flush (io_channel channel) {struct iocache_private_data *data =IOCACHE (channel); +errcode_t retval =0 ; +errcode_t retval2;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); -return io_channel_flush (data->real); + retval =iocache_flush_cache (data); + retval2 =io_channel_flush (data->real); +if (retval) +return retval; +return retval2; }static errcode_t iocache_write_byte (io_channel channel,unsigned long offset,int count,const void *buf) {struct iocache_private_data *data =IOCACHE (channel); +blk64_t bno =B_TO_FSBT (channel, offset); +blk64_t next_bno =B_TO_FSB (channel, offset + count); +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); + retval =iocache_flush_range (data, bno, next_bno - bno); +if (retval) +return retval; +iocache_invalidate_range (data, bno, next_bno - bno);return io_channel_write_byte (data->real, offset, count, buf); }
@@ -170,6 +445,31 @@ static errcode_t iocache_set_option(io_channel channel, const char *option,EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); +errcode_t retval; + +/* don't let unix io cache= options leak through */ +if (!strcmp (option,"cache" )) +return 0 ; + +if (!strcmp (option,"cache_blocks" )) { +long long size; + +if (!arg) +return EXT2_ET_INVALID_ARGUMENT ; + + errno =0 ; + size =strtoll (arg,NULL ,0 ); +if (errno || size ==0 || size >UINT_MAX ) +return EXT2_ET_INVALID_ARGUMENT ; + +cache_set_maxcount (&data->cache, size); +return 0 ; + } + + retval =iocache_flush_cache (data); +if (retval) +return retval; +iocache_invalidate_cache (data);return data->real->manager->set_option (data->real, option, arg); }
@@ -181,31 +481,157 @@ static errcode_t iocache_get_stats(io_channel channel, io_stats *io_stats)EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); -return data->real->manager->get_stats (data->real, io_stats); +/* +* Yes, io_stats is a double-pointer, and we let the caller scribble on +* our stats struct WITHOUT LOCKING! +*/ +if (io_stats) + *io_stats = &data->io_stats; +return 0 ; +} + +static void iocache_update_stats (struct iocache_private_data *data, +unsigned long long bytes_read, +unsigned long long bytes_written, +int cache_op) +{ +pthread_mutex_lock (&data->stats_lock); + data->io_stats.bytes_read += bytes_read; + data->io_stats.bytes_written += bytes_written; +if (cache_op ==CACHE_HIT ) + data->io_stats.cache_hits++; +else + data->io_stats.cache_misses++; +pthread_mutex_unlock (&data->stats_lock); }static errcode_t iocache_read_blk64 (io_channel channel,unsigned long long block,int count,void *buf) { +struct iocache_key ukey = { + .block = block, + };struct iocache_private_data *data =IOCACHE (channel); +unsigned long long i; +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); -return io_channel_read_blk64 (data->real, block, count, buf); +/* +* If we're doing an odd-sized read, flush out the cache and then do a +* direct read. +*/ +if (count <0 ) { +uint64_t fsbcount =B_TO_FSB (channel, -count); + + retval =iocache_flush_range (data, block, fsbcount); +if (retval) +return retval; +iocache_invalidate_range (data, block, fsbcount); +iocache_update_stats (data,0 ,0 ,CACHE_MISS ); +return io_channel_read_blk64 (data->real, block, count, buf); + } + +for (i =0 ; i < count; i++, ukey.block++, buf += channel->block_size) { +struct cache_node *node; +struct iocache_buf *ubuf; + +cache_node_get (&data->cache, &ukey,0 , &node); +if (!node) { +/* cannot instantiate cache, just do a direct read */ + retval =io_channel_read_blk64 (data->real, ukey.block, +1 , buf); +if (retval) +return retval; +iocache_update_stats (data, channel->block_size,0 , +CACHE_MISS ); +continue ; + } + + ubuf =IOBUF (node); +iocache_buf_lock (ubuf); +if (!ubuf->uptodate) { + retval =io_channel_read_blk64 (data->real, ukey.block, +1 , ubuf->buf); +if (!retval) { + ubuf->uptodate =1 ; +iocache_update_stats (data, channel->block_size, +0 ,CACHE_MISS ); + } + }else { +iocache_update_stats (data, channel->block_size,0 , +CACHE_HIT ); + } +if (ubuf->uptodate) +memcpy (buf, ubuf->buf, channel->block_size); +iocache_buf_unlock (ubuf); +cache_node_put (&data->cache, node); +if (retval) +return retval; + } + +return 0 ; }static errcode_t iocache_write_blk64 (io_channel channel,unsigned long long block,int count,const void *buf) { +struct iocache_key ukey = { + .block = block, + };struct iocache_private_data *data =IOCACHE (channel); +unsigned long long i; +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); -return io_channel_write_blk64 (data->real, block, count, buf); +/* +* If we're doing an odd-sized write, flush out the cache and then do a +* direct write. +*/ +if (count <0 ) { +uint64_t fsbcount =B_TO_FSB (channel, -count); + + retval =iocache_flush_range (data, block, fsbcount); +if (retval) +return retval; +iocache_invalidate_range (data, block, fsbcount); +iocache_update_stats (data,0 ,0 ,CACHE_MISS ); +return io_channel_write_blk64 (data->real, block, count, buf); + } + +for (i =0 ; i < count; i++, ukey.block++, buf += channel->block_size) { +struct cache_node *node; +struct iocache_buf *ubuf; + +cache_node_get (&data->cache, &ukey,0 , &node); +if (!node) { +/* cannot instantiate cache, do a direct write */ + retval =io_channel_write_blk64 (data->real, ukey.block, +1 , buf); +if (retval) +return retval; +iocache_update_stats (data,0 , channel->block_size, +CACHE_MISS ); +continue ; + } + + ubuf =IOBUF (node); +iocache_buf_lock (ubuf); +memcpy (ubuf->buf, buf, channel->block_size); +iocache_update_stats (data,0 , channel->block_size, + ubuf->uptodate ?CACHE_HIT :CACHE_MISS ); + ubuf->dirty =1 ; + ubuf->uptodate =1 ; +iocache_buf_unlock (ubuf); +cache_node_put (&data->cache, node); + } + +return 0 ; } static errcode_t iocache_read_blk(io_channel channel, unsigned long block,
@@ -224,11 +650,17 @@ static errcode_t iocache_discard(io_channel channel, unsigned long long block, unsigned long long count) {struct iocache_private_data *data =IOCACHE (channel); +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); -return io_channel_discard (data->real, block, count); + retval =io_channel_discard (data->real, block, count); +if (retval) +return retval; + +iocache_invalidate_range (data, block, count); +return 0 ; } static errcode_t iocache_cache_readahead(io_channel channel,
@@ -247,11 +679,17 @@ static errcode_t iocache_zeroout(io_channel channel, unsigned long long block, unsigned long long count) {struct iocache_private_data *data =IOCACHE (channel); +errcode_t retval;EXT2_CHECK_MAGIC (channel,EXT2_ET_MAGIC_IO_CHANNEL );EXT2_CHECK_MAGIC (data,IOCACHE_IO_CHANNEL_MAGIC ); -return io_channel_zeroout (data->real, block, count); + retval =io_channel_zeroout (data->real, block, count); +if (retval) +return retval; + +iocache_invalidate_range (data, block, count); +return 0 ; }static errcode_t iocache_get_fd (io_channel channel,int *fd)
PATCH 3/6 iocache: bump buffer mru priority every 50 accesses
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
If a buffer is hot enough to survive more than 50 access without being reclaimed, bump its priority to the next MRU so it sticks around longer.
--- lib/support/cache.h | 1 + lib/support/cache.c | 16 ++++++++++++++++ lib/support/iocache.c | 9 +++++++++ 3 files changed, 26 insertions(+)
lib/support/cache.h +1 -0
@@ -180,5 +180,6 @@ 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); +void cache_node_bump_priority (struct cache *cache,struct cache_node *node);#endif /* __CACHE_H__ */
lib/support/cache.c +16 -0
@@ -715,6 +715,22 @@ cache_node_put(cache_shrink (cache); } +/* Bump the priority of a cache node. Caller must hold cn_mutex. */ +void +cache_node_bump_priority ( +struct cache *cache, +struct cache_node *node) +{ +int *priop; + +if (node->cn_priority ==CACHE_DIRTY_PRIORITY ) + priop = &node->cn_old_priority; +else + priop = &node->cn_priority; +if (*priop <CACHE_MAX_PRIORITY ) + (*priop)++; +} + void cache_node_set_priority( struct cache * cache EXT2FS_ATTR((unused)),
lib/support/iocache.c +9 -0
@@ -57,6 +57,7 @@ struct iocache_buf {blk64_t block;void *buf;errcode_t write_error; +uint8_t access; unsigned int uptodate:1; unsigned int dirty:1; };
@@ -566,6 +567,10 @@ static errcode_t iocache_read_blk64(io_channel channel, }if (ubuf->uptodate)memcpy (buf, ubuf->buf, channel->block_size); +if (++ubuf->access >50 ) { +cache_node_bump_priority (&data->cache, node); + ubuf->access =0 ; + }iocache_buf_unlock (ubuf);cache_node_put (&data->cache, node);if (retval)
@@ -627,6 +632,10 @@ static errcode_t iocache_write_blk64(io_channel channel, ubuf->uptodate ? CACHE_HIT : CACHE_MISS); ubuf->dirty =1 ; ubuf->uptodate =1 ; +if (++ubuf->access >50 ) { +cache_node_bump_priority (&data->cache, node); + ubuf->access =0 ; + }iocache_buf_unlock (ubuf);cache_node_put (&data->cache, node); }
PATCH 4/6 fuse2fs: enable caching IO manager
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Enable the new dynamic iocache I/O manager in the fuse server, and turn off all the other cache control.
--- fuse4fs/Makefile.in | 2 +- fuse4fs/fuse4fs.c | 11 ++++++++--- lib/support/iocache.c | 4 ++-- misc/Makefile.in | 3 ++- misc/fuse2fs.c | 7 ++++++- 5 files changed, 19 insertions(+), 8 deletions(-)
fuse4fs/Makefile.in +1 -1
@@ -180,7 +180,7 @@ fuse4fs.o: $(srcdir)/fuse4fs.c $(top_builddir)/lib/config.h \ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/support/bthread.h \ $(top_srcdir)/lib/support/thread.h $(top_srcdir)/lib/support/list.h \ $(top_srcdir)/lib/support/cache.h $(top_srcdir)/version.h \ - $(top_srcdir)/lib/e2p/e2p.h + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/iocache.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 \
fuse4fs/fuse4fs.c +8 -3
@@ -60,6 +60,7 @@#include "support/thread.h" #include "support/list.h" #include "support/cache.h" +#include "support/iocache.h" #include "../version.h" #include "uuid/uuid.h"
@@ -1434,13 +1435,15 @@ static errcode_t fuse4fs_service_openfs(struct fuse4fs *ff, char *options,if (ret)return errno; +iocache_set_backing_manager (unixfd_io_manager); +/* * Open the filesystem with SKIP_MMP so that we can find out if the * filesystem actually has MMP. */ snprintf (path,sizeof (path),"/dev/fd/%d" , ff->bdev_fd); retval =ext2fs_open2 (path, options, *flags |EXT2_FLAG_SKIP_MMP ,0 ,0 , - unixfd_io_manager, &ff->fs); + iocache_io_manager, &ff->fs);if (retval)return retval;
@@ -1473,7 +1476,7 @@ static errcode_t fuse4fs_service_openfs(struct fuse4fs *ff, char *options, *flags |= EXT2_FLAG_DIRECT_IO; } -return ext2fs_open2 (path, options, *flags,0 ,0 , unixfd_io_manager, +return ext2fs_open2 (path, options, *flags,0 ,0 , iocache_io_manager, &ff->fs); }#else
@@ -1581,6 +1584,8 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff)if (ff->directio) flags |=EXT2_FLAG_DIRECT_IO ; +iocache_set_backing_manager (unix_io_manager); +/* * 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
@@ -1616,7 +1621,7 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff) err =fuse4fs_service_openfs (ff, options, &flags); else err =ext2fs_open2 (ff->device, options, flags,0 ,0 , - unix_io_manager, &ff->fs); + iocache_io_manager, &ff->fs);if ((err ==EPERM || err ==EACCES ) && (!ff->ro || (flags &EXT2_FLAG_RW ))) {/*
lib/support/iocache.c +2 -2
@@ -249,7 +249,7 @@ static void iocache_invalidate_bufs(struct iocache_private_data *data,*/ static void iocache_invalidate_cache (struct iocache_private_data *data) { -LIST_HEAD (list); +struct list_head list =LIST_HEAD_INIT (list);cache_walk (&data->cache, iocache_add_list, &list);iocache_invalidate_bufs (data, &list);
@@ -262,7 +262,7 @@ static void iocache_invalidate_cache(struct iocache_private_data *data)static void iocache_invalidate_range (struct iocache_private_data *data,blk64_t block,uint64_t count) { -LIST_HEAD (list); +struct list_head list =LIST_HEAD_INIT (list);uint64_t i;for (i =0 ; i < count; i++) {
misc/Makefile.in +2 -1
@@ -880,7 +880,8 @@ fuse2fs.o: $(srcdir)/fuse2fs.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/iocache.h e2fuzz.o: $(srcdir)/e2fuzz.c $(top_builddir)/lib/config.h \ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
misc/fuse2fs.c +6 -1
@@ -49,6 +49,9 @@#include "ext2fs/ext2fsP.h" #include "support/bthread.h" #include "support/thread.h" +#include "support/list.h" +#include "support/cache.h" +#include "support/iocache.h" #include "../version.h" #include "uuid/uuid.h"
@@ -1176,6 +1179,8 @@ static errcode_t fuse2fs_open(struct fuse2fs *ff)if (ff->directio) flags |=EXT2_FLAG_DIRECT_IO ; +iocache_set_backing_manager (unix_io_manager); +/* * 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
@@ -1208,7 +1213,7 @@ static errcode_t fuse2fs_open(struct fuse2fs *ff) deadline =init_deadline (FUSE2FS_OPEN_TIMEOUT ); do { err = ext2fs_open2(ff->device, options, flags, 0, 0, - unix_io_manager, &ff->fs); + iocache_io_manager, &ff->fs); if ((err == EPERM || err == EACCES) && (!ff->ro || (flags & EXT2_FLAG_RW))) { /*
PATCH 5/6 fuse2fs: increase inode cache size
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Increase the internal inode cache size. Does this improve performance any?
--- fuse4fs/fuse4fs.c | 4 ++++ misc/fuse2fs.c | 4 ++++ 2 files changed, 8 insertions(+)
fuse4fs/fuse4fs.c +4 -0
@@ -1689,6 +1689,10 @@ static errcode_t fuse4fs_open(struct fuse4fs *ff)if (err)return translate_error (ff->fs,0 , err); + err =ext2fs_create_inode_cache (ff->fs,1024 ); +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 ;
misc/fuse2fs.c +4 -0
@@ -1277,6 +1277,10 @@ static errcode_t fuse2fs_open(struct fuse2fs *ff)log_printf (ff,"%s %s.\n" ,_ ("mounted filesystem" ), uuid); } + err =ext2fs_create_inode_cache (ff->fs,1024 ); +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 ;
PATCH 6/6 libext2fs: improve caching for inodes
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Use our new cache code to improve the ondisk inode cache inside libext2fs. Oops, list.h duplication, and libext2fs needs to link against libsupport now.
--- lib/ext2fs/ext2fsP.h | 13 ++- debugfs/Makefile.in | 8 +- e2fsck/Makefile.in | 12 +-- fuse4fs/Makefile.in | 8 +- lib/ext2fs/Makefile.in | 69 +++++++-------- lib/ext2fs/inline_data.c | 4 - lib/ext2fs/inode.c | 215 ++++++++++++++++++++++++++++++++++++---------- misc/Makefile.in | 8 +- resize/Makefile.in | 11 +- tests/fuzz/Makefile.in | 4 - tests/progs/Makefile.in | 4 - 11 files changed, 239 insertions(+), 117 deletions(-)
lib/ext2fs/ext2fsP.h +9 -4
@@ -82,21 +82,26 @@ struct dir_context {errcode_t errcode; }; +#include "support/list.h" +#include "support/cache.h" +/* * Inode cache structure */ struct ext2_inode_cache {void * buffer;blk64_t buffer_blk; -int cache_last; -unsigned int cache_size;int refcount; -struct ext2_inode_cache_ent *cache; +struct cache cache; };struct ext2_inode_cache_ent { +struct cache_node node;ext2_ino_t ino; -struct ext2_inode *inode; +uint8_t access; + +/* bytes representing a host-endian ext2_inode_large object */ +char raw[]; };/*
debugfs/Makefile.in +4 -4
@@ -38,15 +38,15 @@ SRCS= debug_cmds.c $(srcdir)/debugfs.c $(srcdir)/util.c $(srcdir)/ls.c \ $(srcdir)/../e2fsck/recovery.c $(srcdir)/do_journal.c \ $(srcdir)/do_orphan.c -LIBS= $(LIBSUPPORT) $(LIBEXT2FS) $(LIBE2P) $(LIBSS) $(LIBCOM_ERR) $(LIBBLKID) \ +LIBS= $(LIBEXT2FS) $(LIBSUPPORT) $(LIBE2P) $(LIBSS) $(LIBCOM_ERR) $(LIBBLKID) \ $(LIBUUID) $(LIBMAGIC) $(SYSLIBS) $(LIBARCHIVE) -DEPLIBS= $(DEPLIBSUPPORT) $(LIBEXT2FS) $(LIBE2P) $(DEPLIBSS) $(DEPLIBCOM_ERR) \ +DEPLIBS= $(LIBEXT2FS) $(DEPLIBSUPPORT) $(LIBE2P) $(DEPLIBSS) $(DEPLIBCOM_ERR) \ $(DEPLIBBLKID) $(DEPLIBUUID) -STATIC_LIBS= $(STATIC_LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBSS) \ +STATIC_LIBS= $(STATIC_LIBEXT2FS) $(STATIC_LIBSUPPORT) $(STATIC_LIBSS) \ $(STATIC_LIBCOM_ERR) $(STATIC_LIBBLKID) $(STATIC_LIBUUID) \ $(STATIC_LIBE2P) $(LIBMAGIC) $(SYSLIBS) -STATIC_DEPLIBS= $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSS) \ +STATIC_DEPLIBS= $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSUPPORT) $(DEPSTATIC_LIBSS) \ $(DEPSTATIC_LIBCOM_ERR) $(DEPSTATIC_LIBUUID) \ $(DEPSTATIC_LIBE2P)
e2fsck/Makefile.in +6 -6
@@ -16,22 +16,22 @@ PROGS= e2fsck MANPAGES= e2fsck.8 FMANPAGES= e2fsck.conf.5 -LIBS= $(LIBSUPPORT) $(LIBEXT2FS) $(LIBCOM_ERR) $(LIBBLKID) $(LIBUUID) \ +LIBS= $(LIBEXT2FS) $(LIBSUPPORT) $(LIBCOM_ERR) $(LIBBLKID) $(LIBUUID) \ $(LIBINTL) $(LIBE2P) $(LIBMAGIC) $(SYSLIBS) -DEPLIBS= $(DEPLIBSUPPORT) $(LIBEXT2FS) $(DEPLIBCOM_ERR) $(DEPLIBBLKID) \ +DEPLIBS= $(LIBEXT2FS) $(DEPLIBSUPPORT) $(DEPLIBCOM_ERR) $(DEPLIBBLKID) \ $(DEPLIBUUID) $(DEPLIBE2P) -STATIC_LIBS= $(STATIC_LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR) \ +STATIC_LIBS= $(STATIC_LIBEXT2FS) $(STATIC_LIBSUPPORT) $(STATIC_LIBCOM_ERR) \ $(STATIC_LIBBLKID) $(STATIC_LIBUUID) $(LIBINTL) $(STATIC_LIBE2P) \ $(LIBMAGIC) $(SYSLIBS) -STATIC_DEPLIBS= $(DEPSTATIC_LIBSUPPORT) $(STATIC_LIBEXT2FS) \ +STATIC_DEPLIBS= $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSUPPORT) \ $(DEPSTATIC_LIBCOM_ERR) $(DEPSTATIC_LIBBLKID) \ $(DEPSTATIC_LIBUUID) $(DEPSTATIC_LIBE2P) -PROFILED_LIBS= $(PROFILED_LIBSUPPORT) $(PROFILED_LIBEXT2FS) \ +PROFILED_LIBS= $(PROFILED_LIBEXT2FS) $(PROFILED_LIBSUPPORT) \ $(PROFILED_LIBCOM_ERR) $(PROFILED_LIBBLKID) $(PROFILED_LIBUUID) \ $(PROFILED_LIBE2P) $(LIBINTL) $(LIBMAGIC) $(SYSLIBS) -PROFILED_DEPLIBS= $(DEPPROFILED_LIBSUPPORT) $(PROFILED_LIBEXT2FS) \ +PROFILED_DEPLIBS= $(PROFILED_LIBEXT2FS) $(DEPPROFILED_LIBSUPPORT) \ $(DEPPROFILED_LIBCOM_ERR) $(DEPPROFILED_LIBBLKID) \ $(DEPPROFILED_LIBUUID) $(DEPPROFILED_LIBE2P)
fuse4fs/Makefile.in +4 -4
@@ -37,11 +37,11 @@ SRCS=\ 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) +PROFILED_LIBS= $(PROFILED_LIBEXT2FS) $(PROFILED_LIBSUPPORT) $(PROFILED_LIBCOM_ERR) +PROFILED_DEPLIBS= $(PROFILED_LIBEXT2FS) $(DEPPROFILED_LIBSUPPORT) $(DEPPROFILED_LIBCOM_ERR) -STATIC_LIBS= $(LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR) -STATIC_DEPLIBS= $(DEPLIBSUPPORT) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBCOM_ERR) +STATIC_LIBS= $(STATIC_LIBEXT2FS) $(STATIC_LIBSUPPORT) $(STATIC_LIBCOM_ERR) +STATIC_DEPLIBS= $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSUPPORT) $(DEPSTATIC_LIBCOM_ERR) LIBS_E2P= $(LIBE2P) $(LIBCOM_ERR) DEPLIBS_E2P= $(LIBE2P) $(DEPLIBCOM_ERR)
lib/ext2fs/Makefile.in +35 -34
160 lines not on this page · raw message
lib/ext2fs/inline_data.c +0 -4
@@ -817,10 +817,6 @@ int main(int argc, char *argv[]) "tst_inline_data: init inode cache failed\n");exit (1 ); } - -/* setup inode cache */ -for (i =0 ; i < fs->icache->cache_size; i++) - fs->icache->cache[i].ino = first_ino++; }/* test */
lib/ext2fs/inode.c +167 -48
302 lines not on this page · raw message
misc/Makefile.in +4 -4
@@ -115,11 +115,11 @@ SRCS= $(srcdir)/tune2fs.c $(srcdir)/mklost+found.c $(srcdir)/mke2fs.c $(srcdir)/ 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) +PROFILED_LIBS= $(PROFILED_LIBEXT2FS) $(PROFILED_LIBSUPPORT) $(PROFILED_LIBCOM_ERR) +PROFILED_DEPLIBS= $(PROFILED_LIBEXT2FS) $(DEPPROFILED_LIBSUPPORT) $(DEPPROFILED_LIBCOM_ERR) -STATIC_LIBS= $(LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR) -STATIC_DEPLIBS= $(DEPLIBSUPPORT) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBCOM_ERR) +STATIC_LIBS= $(STATIC_LIBEXT2FS) $(STATIC_LIBSUPPORT) $(STATIC_LIBCOM_ERR) +STATIC_DEPLIBS= $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSUPPORT) $(DEPSTATIC_LIBCOM_ERR) LIBS_E2P= $(LIBE2P) $(LIBCOM_ERR) DEPLIBS_E2P= $(LIBE2P) $(DEPLIBCOM_ERR)
resize/Makefile.in +6 -5
@@ -28,12 +28,13 @@ SRCS= $(srcdir)/extent.c \ $(srcdir)/resource_track.c \ $(srcdir)/sim_progress.c -LIBS= $(LIBE2P) $(LIBEXT2FS) $(LIBCOM_ERR) $(LIBINTL) $(SYSLIBS) -DEPLIBS= $(LIBE2P) $(LIBEXT2FS) $(DEPLIBCOM_ERR) +LIBS= $(LIBE2P) $(LIBEXT2FS) $(LIBSUPPORT) $(LIBCOM_ERR) $(LIBINTL) $(SYSLIBS) +DEPLIBS= $(LIBE2P) $(LIBEXT2FS) $(DEPLIBSUPPORT) $(DEPLIBCOM_ERR) -STATIC_LIBS= $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR) \ - $(LIBINTL) $(SYSLIBS) -DEPSTATIC_LIBS= $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBCOM_ERR) +STATIC_LIBS= $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) $(STATIC_LIBSUPPORT) \ + $(STATIC_LIBCOM_ERR) $(LIBINTL) $(SYSLIBS) +DEPSTATIC_LIBS= $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSUPPORT) \ + $(DEPSTATIC_LIBCOM_ERR) .c.o: $(E) " CC $<"
tests/fuzz/Makefile.in +2 -2
@@ -24,9 +24,9 @@ LOCAL_LDFLAGS= @fuzzer_ldflags@ LIBS= $(LIBEXT2FS) $(LIBCOM_ERR) $(LIBSUPPORT) DEPLIBS= $(LIBEXT2FS) $(DEPLIBCOM_ERR) $(DEPLIBSUPPORT) -STATIC_LIBS= $(LIBSUPPORT) $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) \ +STATIC_LIBS= $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) $(STATIC_LIBSUPPORT) \ $(STATIC_LIBCOM_ERR) -STATIC_DEPLIBS= $(DEPLIBSUPPORT) $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) \ +STATIC_DEPLIBS= $(STATIC_LIBE2P) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSUPPORT) \ $(DEPSTATIC_LIBCOM_ERR) FUZZ_LDFLAGS= $(ALL_LDFLAGS)
tests/progs/Makefile.in +2 -2
@@ -23,8 +23,8 @@ TEST_ICOUNT_OBJS= test_icount.o test_icount_cmds.o SRCS= $(srcdir)/test_icount.c \ $(srcdir)/test_rel.c -LIBS= $(LIBEXT2FS) $(LIBSS) $(LIBCOM_ERR) $(SYSLIBS) -DEPLIBS= $(LIBEXT2FS) $(DEPLIBSS) $(DEPLIBCOM_ERR) +LIBS= $(LIBEXT2FS) $(LIBSUPPORT) $(LIBSS) $(LIBCOM_ERR) $(SYSLIBS) +DEPLIBS= $(LIBEXT2FS) $(DEPLIBSUPPORT) $(DEPLIBSS) $(DEPLIBCOM_ERR) .c.o: $(E) " CC $<"
PATCH v6 4/4 fuse4fs: reclaim buffer cache under memory pressure
In reply to Darrick J. WongHi all,
Having a static buffer cache limit of 32MB is very conservative. When there's plenty of free memory, evicting metadata from the cache isn't actually a good idea, so we'd like to let it grow to handle large working sets. However, we also don't want to OOM the kernel or (in the future) the fuse4fs container cgroup, so we need to listen for memory reclamation events in the kernel.
The solution to this is to open the kernel memory pressure stall indicator files, configure an event when too much time is spent waiting for reclamation, and to trim the buffer cache when the events happen.
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-memory-reclaim
---
Commits in this patchset:
* libsupport: add pressure stall monitor
* fuse2fs: only reclaim buffer cache when there is memory pressure
* fuse4fs: enable memory pressure monitoring with service containers
* fuse2fs: flush dirty metadata periodically
--- lib/support/list.h | 6 + lib/support/psi.h | 66 ++++++ fuse4fs/Makefile.in | 3 fuse4fs/fuse4fs.c | 258 +++++++++++++++++++++- lib/support/Makefile.in | 4 lib/support/iocache.c | 19 ++ lib/support/psi.c | 557 +++++++++++++++++++++++++++++++++++++++++++++++ misc/Makefile.in | 3 misc/fuse2fs.c | 189 +++++++++++++++- 9 files changed, 1091 insertions(+), 14 deletions(-) create mode 100644 lib/support/psi.h create mode 100644 lib/support/psi.c
PATCH 1/4 libsupport: add pressure stall monitor
From: Darrick J. Wong <[email protected]>
Create some monitoring code that will sit in the background and watch for resource pressure stalls and call some sort of handler when this happens. This will be useful for shrinking the buffer cache when memory gets tight.
--- lib/support/list.h | 6 + lib/support/psi.h | 57 +++++ lib/support/Makefile.in | 4 lib/support/iocache.c | 19 ++ lib/support/psi.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 596 insertions(+) create mode 100644 lib/support/psi.h create mode 100644 lib/support/psi.c
lib/support/list.h +6 -0
@@ -4,6 +4,12 @@#include <stdbool.h> +#ifdef __GNUC__ +#define EXT2FS_ATTR (x) __attribute__(x) +#else +#define EXT2FS_ATTR (x) +#endif +struct list_head {struct list_head *next, *prev; };
lib/support/psi.h new file +57 -0
57 lines not on this page · raw message
lib/support/Makefile.in +4 -0
@@ -23,6 +23,7 @@ OBJS= bthread.o \ print_fs_flags.o \ profile_helpers.o \ prof_err.o \ + psi.o \ quotaio.o \ quotaio_v2.o \ quotaio_tree.o \
@@ -41,6 +42,7 @@ SRCS= $(srcdir)/argv_parse.c \ $(srcdir)/profile.c \ $(srcdir)/profile_helpers.c \ prof_err.c \ + $(srcdir)/psi.c \ $(srcdir)/quotaio.c \ $(srcdir)/quotaio_tree.c \ $(srcdir)/quotaio_v2.c \
@@ -204,3 +206,5 @@ cache.o: $(srcdir)/cache.c $(top_builddir)/lib/config.h \ $(top_builddir)/lib/dirpaths.h $(srcdir)/list.h $(srcdir)/cache.h iocache.o: $(srcdir)/iocache.c $(top_builddir)/lib/config.h \ $(srcdir)/iocache.h $(srcdir)/cache.h $(srcdir)/list.h +psi.o: $(srcdir)/psi.c $(top_builddir)/lib/config.h \ + $(srcdir)/psi.h $(srcdir)/list.h
lib/support/iocache.c +19 -0
@@ -452,6 +452,20 @@ static errcode_t iocache_set_option(io_channel channel, const char *option,if (!strcmp (option,"cache" ))return 0 ; +if (!strcmp (option,"cache_auto_shrink" )) { +if (!arg) +return EXT2_ET_INVALID_ARGUMENT ; +if (!strcmp (arg,"on" )) { +cache_set_flag (&data->cache,CACHE_AUTO_SHRINK ); +return 0 ; + } +if (!strcmp (arg,"off" )) { +cache_clear_flag (&data->cache,CACHE_AUTO_SHRINK ); +return 0 ; + } +return EXT2_ET_INVALID_ARGUMENT ; + } +if (!strcmp (option,"cache_blocks" )) {long long size;
@@ -467,6 +481,11 @@ static errcode_t iocache_set_option(io_channel channel, const char *option,return 0 ; } +if (!strcmp (option,"cache_shrink" )) { +cache_shrink (&data->cache); +return 0 ; + } + retval =iocache_flush_cache (data);if (retval)return retval;
lib/support/psi.c new file +510 -0
510 lines not on this page · raw message
PATCH 2/4 fuse2fs: only reclaim buffer cache when there is memory pressure
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Use the pressure stall indicator library that we added in the previous patch to make it so that we only shrink the cache when there's memory pressure.
--- fuse4fs/Makefile.in | 3 +- fuse4fs/fuse4fs.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ misc/Makefile.in | 3 +- misc/fuse2fs.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 2 deletions(-)
fuse4fs/Makefile.in +2 -1
@@ -180,7 +180,8 @@ fuse4fs.o: $(srcdir)/fuse4fs.c $(top_builddir)/lib/config.h \ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/support/bthread.h \ $(top_srcdir)/lib/support/thread.h $(top_srcdir)/lib/support/list.h \ $(top_srcdir)/lib/support/cache.h $(top_srcdir)/version.h \ - $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/iocache.h + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/iocache.h \ + $(top_srcdir)/lib/support/psi.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 \
fuse4fs/fuse4fs.c +84 -0
126 lines not on this page · raw message
misc/Makefile.in +2 -1
@@ -881,7 +881,8 @@ fuse2fs.o: $(srcdir)/fuse2fs.c $(top_builddir)/lib/config.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/support/cache.h \ - $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/support/iocache.h + $(top_srcdir)/lib/support/list.h $(top_srcdir)/lib/support/iocache.h \ + $(top_srcdir)/lib/support/psi.h e2fuzz.o: $(srcdir)/e2fuzz.c $(top_builddir)/lib/config.h \ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
misc/fuse2fs.c +84 -0
126 lines not on this page · raw message
PATCH 3/4 fuse4fs: enable memory pressure monitoring with service containers
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Ask the fuse filesystem service mount helper to open the memory pressure stall files because we cannot open them ourselves.
--- lib/support/psi.h | 9 +++++++ fuse4fs/fuse4fs.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++- lib/support/psi.c | 53 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 5 deletions(-)
lib/support/psi.h +9 -0
@@ -54,4 +54,13 @@ static inline bool psi_active(struct psi *psi) return psi != NULL; } +char *psi_system_path (enum psi_type type); +ssize_t psi_cgroup_path (enum psi_type type,char *path,size_t pathsize); + +#define PSI_OPEN_FLAGS (O_RDWR | O_NONBLOCK) + +int psi_create_from (enum psi_type type,unsigned int psi_flags, +uint64_t stall_us,uint64_t window_us,uint64_t timeout_us, +int *system_fd,int *cgroup_fd,struct psi **psip); +#endif /* __PSI_H__ */
fuse4fs/fuse4fs.c +71 -2
109 lines not on this page · raw message
lib/support/psi.c +50 -3
77 lines not on this page · raw message
PATCH 4/4 fuse2fs: flush dirty metadata periodically
In reply to Darrick J. WongFrom: Darrick J. Wong <[email protected]>
Flush dirty metadata out to disk periodically like the kernel, to reduce the potential for data loss if userspace doesn't explicitly fsync.
--- fuse4fs/fuse4fs.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++--- misc/fuse2fs.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 198 insertions(+), 12 deletions(-)
fuse4fs/fuse4fs.c +99 -6
@@ -27,6 +27,7 @@#include <unistd.h> #include <ctype.h> #include <assert.h> +#include <limits.h> #define FUSE_DARWIN_ENABLE_EXTENSIONS 0#ifdef __SET_FOB_FOR_FUSE# error Do not set magic value __SET_FOB_FOR_FUSE!!!!
@@ -309,6 +310,10 @@ struct fuse4fs {#endif struct psi *mem_psi;struct psi_handler *mem_psi_handler; + +struct bthread *flush_thread; +unsigned int flush_interval; +double last_flush; };#ifdef HAVE_FUSE4FS_SERVICE
@@ -1003,6 +1008,71 @@ fuse4fs_set_handle(struct fuse_file_info *fp, struct fuse4fs_file_handle *fh) fp->keep_cache =1 ; } +static errcode_t fuse4fs_flush (struct fuse4fs *ff,int flags) +{ +double last_flush =gettime_monotonic (); +errcode_t err; + + err =ext2fs_flush2 (ff->fs, flags); +if (err) +return err; + + ff->last_flush = last_flush; +return 0 ; +} + +static inline int fuse4fs_flush_wanted (struct fuse4fs *ff) +{ +return ff->fs !=NULL && ff->opstate ==F4OP_WRITABLE && + ff->last_flush + ff->flush_interval <=gettime_monotonic (); +} + +static void fuse4fs_flush_bthread (void *data) +{ +struct fuse4fs *ff = data; +ext2_filsys fs; +errcode_t err; +int ret =0 ; + + fs =fuse4fs_start (ff); +if (fuse4fs_flush_wanted (ff) && !bthread_cancelled (ff->flush_thread)) { + err =fuse4fs_flush (ff,0 ); +if (err) + ret =translate_error (fs,0 , err); + } +fuse4fs_finish (ff, ret); +} + +static void fuse4fs_flush_start (struct fuse4fs *ff) +{ +int ret; + +if (!ff->flush_interval) +return ; + + ret =bthread_create ("fuse4fs_flush" , fuse4fs_flush_bthread, ff, + ff->flush_interval, &ff->flush_thread); +if (ret) { +err_printf (ff,"flusher: %s.\n" ,error_message (ret)); +return ; + } + + ret =bthread_start (ff->flush_thread); +if (ret) +err_printf (ff,"flusher: %s.\n" ,error_message (ret)); +} + +static void fuse4fs_flush_cancel (struct fuse4fs *ff) +{ +if (ff->flush_thread) +bthread_cancel (ff->flush_thread); +} + +static void fuse4fs_flush_destroy (struct fuse4fs *ff) +{ +bthread_destroy (&ff->flush_thread); +} +static void get_now (struct timespec *now) {#ifdef CLOCK_REALTIME
@@ -1966,7 +2036,7 @@ static int fuse4fs_mount(struct fuse4fs *ff)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 ); + err =fuse4fs_flush (ff,0 );if (err)return translate_error (fs,0 , err); }
@@ -1994,7 +2064,7 @@ static void op_destroy(void *userdata)if (err)translate_error (fs,0 , err); - err =ext2fs_flush2 (fs,0 ); + err =fuse4fs_flush (ff,0 );if (err)translate_error (fs,0 , err); }
@@ -2186,6 +2256,7 @@ static void op_init(void *userdata, struct fuse_conn_info *conn)*/ fuse4fs_mmp_start (ff);fuse4fs_psi_start (ff); +fuse4fs_flush_start (ff);#if FUSE_VERSION >=FUSE_MAKE_VERSION (3 ,17 )/*
@@ -2553,7 +2624,7 @@ static inline int fuse4fs_dirsync_flush(struct fuse4fs *ff, ext2_ino_t ino, *flushed =0 ;return 0 ; flush: - err =ext2fs_flush2 (fs,0 ); + err =fuse4fs_flush (ff,0 );if (err)return translate_error (fs,0 , err);
@@ -4378,7 +4449,7 @@ static void op_release(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)),if ((fp->flags &O_SYNC ) &&fuse4fs_is_writeable (ff) && (fh->open_flags &EXT2_FILE_WRITE )) { - err =ext2fs_flush2 (fs,EXT2_FLAG_FLUSH_NO_SYNC ); + err =fuse4fs_flush (ff,EXT2_FLAG_FLUSH_NO_SYNC );if (err) ret =translate_error (fs, fh->ino, err); }
@@ -4409,7 +4480,7 @@ static void op_fsync(fuse_req_t req, fuse_ino_t fino EXT2FS_ATTR((unused)), 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 ); + err =fuse4fs_flush (ff,0 );if (err) ret =translate_error (fs, fh->ino, err); }
@@ -5730,6 +5801,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, const struct fuse_ctx *ctxt,err_printf (ff,"%s.\n" ,_ ("shut down requested" )); +fuse4fs_flush_cancel (ff);fuse4fs_mmp_cancel (ff);/*
@@ -5738,7 +5810,7 @@ static int ioctl_shutdown(struct fuse4fs *ff, const struct fuse_ctx *ctxt,* any of the flags. Flush whatever is dirty and shut down. */ if (ff->opstate ==F4OP_WRITABLE ) -ext2fs_flush2 (fs,0 ); +fuse4fs_flush (ff,0 ); ff->opstate =F4OP_SHUTDOWN ; fs->flags &= ~EXT2_FLAG_RW ;
@@ -6192,6 +6264,7 @@ enum {FUSE4FS_CACHE_SIZE ,FUSE4FS_DIRSYNC ,FUSE4FS_ERRORS_BEHAVIOR , + FUSE4FS_FLUSH_INTERVAL, };#define FUSE4FS_OPT (t, p, v) { t, offsetof(struct fuse4fs, p), v }
@@ -6216,6 +6289,7 @@ static struct fuse_opt fuse4fs_opts[] = {#ifdef HAVE_CLOCK_MONOTONIC FUSE4FS_OPT ("timing" , timing,1 ), #endif +FUSE_OPT_KEY ("flush_interval=%s" ,FUSE4FS_FLUSH_INTERVAL ),FUSE_OPT_KEY ("user_xattr" ,FUSE4FS_IGNORED ), FUSE_OPT_KEY("noblock_validity", FUSE4FS_IGNORED),
@@ -6274,6 +6348,21 @@ static int fuse4fs_opt_proc(void *data, const char *arg,/* do not pass through to libfuse */ return 0 ; +case FUSE4FS_FLUSH_INTERVAL : +char *p; +unsigned long val; + + errno =0 ; + val =strtoul (arg +15 , &p,0 ); +if (p != arg +strlen (arg) || errno || val >UINT_MAX ) { +fprintf (stderr,"%s: %s.\n" , arg, +_ ("Unrecognized flush interval" )); +return -1 ; + } + +/* do not pass through to libfuse */ + ff->flush_interval = val; +return 0 ;case FUSE4FS_IGNORED :return 0 ;case FUSE4FS_HELP :
@@ -6301,6 +6390,7 @@ static int fuse4fs_opt_proc(void *data, const char *arg," -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" +" -o flush=<time> flush dirty metadata on this interval\n" "\n" , outargs->argv[0]);if (key ==FUSE4FS_HELPFULL ) {
@@ -6621,6 +6711,7 @@ int main(int argc, char *argv[]) .psi_sys_mem_fd = -1, .psi_cgroup_mem_fd = -1, #endif + .flush_interval = 30, };errcode_t err;FILE *orig_stderr = stderr;
@@ -6788,6 +6879,7 @@ int main(int argc, char *argv[]) _("Mount failed while opening filesystem. Check dmesg(1) for details."));fflush (orig_stderr); } +fuse4fs_flush_destroy (&fctx);fuse4fs_psi_destroy (&fctx);fuse4fs_mmp_destroy (&fctx);fuse4fs_unmount (&fctx);
@@ -6969,6 +7061,7 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err, _("Remounting read-only due to errors.")); ff->opstate =F4OP_READONLY ; } +fuse4fs_flush_cancel (ff);fuse4fs_mmp_cancel (ff); fs->flags &= ~EXT2_FLAG_RW ;break ;
misc/fuse2fs.c +99 -6
213 lines not on this page · raw message