Add feature: notify_inode_expire_only

Resolves: rhbz#2171095
Signed-off-by: Pavel Reichl <preichl@redhat.com>
This commit is contained in:
Pavel Reichl 2023-03-24 06:33:04 +01:00
parent bfc659ed7f
commit e828426ddf
5 changed files with 480 additions and 1 deletions

View File

@ -0,0 +1,179 @@
From 4df08719f3415cde6f802a755922b7f76e198cd7 Mon Sep 17 00:00:00 2001
From: Dharmendra Singh <dsingh@ddn.com>
Date: Mon, 28 Feb 2022 11:15:06 +0000
Subject: [PATCH] Modify structures in libfuse to handle flags beyond 32 bits.
In fuse kernel, 'commit 53db28933e95 ("fuse: extend init flags")'
made the changes to handle flags going beyond 32 bits but i think
changes were not done in libfuse to handle the same.
This patch prepares the ground in libfuse for incoming FUSE kernel
patches (Atomic open + lookup) where flags went beyond 32 bits.
It makes struct same as in fuse kernel resulting in name change of
few fields.
---
include/fuse_kernel.h | 8 ++++-
lib/fuse_lowlevel.c | 75 ++++++++++++++++++++++++++++----------------------
2 files changed, 48 insertions(+), 35 deletions(-)
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -272,6 +272,7 @@ struct fuse_file_lock {
#define FUSE_POSIX_ACL (1 << 20)
#define FUSE_MAX_PAGES (1 << 22)
#define FUSE_CACHE_SYMLINKS (1 << 23)
+#define FUSE_INIT_EXT (1 << 30)
/**
* CUSE INIT request/reply flags
@@ -596,6 +597,8 @@ struct fuse_init_in {
uint32_t minor;
uint32_t max_readahead;
uint32_t flags;
+ uint32_t flags2;
+ uint32_t unused[11];
};
#define FUSE_COMPAT_INIT_OUT_SIZE 8
@@ -611,8 +614,9 @@ struct fuse_init_out {
uint32_t max_write;
uint32_t time_gran;
uint16_t max_pages;
- uint16_t padding;
- uint32_t unused[8];
+ uint16_t map_alignment;
+ uint32_t flags2;
+ uint32_t unused[7];
};
#define CUSE_INIT_INFO_MAX 4096
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -1819,7 +1819,8 @@ static void do_init(fuse_req_t req, fuse
struct fuse_session *se = req->se;
size_t bufsize = se->bufsize;
size_t outargsize = sizeof(outarg);
-
+ uint64_t inargflags = 0;
+ uint64_t outargflags = 0;
(void) nodeid;
if (se->debug) {
fprintf(stderr, "INIT: %u.%u\n", arg->major, arg->minor);
@@ -1854,39 +1855,42 @@ static void do_init(fuse_req_t req, fuse
if (arg->minor >= 6) {
if (arg->max_readahead < se->conn.max_readahead)
se->conn.max_readahead = arg->max_readahead;
- if (arg->flags & FUSE_ASYNC_READ)
+ inargflags = arg->flags;
+ if (inargflags & FUSE_INIT_EXT)
+ inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+ if (inargflags & FUSE_ASYNC_READ)
se->conn.capable |= FUSE_CAP_ASYNC_READ;
- if (arg->flags & FUSE_POSIX_LOCKS)
+ if (inargflags & FUSE_POSIX_LOCKS)
se->conn.capable |= FUSE_CAP_POSIX_LOCKS;
- if (arg->flags & FUSE_ATOMIC_O_TRUNC)
+ if (inargflags & FUSE_ATOMIC_O_TRUNC)
se->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC;
- if (arg->flags & FUSE_EXPORT_SUPPORT)
+ if (inargflags & FUSE_EXPORT_SUPPORT)
se->conn.capable |= FUSE_CAP_EXPORT_SUPPORT;
- if (arg->flags & FUSE_DONT_MASK)
+ if (inargflags & FUSE_DONT_MASK)
se->conn.capable |= FUSE_CAP_DONT_MASK;
- if (arg->flags & FUSE_FLOCK_LOCKS)
+ if (inargflags & FUSE_FLOCK_LOCKS)
se->conn.capable |= FUSE_CAP_FLOCK_LOCKS;
- if (arg->flags & FUSE_AUTO_INVAL_DATA)
+ if (inargflags & FUSE_AUTO_INVAL_DATA)
se->conn.capable |= FUSE_CAP_AUTO_INVAL_DATA;
- if (arg->flags & FUSE_DO_READDIRPLUS)
+ if (inargflags & FUSE_DO_READDIRPLUS)
se->conn.capable |= FUSE_CAP_READDIRPLUS;
- if (arg->flags & FUSE_READDIRPLUS_AUTO)
+ if (inargflags & FUSE_READDIRPLUS_AUTO)
se->conn.capable |= FUSE_CAP_READDIRPLUS_AUTO;
- if (arg->flags & FUSE_ASYNC_DIO)
+ if (inargflags & FUSE_ASYNC_DIO)
se->conn.capable |= FUSE_CAP_ASYNC_DIO;
- if (arg->flags & FUSE_WRITEBACK_CACHE)
+ if (inargflags & FUSE_WRITEBACK_CACHE)
se->conn.capable |= FUSE_CAP_WRITEBACK_CACHE;
- if (arg->flags & FUSE_NO_OPEN_SUPPORT)
+ if (inargflags & FUSE_NO_OPEN_SUPPORT)
se->conn.capable |= FUSE_CAP_NO_OPEN_SUPPORT;
- if (arg->flags & FUSE_PARALLEL_DIROPS)
+ if (inargflags & FUSE_PARALLEL_DIROPS)
se->conn.capable |= FUSE_CAP_PARALLEL_DIROPS;
- if (arg->flags & FUSE_POSIX_ACL)
+ if (inargflags & FUSE_POSIX_ACL)
se->conn.capable |= FUSE_CAP_POSIX_ACL;
- if (arg->flags & FUSE_HANDLE_KILLPRIV)
+ if (inargflags & FUSE_HANDLE_KILLPRIV)
se->conn.capable |= FUSE_CAP_HANDLE_KILLPRIV;
- if (arg->flags & FUSE_CACHE_SYMLINKS)
+ if (inargflags & FUSE_CACHE_SYMLINKS)
se->conn.capable |= FUSE_CAP_CACHE_SYMLINKS;
- if (!(arg->flags & FUSE_MAX_PAGES)) {
+ if (!(inargflags & FUSE_MAX_PAGES)) {
size_t max_bufsize =
FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+ FUSE_BUFFER_HEADER_SIZE;
@@ -1977,37 +1981,42 @@ static void do_init(fuse_req_t req, fuse
outarg.flags |= FUSE_MAX_PAGES;
outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
}
-
+ outargflags = outarg.flags;
/* Always enable big writes, this is superseded
by the max_write option */
- outarg.flags |= FUSE_BIG_WRITES;
+ outargflags |= FUSE_BIG_WRITES;
if (se->conn.want & FUSE_CAP_ASYNC_READ)
- outarg.flags |= FUSE_ASYNC_READ;
+ outargflags |= FUSE_ASYNC_READ;
if (se->conn.want & FUSE_CAP_POSIX_LOCKS)
- outarg.flags |= FUSE_POSIX_LOCKS;
+ outargflags |= FUSE_POSIX_LOCKS;
if (se->conn.want & FUSE_CAP_ATOMIC_O_TRUNC)
- outarg.flags |= FUSE_ATOMIC_O_TRUNC;
+ outargflags |= FUSE_ATOMIC_O_TRUNC;
if (se->conn.want & FUSE_CAP_EXPORT_SUPPORT)
- outarg.flags |= FUSE_EXPORT_SUPPORT;
+ outargflags |= FUSE_EXPORT_SUPPORT;
if (se->conn.want & FUSE_CAP_DONT_MASK)
- outarg.flags |= FUSE_DONT_MASK;
+ outargflags |= FUSE_DONT_MASK;
if (se->conn.want & FUSE_CAP_FLOCK_LOCKS)
- outarg.flags |= FUSE_FLOCK_LOCKS;
+ outargflags |= FUSE_FLOCK_LOCKS;
if (se->conn.want & FUSE_CAP_AUTO_INVAL_DATA)
- outarg.flags |= FUSE_AUTO_INVAL_DATA;
+ outargflags |= FUSE_AUTO_INVAL_DATA;
if (se->conn.want & FUSE_CAP_READDIRPLUS)
- outarg.flags |= FUSE_DO_READDIRPLUS;
+ outargflags |= FUSE_DO_READDIRPLUS;
if (se->conn.want & FUSE_CAP_READDIRPLUS_AUTO)
- outarg.flags |= FUSE_READDIRPLUS_AUTO;
+ outargflags |= FUSE_READDIRPLUS_AUTO;
if (se->conn.want & FUSE_CAP_ASYNC_DIO)
- outarg.flags |= FUSE_ASYNC_DIO;
+ outargflags |= FUSE_ASYNC_DIO;
if (se->conn.want & FUSE_CAP_WRITEBACK_CACHE)
- outarg.flags |= FUSE_WRITEBACK_CACHE;
+ outargflags |= FUSE_WRITEBACK_CACHE;
if (se->conn.want & FUSE_CAP_POSIX_ACL)
- outarg.flags |= FUSE_POSIX_ACL;
+ outargflags |= FUSE_POSIX_ACL;
if (se->conn.want & FUSE_CAP_CACHE_SYMLINKS)
- outarg.flags |= FUSE_CACHE_SYMLINKS;
+ outargflags |= FUSE_CACHE_SYMLINKS;
+
+ outarg.flags = outargflags;
+
+ if (inargflags & FUSE_INIT_EXT)
+ outarg.flags2 = outargflags >> 32;
outarg.max_readahead = se->conn.max_readahead;
outarg.max_write = se->conn.max_write;
if (se->conn.proto_minor >= 13) {

118
0006-BZ_2171095.patch Normal file
View File

@ -0,0 +1,118 @@
From 22cc0c761ad23395a8220ce1954cfb8a64f36ff4 Mon Sep 17 00:00:00 2001
From: HereThereBeDragons <HereThereBeDragons@users.noreply.github.com>
Date: Thu, 27 Oct 2022 17:52:10 +0200
Subject: [PATCH] Initial patch provided by Miklos Szeredi
<mszeredi@redhat.com>
---
include/fuse_kernel.h | 8 +++++++-
include/fuse_lowlevel.h | 8 ++++++++
lib/fuse_lowlevel.c | 18 ++++++++++++++----
lib/fuse_versionscript | 1 +
4 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h
index 09da620b5..e0666a1f7 100644
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -336,6 +336,12 @@
*/
#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+/**
+ * notify_inval_entry flags
+ * FUSE_EXPIRE_ONLY
+ */
+#define FUSE_EXPIRE_ONLY (1 << 0)
+
enum fuse_opcode {
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, /* no reply */
@@ -800,7 +806,7 @@ struct fuse_notify_inval_inode_out {
struct fuse_notify_inval_entry_out {
uint64_t parent;
uint32_t namelen;
- uint32_t padding;
+ uint32_t flags;
};
struct fuse_notify_delete_out {
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
index 53f0fcf4b..c955cc4bb 100644
--- a/include/fuse_lowlevel.h
+++ b/include/fuse_lowlevel.h
@@ -1675,6 +1675,14 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
const char *name, size_t namelen);
+enum fuse_expire_flags {
+ FUSE_LL_EXPIRE_ONLY = (1 << 0),
+};
+
+int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+ const char *name, size_t namelen,
+ enum fuse_expire_flags flags);
+
/**
* This function behaves like fuse_lowlevel_notify_inval_entry() with
* the following additional effect (at least as of Linux kernel 4.8):
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index e82cd9e9f..7b9d71043 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -2161,21 +2161,24 @@
return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
}
-int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
- const char *name, size_t namelen)
+int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+ const char *name, size_t namelen,
+ enum fuse_expire_flags flags)
{
struct fuse_notify_inval_entry_out outarg;
struct iovec iov[3];
if (!se)
return -EINVAL;
-
+
if (se->conn.proto_major < 6 || se->conn.proto_minor < 12)
return -ENOSYS;
outarg.parent = parent;
outarg.namelen = namelen;
- outarg.padding = 0;
+ outarg.flags = 0;
+ if (flags & FUSE_LL_EXPIRE_ONLY)
+ outarg.flags |= FUSE_EXPIRE_ONLY;
iov[1].iov_base = &outarg;
iov[1].iov_len = sizeof(outarg);
@@ -2292,6 +2295,13 @@ int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
}
+int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+ const char *name, size_t namelen)
+{
+ return fuse_lowlevel_notify_expire_entry(se, parent, name, namelen, 0);
+}
+
+
int fuse_lowlevel_notify_delete(struct fuse_session *se,
fuse_ino_t parent, fuse_ino_t child,
const char *name, size_t namelen)
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index 7e50e7507..795ea4aa9 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -151,6 +151,7 @@
FUSE_3.3 {
global:
fuse_open_channel;
+ fuse_lowlevel_notify_expire_entr;
} FUSE_3.2;
# Local Variables:

125
0007-BZ_2171095-cap.patch Normal file
View File

@ -0,0 +1,125 @@
From 6b1612e3a85b993f82124cccf149df3830e4a9c5 Mon Sep 17 00:00:00 2001
From: HereThereBeDragons <HereThereBeDragons@users.noreply.github.com>
Date: Thu, 27 Oct 2022 17:52:10 +0200
Subject: [PATCH] adding comments and capability discovery, enum for flags
moved to top of file
---
example/printcap.c | 2 ++
include/fuse_common.h | 16 ++++++++++++++++
include/fuse_lowlevel.h | 40 ++++++++++++++++++++++++++++++++++++----
lib/fuse_lowlevel.c | 2 ++
4 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/example/printcap.c b/example/printcap.c
index edfd8f531..48679883c 100644
--- a/example/printcap.c
+++ b/example/printcap.c
@@ -79,6 +79,8 @@
printf("\tFUSE_CAP_POSIX_ACL\n");
if(conn->capable & FUSE_CAP_CACHE_SYMLINKS)
printf("\tFUSE_CAP_CACHE_SYMLINKS\n");
+ if(conn->capable & FUSE_CAP_EXPIRE_ONLY)
+ printf("\tFUSE_CAP_EXPIRE_ONLY\n");
fuse_session_exit(se);
}
diff --git a/include/fuse_common.h b/include/fuse_common.h
index e9d874556..dbba05af8 100644
--- a/include/fuse_common.h
+++ b/include/fuse_common.h
@@ -338,6 +338,22 @@
#define FUSE_CAP_CACHE_SYMLINKS (1 << 23)
/**
+ * Indicates support that dentries can be expired or invalidated.
+ *
+ * Expiring dentries, instead of invalidating them, makes a difference for
+ * overmounted dentries, where plain invalidation would detach all submounts
+ * before dropping the dentry from the cache. If only expiry is set on the
+ * dentry, then any overmounts are left alone and until ->d_revalidate()
+ * is called.
+ *
+ * Note: ->d_revalidate() is not called for the case of following a submount,
+ * so invalidation will only be triggered for the non-overmounted case.
+ * The dentry could also be mounted in a different mount instance, in which case
+ * any submounts will still be detached.
+*/
+#define FUSE_CAP_EXPIRE_ONLY (1 << 26)
+
+/**
* Ioctl flags
*
* FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
index c955cc4bb..6a1a5d534 100644
--- a/include/fuse_lowlevel.h
+++ b/include/fuse_lowlevel.h
@@ -127,6 +127,15 @@ struct fuse_forget_data {
uint64_t nlookup;
};
+/**
+ * Flags for fuse_lowlevel_notify_expire_entry()
+ * 0 = invalidate entry
+ * FUSE_LL_EXPIRE_ONLY = expire entry
+*/
+enum fuse_expire_flags {
+ FUSE_LL_EXPIRE_ONLY = (1 << 0),
+};
+
/* 'to_set' flags in setattr */
#define FUSE_SET_ATTR_MODE (1 << 0)
#define FUSE_SET_ATTR_UID (1 << 1)
@@ -1675,10 +1684,33 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
const char *name, size_t namelen);
-enum fuse_expire_flags {
- FUSE_LL_EXPIRE_ONLY = (1 << 0),
-};
-
+/**
+ * Notify to expire or invalidate parent attributes and the dentry
+ * matching parent/name
+ *
+ * Underlying function for fuse_lowlevel_notify_inval_entry().
+ *
+ * In addition to invalidating an entry, it also allows to expire an entry.
+ * In that case, the entry is not forcefully removed from kernel cache
+ * but instead the next access to it forces a lookup from the filesystem.
+ *
+ * This makes a difference for overmounted dentries, where plain invalidation
+ * would detach all submounts before dropping the dentry from the cache.
+ * If only expiry is set on the dentry, then any overmounts are left alone and
+ * until ->d_revalidate() is called.
+ *
+ * Note: ->d_revalidate() is not called for the case of following a submount,
+ * so invalidation will only be triggered for the non-overmounted case.
+ * The dentry could also be mounted in a different mount instance, in which case
+ * any submounts will still be detached.
+ *
+ * @param se the session object
+ * @param parent inode number
+ * @param name file name
+ * @param namelen strlen() of file name
+ * @param flags flags to control if the entry should be expired or invalidated
+ * @return zero for success, -errno for failure
+*/
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
const char *name, size_t namelen,
enum fuse_expire_flags flags);
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 7b9d71043..7d7630925 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -1991,6 +1991,8 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
bufsize = max_bufsize;
}
}
+ if (arg->minor >= 38)
+ se->conn.capable |= FUSE_CAP_EXPIRE_ONLY;
} else {
se->conn.max_readahead = 0;
}

View File

@ -0,0 +1,45 @@
From: Miklos Szeredi <mszeredi@redhat.com>
Subject: libfuse: add feature flag for expire-only
Add the FUSE_HAS_EXPIRE_ONLY flag. This should be used to set the
FUSE_CAP_EXPIRE_ONLY capability flag on the low level API instead of
checking for the version.
Checking the version doesn't work if the feature is backported to an
earlier kernel.
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
include/fuse_kernel.h | 2 ++
lib/fuse_lowlevel.c | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -248,6 +248,7 @@
* FUSE_POSIX_ACL: filesystem supports posix acls
* FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages
* FUSE_CACHE_SYMLINKS: cache READLINK responses
+ * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
@@ -272,6 +273,7 @@
#define FUSE_MAX_PAGES (1 << 22)
#define FUSE_CACHE_SYMLINKS (1 << 23)
#define FUSE_INIT_EXT (1 << 30)
+#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
/**
* CUSE INIT request/reply flags
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -2008,7 +2008,7 @@ void do_init(fuse_req_t req, fuse_ino_t
bufsize = max_bufsize;
}
}
- if (arg->minor >= 38)
+ if (inargflags & FUSE_HAS_EXPIRE_ONLY)
se->conn.capable |= FUSE_CAP_EXPIRE_ONLY;
} else {
se->conn.max_readahead = 0;

View File

@ -4,7 +4,7 @@
Name: fuse
Version: %{fuse2ver}
Release: 16%{?dist}
Release: 17%{?dist}
Summary: File System in Userspace (FUSE) v2 utilities
License: GPL+
URL: http://fuse.sf.net
@ -26,6 +26,10 @@ Patch8: 0001-Synchronize-fuse_kernel.h.patch
Patch9: 0002-fuse_lowlevel-Add-max_pages-support-384.patch
Patch10: 0003-Allow-caching-symlinks-in-kernel-page-cache.-551.patch
Patch11: 0004-Add-support-for-in-kernel-readdir-caching.patch
Patch12: 0005-BZ_217095_Modify-structures-in-libfuse-to-handle-flags-beyond-rhel-8.patch
Patch13: 0006-BZ_2171095.patch
Patch14: 0007-BZ_2171095-cap.patch
Patch15: 0008-BZ_217095-libfuse-add-feature-flag-for-expire-only.patch
Requires: which
Conflicts: filesystem < 3
@ -120,6 +124,10 @@ pushd lib%{name}-%{name}-%{fuse3ver}
%patch9 -p1
%patch10 -p1
%patch11 -p1
%patch12 -p1
%patch13 -p1
%patch14 -p1
%patch15 -p1
popd
@ -251,6 +259,10 @@ rm -f %{buildroot}/usr/lib/udev/rules.d/99-fuse3.rules
%{_includedir}/fuse3/
%changelog
* Thu Mar 23 2023 Pavel Reichl <preichl@redhat.com> - 2.9.7-17
- Add feature_notify_inode_expire_only
- Fixes rhbz#2171095
* Mon May 30 2022 Pavel Reichl <preichl@redhat.com> - 2.9.7-16
- Back-port max_pages support,
- caching symlinks in kernel page cache,