From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D118743DA31; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; cv=none; b=GoO6jiyF6LJBElPeTXNOYK3FrTwpSdvHJrOpAI0V6c737u34IeJGuD2IcVGuyGHqbkUFx6kJ53THdinxx8TUWawlvWDDegBPqJ8nHjGJBIaAJMIaFp6rczJYGyEb1pUtOEQE+u+DRDj6azJf8fXXI1TToVLzo+7n79QJa6EZAyA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; c=relaxed/simple; bh=2L5FYYP+P26CfeynEaGsZgzkZzLqEnRyMSkT7K4WK6s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mEyTf98+uhiKnPB0RlgNS0XlpDN5XDTGnryvE6Qf7LVqKjtRS8rTyvEA8QpFEkFuq0s2SJI66fMkzVlIg5CVmSHMMzLMtv/mhD9pyJYbplGpsGiWKQ3sxsqoUWPOVR2qyL9zdNkIbk6hbJvrOh0i2kUioSpoHUWzd4KG3qf0s1w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=YiOFIFxH; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="YiOFIFxH" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=tOMFOt5Jf/86iCoy9hzBOMyGjPBsBkQw+YVR+eCXoQo=; b= YiOFIFxHeVo6pa8ha5Z+4G4JspvmvofxZyOrTwOkcXQ3eyTxNK0YHnxCYEE6cJ0vYAFT/y9JO74dd ubsubtQWxae8ombD6QF8i9Ndl0ppVX32y681Ne7zXmILZj4eAJ3IcjBrlGVzb/OYMLtqiA9Fb30Bi nUJfNs9rm7B5J+ry5ArMJ1f4/oGdzQiCdwkdi9IFnL3qF8DsS+BTbmZfoIbvTsenQvin1payUSZBZ RvHBjWyJ6uu6ZavyHHTX1lyKUKOWzlKwpaXR+aYjv22zWTKL8QOvRg2VSJVV7brCc1NsaBHA+ZaEx u84cinV34J57vUGMKimPHXpchscPHgceCQ==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3o-004Xxy-1b; Mon, 17 Aug 2026 16:11:32 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 1/8] Documentation: fuse: add document on caches being used by FUSE Date: Mon, 17 Aug 2026 15:11:49 +0100 Message-ID: <20260817141156.6079-2-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This new file aims at documenting the caches that are used by FUSE. At the moment only symlink, attributes, ACLs and readdir caches are described. Signed-off-by: Luis Henriques --- .../filesystems/fuse/fuse-caches.rst | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 Documentation/filesystems/fuse/fuse-caches.rst diff --git a/Documentation/filesystems/fuse/fuse-caches.rst b/Documentation= /filesystems/fuse/fuse-caches.rst new file mode 100644 index 000000000000..071febf45d00 --- /dev/null +++ b/Documentation/filesystems/fuse/fuse-caches.rst @@ -0,0 +1,142 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +FUSE Caches +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +This document summarises the different types of caches that are used in FU= SE. +For each cache type, it attempts to document the rules that are followed to +insert, validate and invalidate data into the cache. + +symlink caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Whenever there's a link resolution request, the VFS will call into +``fuse_get_link()`` which will then send a ``FUSE_READLINK`` request to the +user-space FUSE server. However, the server can ask the kernel to cache all +links resolutions by setting the ``FUSE_CACHE_SYMLINKS`` flag during the +``FUSE_INIT`` negotiation. + +If this flag is set, FUSE will immediately call into the VFS +``__page_get_link()`` from the ``->get_link()`` inode operation. The first= time +this is done for a specific link, it will end-up sending the ``FUSE_READLI= NK`` +to user-space but the link contents will then be added into page-cache. Th= e next +time the link needs to be resolved, it will use the link content that is a= lready +cached, and will only fallback into sending the request to use-space if the +folio isn't up-to-date. + +Attributes caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Attributes obtained from user-space, for example when an inode is first +looked-up, are cached in the kernel. However, these attributes have a time= out +associated and once expired they are invalidated. + +Thus, the ``FUSE_GETATTR`` operation will be sent to user-space only if the +attributes aren't yet available, the attributes aren't valid (timeout), or= if +there is an explicit request for doing so (for example, by using the +``AT_STATX_FORCE_SYNC`` flag in ``statx``). This may happen in the followi= ng +situations: + +#. An explicit request from VFS to get the attributes for an inode (throug= h the + ``->getattr()`` callback). +#. When an ``->llseek()`` is requested to FUSE with a type of request + (``whence``): + + - ``SEEK_{HOLE,DATA}`` and the user-space doesn't implement the + ``FUSE_LSEEK`` operation (it has returned ``ENOSYS``), or + - ``SEEK_END`` + +#. When doing a buffered read past EOF or automatic page cache invalidatio= n mode + is enabled (``FUSE_AUTO_INVAL_DATA``). +#. When doing a buffered write with write-back cache enabled + (``FUSE_CAP_WRITEBACK_CACHE``). + +ACL caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +FUSE has allowed the usage of POSIX ACLs for a long time as they could be = set +and accessed simply as extended attributes. However, it was only with the +addition of the ``FUSE_POSIX_ACL`` flag that ACLs started to be fully supp= orted. +Without this flag, ACLs can still be set, but the VFS won't use them for +performing permission checks - that would be the user-space server's +responsibility. + +Also, without setting ``FUSE_POSIX_ACL``, ACLs will not be cached by the k= ernel. +In this case, new inodes ``i_acl`` and ``i_default_acl`` fields will be se= t to +``ACL_DONT_CACHE``. + +On the other hand, if ``FUSE_POSIX_ACL`` is set during ``FUSE_INIT``, when= an +ACL is accessed the VFS layer will first check if it's already cached. If = it is +not, FUSE ``->get_acl`` operation is called, which will eventually send a +user-space request. Future accesses to this inode ACL will then use the ca= ched +data. + +Setting an ACL in an inode, however, won't cache it immediately. It will s= end +user-space a request with the new ACL, and the FUSE server may perform some +modifications before storing it. + +On the other hand, ACLs will be removed for the cache in the following +situations: + +- When setting an ACL in an inode and the user-space server has set the + ``FUSE_POSIX_ACL`` flag, all previously cached ACLs for this inode will= be + invalidated. +- When invalidating an inode through the ``FUSE_NOTIFY_INVAL_INODE`` oper= ation. +- When ``->d_revalidate()`` is called for a dentry that requires a lookup= (e.g. + it has expired) and that lookup operation is successful. +- When the VFS needs to check access rights for an inode (by calling + ``->permission()``), attributes may need to be refreshed. If that happe= ns, + any cached ACLs for that inode will be invalidated. +- After setting an inode attribute (i.e. operation ``FUSE_SETATTR`` is se= nt to + user-space), the user-space server may have also updated the ACLs, so a= ny + cached ACLs for this inode are also invalidated. +- While processing ``FUSE_READDIRPLUS`` and a new dentry is added (unless= this + dentry is already being looked up (``DCACHE_PAR_LOOKUP``)) +- In general, when there is the need to sent a ``FUSE_STATX`` or + ``FUSE_GETATTR`` to user-space (e.g. because the attributes have expire= d). + This may happen in the following cases: + + - When doing an ``->llseek()`` on a file with ``SEEK_END``, ``SEEK_HOL= E`` or + ``SEEK_DATA``. + - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time (to + automatically invalidate cached pages), and a buffered read + (``->read_iter()``) past EOF is done on a non-passthrough file. + - When the ``FUSE_WRITEBACK_CACHE`` flag is set at ``INIT`` time, and a + buffered write (``->write_iter()``) past EOF is done on a non-passth= rough + file. + - When the ``FUSE_AUTO_INVAL_DATA`` flag is set at ``INIT`` time and t= he VFS + needs to read a directory contents (``->iterate_shared()``) for a + directory that is allowed to be cached. + +readdir caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +When opening a directory for doing a readdir, a ``FUSE_OPENDIR`` will be s= ent +and the user-space server will be responsible for setting the open flags r= elated +with caching, namely ``FOPEN_KEEP_CACHE`` and ``FOPEN_CACHE_DIR``. + +If neither flags are set by the user-space FUSE server, then every ``readd= ir`` +will result in a ``FUSE_READDIR`` (or ``FUSE_READDIRPLUS``) request being = sent. +If ``FOPEN_CACHE_DIR`` is set by the server, then the result of a ``readdi= r`` +will be cached by the kernel and reused. However, if ``FOPEN_KEEP_CACHE`` = isn't +also set, the cache will be invalidated next time the directory is open. + +The readdir cache will also expire and resetted in the following situation= s if: + +- The inode ``mtime`` doesn't match the cache ``mtime``, +- The inode ``iversion`` doesn't match the cache ``iversion``, +- The FUSE connection ``epoch`` doesn't match the cache ``epoch``. + +dentry caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +TBD + +data caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +TBD From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A940C43CED7; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975904; cv=none; b=DMODOoS5ZaOcJiKttfgxH7DCGnoj/ge8wr2bBbrvzZit5Elm9AJaz9o6OKErkd3S4qJKHPExp0ooYYgvu3/dmfNmhjpDHlT3ZmqOAO/kkVYv3WhDt9TpGRq8yF6po4G81ruVu9e0cTciftkyi8RWybFsU1iRzkfb160z2Sx/ID8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975904; c=relaxed/simple; bh=ViSoEq4vpyD60Ldrif6uaDkuIiekUXfqjSAHTbn9nkU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mEc3gdSZgsWGE4JtxtdTEHkMcvifNZRigq+rLJoLFuaYgqIj+M1JpMbkfG7q5QaNFssGG0SBAxjLxEbfzHLbhUbM6z5PmYp2BFmzil3ZJj00o7ySY4SgXqh/kf4qPjTdIKJjtaqwN0FczONJNo/gH+xoifpMAHsadqWF6fn9jko= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=LWfsLOxl; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="LWfsLOxl" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=OoMzp7zA7wu3QMs1/+FxdtF6S2j8fxJSGn3vUluin+U=; b= LWfsLOxlWRQtq9KB1wLNzcbknKPZqSd5oNrKeuxLb81kJ62tIbdSv582j71/sJR8RU3BdT+sMz0u9 GW6YD4+SLWOi9UQQCUne9cg0+ldIqszV17TQSRPW6LrXmJ7C7uRuRVL/MYPjZoizEh93rIGq5xbw8 33BVoFRbFx9g7hJxQn/Z/p0woPvShOjdx0Z/5uxCTdojQ+rQHbCQkqDcqdIQsD0vtRcV6ZgP+77lz YeUTpgIir8NNQnboUGV9PLWju2ZUww3OHoqAQZNtHphhu/RGJbJiBrzpq7gAmjYvPqGFrBGM3r9UG vCwoA23AzU6cR6aNVuQX/29oHzf5uX5NEg==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3o-004Xy2-HL; Mon, 17 Aug 2026 16:11:32 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 2/8] selftests/fuse: convert fusectl test to fuse3 Date: Mon, 17 Aug 2026 15:11:50 +0100 Message-ID: <20260817141156.6079-3-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Since it is probably not worth adding new fuse kselftests based on fuse2, it is a good idea to convert the single existing test to fuse3. The conversion is trivial, as it only requires some changes to function signatures (the gettattr and truncate fuse operations), and to the filler() helper. Signed-off-by: Luis Henriques Reviewed-by: Amir Goldstein --- .../selftests/filesystems/fuse/Makefile | 21 +++++++------------ .../selftests/filesystems/fuse/fuse_mnt.c | 17 ++++++++------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index f47141484275..54411bc349d2 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -2,30 +2,23 @@ =20 CFLAGS +=3D -Wall -O2 -g $(KHDR_INCLUDES) =20 -TEST_GEN_PROGS :=3D fusectl_test +TEST_GEN_PROGS :=3D fusectl_test fuse_acl_cache_test TEST_GEN_FILES :=3D fuse_mnt =20 -# fuse_acl_cache_test requires libfuse3; add it only when the library is p= resent. -ACL_CFLAGS :=3D $(shell pkg-config fuse3 --cflags 2>/dev/null) -ACL_LDLIBS :=3D $(shell pkg-config fuse3 --libs 2>/dev/null) -ifneq ($(ACL_CFLAGS),) -TEST_GEN_PROGS +=3D fuse_acl_cache_test -endif - include ../../lib.mk =20 -VAR_CFLAGS :=3D $(shell pkg-config fuse --cflags 2>/dev/null) +VAR_CFLAGS :=3D $(shell pkg-config fuse3 --cflags 2>/dev/null) ifeq ($(VAR_CFLAGS),) -VAR_CFLAGS :=3D -D_FILE_OFFSET_BITS=3D64 -I/usr/include/fuse +VAR_CFLAGS :=3D -D_FILE_OFFSET_BITS=3D64 -I/usr/include/fuse3 endif =20 -VAR_LDLIBS :=3D $(shell pkg-config fuse --libs 2>/dev/null) +VAR_LDLIBS :=3D $(shell pkg-config fuse3 --libs 2>/dev/null) ifeq ($(VAR_LDLIBS),) -VAR_LDLIBS :=3D -lfuse -pthread +VAR_LDLIBS :=3D -lfuse3 -pthread endif =20 $(OUTPUT)/fuse_mnt: CFLAGS +=3D $(VAR_CFLAGS) $(OUTPUT)/fuse_mnt: LDLIBS +=3D $(VAR_LDLIBS) =20 -$(OUTPUT)/fuse_acl_cache_test: CFLAGS +=3D $(ACL_CFLAGS) -$(OUTPUT)/fuse_acl_cache_test: LDLIBS +=3D $(ACL_LDLIBS) +$(OUTPUT)/fuse_acl_cache_test: CFLAGS +=3D $(VAR_CFLAGS) +$(OUTPUT)/fuse_acl_cache_test: LDLIBS +=3D $(VAR_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c b/tools/te= sting/selftests/filesystems/fuse/fuse_mnt.c index d12b17f30fad..5d335fa5cf05 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_mnt.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_mnt.c @@ -4,7 +4,7 @@ * Creates a simple FUSE filesystem with a single read-write file (/test) */ =20 -#define FUSE_USE_VERSION 26 +#define FUSE_USE_VERSION 31 =20 #include #include @@ -20,7 +20,8 @@ static char *content; static size_t content_size =3D 0; static const char test_path[] =3D "/test"; =20 -static int test_getattr(const char *path, struct stat *st) +static int test_getattr(const char *path, struct stat *st, + struct fuse_file_info *fi) { memset(st, 0, sizeof(*st)); =20 @@ -41,14 +42,15 @@ static int test_getattr(const char *path, struct stat *= st) } =20 static int test_readdir(const char *path, void *buf, fuse_fill_dir_t fille= r, - off_t offset, struct fuse_file_info *fi) + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { if (strcmp(path, "/")) return -ENOENT; =20 - filler(buf, ".", NULL, 0); - filler(buf, "..", NULL, 0); - filler(buf, test_path + 1, NULL, 0); + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, test_path + 1, NULL, 0, FUSE_FILL_DIR_DEFAULTS); =20 return 0; } @@ -107,7 +109,8 @@ static int test_write(const char *path, const char *buf= , size_t size, return size; } =20 -static int test_truncate(const char *path, off_t size) +static int test_truncate(const char *path, off_t size, + struct fuse_file_info *fi) { if (strcmp(path, test_path) !=3D 0) return -ENOENT; From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A25B041D628; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975906; cv=none; b=t3U7aUHqOLKA2veEKoWf2AwOYRE8hqCrpzGE4b8rSzZYCERretg9T+7HvDQ+HG+sqMOdscQXwneHxl4J1lmtR8R601A8ogTMW3/hlptCZkn1YxNmDeKIXlgOEqT5VW3s6Lo+I6Egf6FUxc0aMjRdSMSZo1YNNv7V66mg0qcEiVU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975906; c=relaxed/simple; bh=gzdMPSY5857b33eoqcvliN4DZtcbHYo5tfiX4/6mTiM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jHf6b/EkzU2HyJW2SrzaEyNs/l3vPDcL2TCc7B3U1KpAVkFPaIKiMIC8jsH5NEI/gJL5BseFCG9kB4fluxaxrsffHrdGHvABlsq7He1Yr9pT/XpbqOk0O3QZQpzI+MzAT7kWjHhE+0i3PtIzf/e4CSJAjPDpPc1c+RxY3NyiUVY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=rpxXcis0; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="rpxXcis0" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=YxrwnrFhtOfXg8AzEHEtSrO2yR4kSnVVGUJ2cFKcvN4=; b= rpxXcis0UvU9CK5L6qSwYGL1Oqn9s+UUloY3QsuB3IGb3E/V2taQWDq+likoAy4Hyxyw7b9fns912 Udj6Cf9k9kzmvnEXp9iX2PpP0zJohpy4O5++NpBULew00hs5vmQWYGxcgXRuJv0FdFt4PCcdFTtjM kJQaUbjE5EfjrPH/ffgSM0KA4xeuVC21PG8i0SxYxXAEB+eTD+ezigucPYXSODVsVfzN+mC7BjHQx fTwTen8Q/AcUklCW0yDWNcguPXOPwLvqrkWnf99MumV1JIu3A7Gvdq8G+TkMWRF1LknDVuM0RlxpL Kd73v4YEFre13cCvBYQovvUW0DTxLRrcXw==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3p-004Xy6-00; Mon, 17 Aug 2026 16:11:33 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 3/8] selftests/fuse: check that fusectlfs is mounted Date: Mon, 17 Aug 2026 15:11:51 +0100 Message-ID: <20260817141156.6079-4-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The control filesystem for FUSE needs to be mounted for the fusectl_test to be successfully run. Skip the test is that is not the case. Signed-off-by: Luis Henriques Reviewed-by: Amir Goldstein --- tools/testing/selftests/filesystems/fuse/fusectl_test.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/testing/selftests/filesystems/fuse/fusectl_test.c b/tool= s/testing/selftests/filesystems/fuse/fusectl_test.c index 0d1d012c35ed..2dcfd3ebe0d5 100644 --- a/tools/testing/selftests/filesystems/fuse/fusectl_test.c +++ b/tools/testing/selftests/filesystems/fuse/fusectl_test.c @@ -48,6 +48,7 @@ FIXTURE_SETUP(fusectl) uid_t uid =3D getuid(); gid_t gid =3D getgid(); char buf[32]; + char path_buf[PATH_MAX]; =20 /* Setup userns */ ASSERT_EQ(unshare(CLONE_NEWNS|CLONE_NEWUSER), 0); @@ -93,6 +94,12 @@ FIXTURE_SETUP(fusectl) strerror(errno)); =20 self->connection =3D statbuf.st_dev; + + sprintf(path_buf, "/sys/fs/fuse/connections/%d", self->connection); + if (access(path_buf, F_OK) !=3D 0) + SKIP(return, + "fusectl doesn't seem to be mounted: %s\n", + strerror(errno)); } =20 FIXTURE_TEARDOWN(fusectl) From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 54A7643BDCD; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; cv=none; b=izEsNpx84EvifjrDyAFhkHqPKmclK0qUW+5Xt1q7Jio0u+7MnNbpmp5pGhdVKWDZuyOl2sshhyNyJiD7kpejx8rNssQOrpQgsyR1eGtoZ0KVTDgj+MJRPWdUXee8KClSTxoiPVvkkmS12KYF/jIv4gwda6BVHpfU8LB+ElT72Qk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; c=relaxed/simple; bh=xNe3ocTe33Er3vieooNBkUBf0xS8WtLtVsylzb0nOzA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XKsRysgvHQFevBPWZSoZInrbxRHMP43f5dVteVvexKxZlZQ+b9Qu4EXZLuvWxD5Y0BH9ThCQA1p21JqFh8BLT3ZPKNptCo5lAwls/or9/NMSBbE6TcaKxv12JlIluIfwwsNLWPGSjmMNzvPCTHt5/5TQC30IOBxzqUWQa/ZEXx4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=hc79EFDX; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="hc79EFDX" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=vzt8+0kE+12ca/bfqPHftapgOIcUtreJoiZgTf5pCDc=; b= hc79EFDXA5xnUxwm8rFnN+4EHxUVFF1HcqVCiT0xYHvaFvGv4p5srWExsWo3M2h6eEOUkilxLlm4l AZpJB4wvGOdIBVfC23A94BsQMP/nc3FOcCYp5XGR5vxOo55Eh6UxMtl/evYCcyNwEY8QQVHiA+3oV bTTdOKK/pfPvQVEAwde3vSHJXf9neshsvi9iqVAc3mqmC4SpCtWUX1WGd+QdcHSngQptsi1R3wday bAB0GDXLqOMiFvh0xofPlWavMZwlxPDLsHFnmn/6plIlnZq0kK1qRcHR8yt04gy3j/74wVXAclpp1 4SrTUy5H51IKcCJjdK5Pr/HpSLGBycgaDg==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3p-004XyA-Gi; Mon, 17 Aug 2026 16:11:33 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 4/8] selftests/fuse: add fuse symlink caching test Date: Mon, 17 Aug 2026 15:11:52 +0100 Message-ID: <20260817141156.6079-5-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This patch adds a simple test that allows to verify that, when resolving a symlink, user-space is called only the first time when caching is enabled or, if caching is disabled, every time the symlink resolution is requested. Signed-off-by: Luis Henriques --- .../selftests/filesystems/fuse/.gitignore | 1 + .../selftests/filesystems/fuse/Makefile | 5 +- .../fuse/fuse_symlink_cache_test.c | 204 ++++++++++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_symlink_c= ache_test.c diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/te= sting/selftests/filesystems/fuse/.gitignore index 3e72e742d08e..873304f8d1a1 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only fuse_mnt fusectl_test +fuse_symlink_cache_test diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index 54411bc349d2..4091b1cc939e 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -2,7 +2,7 @@ =20 CFLAGS +=3D -Wall -O2 -g $(KHDR_INCLUDES) =20 -TEST_GEN_PROGS :=3D fusectl_test fuse_acl_cache_test +TEST_GEN_PROGS :=3D fusectl_test fuse_acl_cache_test fuse_symlink_cache_te= st TEST_GEN_FILES :=3D fuse_mnt =20 include ../../lib.mk @@ -22,3 +22,6 @@ $(OUTPUT)/fuse_mnt: LDLIBS +=3D $(VAR_LDLIBS) =20 $(OUTPUT)/fuse_acl_cache_test: CFLAGS +=3D $(VAR_CFLAGS) $(OUTPUT)/fuse_acl_cache_test: LDLIBS +=3D $(VAR_LDLIBS) + +$(OUTPUT)/fuse_symlink_cache_test: CFLAGS +=3D $(VAR_CFLAGS) +$(OUTPUT)/fuse_symlink_cache_test: LDLIBS +=3D $(VAR_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_te= st.c b/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c new file mode 100644 index 000000000000..c922db832c68 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Simple filesystem to test FUSE symlink cache + * + * This is a simple FUSE filesystem that contains two objects: a file named + * 'file' and a symlink to that file named 'link'. If symlink caching is + * disabled (i.e. FUSE_CAP_CACHE_SYMLINKS is reset during FUSE_INIT), when= ever + * the ->readlink() is executed to resolve 'link' a counter will be increm= ented. + * + * If symlink caching is enabled (i.e. FUSE_CAP_CACHE_SYMLINKS is set duri= ng + * FUSE_INIT), resolving a symlink will only call into user-space the first + * time. + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +#define FILENAME "file" +#define FILE_INO 42 + +#define LINKNAME "link" +#define LINK_INO 43 + +#define TIMEOUT 86400.0f + +#define SYMLINK_MOUNTPOINT "/tmp/symlink_cache_test_XXXXXX" + +struct test_state { + pthread_mutex_t lock; + bool cache; + int readlink_counter; +} test_state =3D { + .lock =3D PTHREAD_MUTEX_INITIALIZER, +}; + +static void fs_init(void *userdata, struct fuse_conn_info *conn) +{ + pthread_mutex_lock(&test_state.lock); + if (test_state.cache) + fuse_set_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS); + else + fuse_unset_feature_flag(conn, FUSE_CAP_CACHE_SYMLINKS); + pthread_mutex_unlock(&test_state.lock); +} + +static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e =3D {}; + + if (parent !=3D FUSE_ROOT_ID || + (!strcmp(name, FILENAME) && !(strcmp(name, LINKNAME)))) + fuse_reply_err(req, ENOENT); + else { + if (!strcmp(name, FILENAME)) { + e.ino =3D FILE_INO; + e.attr.st_mode =3D S_IFREG | 0444; + e.attr.st_nlink =3D 2; + } else if (!strcmp(name, LINKNAME)) { + e.ino =3D LINK_INO; + e.attr.st_mode =3D S_IFLNK | 0444; + e.attr.st_nlink =3D 1; + e.attr.st_size =3D strlen(FILENAME); + } else { + e.ino =3D FUSE_ROOT_ID; + e.attr.st_mode =3D S_IFDIR | 0755; + e.attr.st_nlink =3D 2; + } + e.attr_timeout =3D TIMEOUT; + e.entry_timeout =3D TIMEOUT; + fuse_reply_entry(req, &e); + } +} + +static void fs_readlink(fuse_req_t req, fuse_ino_t ino) +{ + char buf[PATH_MAX]; + size_t sz =3D strlen(FILENAME); + + if (ino !=3D LINK_INO) { + fuse_reply_err(req, ENOENT); + return; + } + + memcpy(buf, FILENAME, sz); + buf[sz] =3D '\0'; + pthread_mutex_lock(&test_state.lock); + test_state.readlink_counter++; + pthread_mutex_unlock(&test_state.lock); + + fuse_reply_readlink(req, buf); +} + +static const struct fuse_lowlevel_ops symlink_ops =3D { + .init =3D fs_init, + .lookup =3D fs_lookup, + .readlink =3D fs_readlink, +}; + +static void *run_daemon(void *arg) +{ + struct fuse_session *se =3D (struct fuse_session *)arg; + + fuse_session_loop(se); + + return NULL; +} + +FIXTURE(symlink_cache) +{ + struct fuse_session *se; + char mountpoint[PATH_MAX]; + pthread_t thread; +}; +FIXTURE_VARIANT(symlink_cache) +{ + const bool cache; +}; +FIXTURE_VARIANT_ADD(symlink_cache, symlinks_nocache) +{ + /* Variant with symlink cache disabled */ + .cache =3D false, +}; +FIXTURE_VARIANT_ADD(symlink_cache, symlinks_cache) +{ + /* Variant with symlink cache enabled */ + .cache =3D true, +}; + +FIXTURE_SETUP(symlink_cache) +{ + char *fuse_argv[] =3D { "fuse_symlink_cache_test", NULL }; + struct fuse_args args =3D FUSE_ARGS_INIT(1, fuse_argv); + + pthread_mutex_lock(&test_state.lock); + test_state.readlink_counter =3D 0; + test_state.cache =3D variant->cache; + pthread_mutex_unlock(&test_state.lock); + + strcpy(self->mountpoint, SYMLINK_MOUNTPOINT); + if (!mkdtemp(self->mountpoint)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + self->se =3D fuse_session_new(&args, &symlink_ops, + sizeof(symlink_ops), NULL); + if (!self->se) { + rmdir(self->mountpoint); + SKIP(return, "Failed to created FUSE session"); + } + if (fuse_session_mount(self->se, self->mountpoint)) { + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "Failed to mount FUSE session"); + } + if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { + fuse_session_unmount(self->se); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "pthread_create: %s", strerror(errno)); + } + + fuse_opt_free_args(&args); +} + +FIXTURE_TEARDOWN(symlink_cache) +{ + fuse_session_exit(self->se); + fuse_session_unmount(self->se); + pthread_join(self->thread, NULL); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); +} + +TEST_F(symlink_cache, test_symlink_cache) +{ + char pathname[PATH_MAX]; + char buf[PATH_MAX]; + ssize_t sz; + int counter; + int i; + + sprintf(pathname, "%s/%s", self->mountpoint, LINKNAME); + for (i =3D 0; i < 100; i++) { + sz =3D readlink(pathname, buf, PATH_MAX); + ASSERT_NE(sz, -1); + } + pthread_mutex_lock(&test_state.lock); + counter =3D test_state.readlink_counter; + pthread_mutex_unlock(&test_state.lock); + + if (variant->cache) { + ASSERT_EQ(counter, 1); + } else { + ASSERT_EQ(counter, 100); + } +} + +TEST_HARNESS_MAIN From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A2659427FB2; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; cv=none; b=BtUj1M/kiL4B7EAZEh2jBR+Q5REfvZIG6lD4+vkdYZmkfdrex5VhrhwDzBWKo2vkxt0hkEy2SjVbymA20kb5OrdtmoLokM41w3TTzYULy0IHY+SI2172YByyz2G6UX5ZNA9DJ+RqRqTUrcYu9TloDMCJi9t/UhMhP/jRexXPyao= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; c=relaxed/simple; bh=ihSKibfTWpBQSjjXSDVwzIluMaj7qMkuoUclca7rI1M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eG9NgNe5Q4e2prze6Dz0C65zCRUjIQuH+CO/QuCii/Oo3HRwMoiP/JS2wr32r/LnDwp9sFt06z9oW2JiFDEoouYBaCVYI6K7cMJvObtuYeIUNea3BNxb+Us12v5WKBJY7W7gYz4sNQMCSVFBPwgsQQGiosUrnnDSdy0+lab1JPE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=LfhbzVrq; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="LfhbzVrq" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=IgEqs6/CtSLWAT0HRstB1punCeJ+FxZhdHbf0ovicU4=; b= LfhbzVrq9N7aCJ5B85/FJiFaWuc/aAmayMsGyJpOErkSbbwvVIMzsGma8F4K2bj9Z2sDMztercTYZ ZAv3MPJ0BmuCTRTc+UZUvmxz853IgsFiRLezo2kLmU0L5jYcPQggWfl8C8X6O9pcWrOxpVyULl7b1 vK2wjU+7mMjBRyGMHctvGN8c/4AD0sal6Z2E0BpL7cBII5VmU1g3eTasJwoIrq/E2n9Yfp1M9uPfl NmAgjBXn6TqYuLdbv7vWME4jEMFm4YZzFzU+RGxzbwSl7jNyXjLLsSnV97HXqfnxopVMLebdy2uDi kaSEgRnw7QB6GnnSa0MoycNJThzmP+/FMw==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3q-004XyF-1W; Mon, 17 Aug 2026 16:11:34 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 5/8] selftests/fuse: factor-out test fixture setup/teardown Date: Mon, 17 Aug 2026 15:11:53 +0100 Message-ID: <20260817141156.6079-6-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In order to reduce new tests setup/teardown code duplication, factor-out these functions from the existing acl_cache test. Signed-off-by: Luis Henriques --- .../filesystems/fuse/fuse_acl_cache_test.c | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c= b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c index 2411a6e285f1..8bdc90572be2 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -50,6 +50,8 @@ =20 #include "kselftest_harness.h" =20 +#define MAX_ERR_MSG 256 + /* ---- ACL binary encoding ----------------------------------------------= -- */ /* * POSIX ACL v2 xattr format (little-endian): @@ -193,52 +195,74 @@ FIXTURE(acl_cache) { pthread_t thread; }; =20 -FIXTURE_SETUP(acl_cache) +int fs_setup(struct fuse_session **se, char *mountpoint, char *file_path, + pthread_t *thread, char *err) { char *fuse_argv[] =3D { "fuse_acl_cache_test", NULL }; struct fuse_args args =3D FUSE_ARGS_INIT(1, fuse_argv); =20 - g_ds.acl =3D acl_a; - g_ds.acl_size =3D sizeof(acl_a); - g_ds.getxattr_count =3D 0; - - strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX"); - if (!mkdtemp(self->mountpoint)) - SKIP(return, "mkdtemp: %s", strerror(errno)); + strcpy(mountpoint, "/tmp/acl_cache_test_XXXXXX"); + if (!mkdtemp(mountpoint)) { + snprintf(err, MAX_ERR_MSG, "mkdtemp: %s", strerror(errno)); + return -1; + } =20 - snprintf(self->file_path, sizeof(self->file_path), - "%s/" FILE_NAME, self->mountpoint); + snprintf(file_path, PATH_MAX, "%s/" FILE_NAME, mountpoint); =20 - self->se =3D fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL); - if (!self->se) { - rmdir(self->mountpoint); - SKIP(return, "fuse_session_new failed"); + *se =3D fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL); + if (!*se) { + rmdir(mountpoint); + snprintf(err, MAX_ERR_MSG, "fuse_session_new failed"); + return -1; } =20 - if (fuse_session_mount(self->se, self->mountpoint)) { - fuse_session_destroy(self->se); - rmdir(self->mountpoint); - SKIP(return, "fuse_session_mount failed " - "(missing fusermount3 or insufficient privileges)"); + if (fuse_session_mount(*se, mountpoint)) { + fuse_session_destroy(*se); + rmdir(mountpoint); + snprintf(err, MAX_ERR_MSG, "fuse_session_mount failed " + "(missing fusermount3 or insufficient privileges)"); + return -1; } =20 - if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { - fuse_session_unmount(self->se); - fuse_session_destroy(self->se); - rmdir(self->mountpoint); - SKIP(return, "pthread_create: %s", strerror(errno)); + if (pthread_create(thread, NULL, run_daemon, *se)) { + fuse_session_unmount(*se); + fuse_session_destroy(*se); + rmdir(mountpoint); + snprintf(err, MAX_ERR_MSG, "pthread_create: %s", strerror(errno)); + return -1; } =20 fuse_opt_free_args(&args); + + return 0; +} + +static void fs_teardown(struct fuse_session *se, pthread_t thread, + char *mountpoint) +{ + fuse_session_exit(se); + fuse_session_unmount(se); + pthread_join(thread, NULL); + fuse_session_destroy(se); + rmdir(mountpoint); +} + +FIXTURE_SETUP(acl_cache) +{ + char err[MAX_ERR_MSG]; + + g_ds.acl =3D acl_a; + g_ds.acl_size =3D sizeof(acl_a); + g_ds.getxattr_count =3D 0; + + if (fs_setup(&self->se, self->mountpoint, self->file_path, + &self->thread, err)) + SKIP(return, err); } =20 FIXTURE_TEARDOWN(acl_cache) { - fuse_session_exit(self->se); - fuse_session_unmount(self->se); - pthread_join(self->thread, NULL); - fuse_session_destroy(self->se); - rmdir(self->mountpoint); + fs_teardown(self->se, self->thread, self->mountpoint); } =20 static int do_force_statx(const char *path) From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A69EE43CE77; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; cv=none; b=MwoIQdSb6rEoIETdbL/hg0PAiISNBqlnF2rAmg4SWrvgZkeh1n7Sw0Q3LJZzS8IiEM+oJ2lcJle7hwnHaLxNQlTW1jpZHed/o8vUFqt6Q3AjmQI4PfMZDjSt19N+DQP4Kr3eGvwSz2kduJQfxJg2UI6NtSFOlyjFB/jIc4oR2lU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; c=relaxed/simple; bh=M0H3WrcnOolr7jc34Nz78b2q5pmo61h42FZwDQEPCSw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XjTG58YbLGnahqltHPcgQF7+e0qYwROC7LbAUMg8lXQzp5RjsH5jEUReyxIhJg6WgOgVm3ZaP4HaCyj+/kUrZgx7WiZs81vdNeYoXLbGtwqQt6gadd+k+i0n7ts/f5R+L7mG2WU3puKxOwr0SACGzVi8c9Um6py1bGM6L+L7b1E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=A48niJ/e; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="A48niJ/e" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=8prppAdDmMPSt7YwLI9nxGXZpZx3pn5Vs27dXZc7IGo=; b= A48niJ/e50giiTNSGKVvb5lu8I4jUxOzT/ThVAy9S1sBtBiogNUYNIfXHLNz32wMia8Zy8dQmo/V+ aqW5PL7MxtRDLBMDicnoiHi7MerZE78m3bIvqnJxbwd/Zd3WWzyzQfxhqNaZDcAzoA0Y5P0DeNpx2 uGAr5C4uW28kO7hlxN5cb3ajWiz3wN1LMnIAEhLjOP6qQK3HzvUL+wWUFM5Kjm7RcgvY5+QUfH++M IUMsMrbuZZaExNc/CotsG/eQfejMM3YD7ADb5pw0MKZVPTRku5XebxOzlTxd7NpsYHlpJmEN41h8Z hrL7osERiMS5xsxQiqf5rX9kroMvp4NLEg==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3q-004XyK-II; Mon, 17 Aug 2026 16:11:34 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 6/8] selftests/fuse: use dynamically allocated memory to store ACLs Date: Mon, 17 Aug 2026 15:11:54 +0100 Message-ID: <20260817141156.6079-7-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Instead of directly using static arrays for the ACL value, allocate memory for storing it. This will make it easier to implement ACL tests that also set the xattr dynamically. Signed-off-by: Luis Henriques --- .../filesystems/fuse/fuse_acl_cache_test.c | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c= b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c index 8bdc90572be2..bf8b3807e603 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -90,7 +90,7 @@ static const uint8_t acl_b[] =3D { =20 struct daemon_state { pthread_mutex_t lock; - const uint8_t *acl; + uint8_t *acl; size_t acl_size; int getxattr_count; }; @@ -152,15 +152,26 @@ static void fs_getattr(fuse_req_t req, fuse_ino_t ino, static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) { + uint8_t *acl =3D NULL; + size_t acl_size; + if (ino !=3D FILE_INO || strcmp(name, "system.posix_acl_access") !=3D 0) { fuse_reply_err(req, ENODATA); return; } =20 + if (size) { + acl =3D malloc(size); + if (!acl) { + fuse_reply_err(req, ENOMEM); + return; + } + } pthread_mutex_lock(&g_ds.lock); - const uint8_t *acl =3D g_ds.acl; - size_t acl_size =3D g_ds.acl_size; + acl_size =3D g_ds.acl_size; + if (acl && (size >=3D acl_size)) + memcpy(acl, g_ds.acl, acl_size); g_ds.getxattr_count++; pthread_mutex_unlock(&g_ds.lock); =20 @@ -170,6 +181,8 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t ino,= const char *name, fuse_reply_err(req, ERANGE); else fuse_reply_buf(req, (const char *)acl, acl_size); + + free(acl); } =20 static const struct fuse_lowlevel_ops fs_ops =3D { @@ -251,8 +264,10 @@ FIXTURE_SETUP(acl_cache) { char err[MAX_ERR_MSG]; =20 - g_ds.acl =3D acl_a; - g_ds.acl_size =3D sizeof(acl_a); + g_ds.acl_size =3D sizeof(acl_a); + g_ds.acl =3D malloc(g_ds.acl_size); + ASSERT_NE(g_ds.acl, NULL); + memcpy(g_ds.acl, acl_a, g_ds.acl_size); g_ds.getxattr_count =3D 0; =20 if (fs_setup(&self->se, self->mountpoint, self->file_path, @@ -263,6 +278,7 @@ FIXTURE_SETUP(acl_cache) FIXTURE_TEARDOWN(acl_cache) { fs_teardown(self->se, self->thread, self->mountpoint); + free(g_ds.acl); } =20 static int do_force_statx(const char *path) @@ -278,6 +294,7 @@ TEST_F(acl_cache, stale_after_force_sync) char buf[512]; ssize_t sz; int count; + uint8_t *acl; =20 /* * Step 1: two getxattr calls before any statx(FORCE_SYNC). @@ -338,8 +355,13 @@ TEST_F(acl_cache, stale_after_force_sync) * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case). * On a fixed kernel the ACL was never cached, so this is moot. */ + acl =3D malloc(sizeof(acl_b)); + ASSERT_NE(acl, NULL); + memcpy(acl, acl_b, sizeof(acl_b)); + pthread_mutex_lock(&g_ds.lock); - g_ds.acl =3D acl_b; + free(g_ds.acl); + g_ds.acl =3D acl; g_ds.acl_size =3D sizeof(acl_b); pthread_mutex_unlock(&g_ds.lock); TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b)); From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D2BEE43E06B; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975904; cv=none; b=L9rGWMlAxB0SxSVdxVD3mJdWblIdJdF6QZu1qQeOprUXu5VZUwwrYXlUnFB+h5bQQp0RXtf9Dz9bsbsWivJSfRbj36IVRVolqZ3MUFFCw+iiScWn4JGlrm/aaWZdY0cOuAQlmmsLNx6YElR9E2aQ8TJhkFWbloNULms5F8cf0MQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975904; c=relaxed/simple; bh=53FyT5hQuv3wWQE3YDOn9uEae72kYkG1IDbDjue/908=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jq9oI1TzghSCNVWFyuXltq/6L/KjAlZ/COm3cxkhzx1fpPggCbY3MF6H8+1cQC1Hd0XNRuU9mmTgNqKt0MYfBvxk9wKq0KUdugqPOP4t0uZmfxRWBx0tpIF9Sv+bBs0zH4LCAUVxY9HG56/Wpvz8LTJj3gMKZnavJVe4goMvuXE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=TSWlMBKV; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="TSWlMBKV" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=Dsj5g0gTqA2a4Mul6NpOYtRPLWWQfXgRLquExfj9H4k=; b= TSWlMBKVbdunBg9oV43dm9+t/t8HxJFPCicyuebuI+CneeH9X77WzZEvnO0sQ8ng0QOgw6jFxIuAk mQNj6jhfmAH/n8CK1z78sWdirjnNYXo6SNJ2449MDxwwKW5z1cCK6ZtDD+ypL2rjzEpVDnARTaVrR KI/jXq/UWY1BvtgH0W7L1wVwnwgg9wYoFOnpJjocn3tNARiPjGoFTAAsJ+fLCcBcpsQpptvG3dahK dURjxp65oSF2cakIEJv2XQnkb933MgfwhfwoacBXAHVvTcefwdp8EPkJHibbzNuy0FsnfgJ2Vk1iI DH5R/sGbm0Q/BGsN/vEbB0/njyEUN+kzWA==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3r-004Xyc-1p; Mon, 17 Aug 2026 16:11:35 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 7/8] selftests/fuse: add some extra ACL caching tests Date: Mon, 17 Aug 2026 15:11:55 +0100 Message-ID: <20260817141156.6079-8-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds some extra tests to ACL caching: - Verify that reading ACLs results in the expected number of requests being sent user-space, depending on whether cache is enabled or disabled - Verify caching behaviour on some caching invalidation scenarios While there, add test binary to .gitignore. Signed-off-by: Luis Henriques --- .../selftests/filesystems/fuse/.gitignore | 1 + .../filesystems/fuse/fuse_acl_cache_test.c | 177 ++++++++++++++++++ 2 files changed, 178 insertions(+) diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/te= sting/selftests/filesystems/fuse/.gitignore index 873304f8d1a1..d5061752e2b0 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -2,3 +2,4 @@ fuse_mnt fusectl_test fuse_symlink_cache_test +fuse_acl_cache_test diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c= b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c index bf8b3807e603..c1291fb1c8b9 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -93,6 +93,7 @@ struct daemon_state { uint8_t *acl; size_t acl_size; int getxattr_count; + bool cache; }; =20 /* @@ -101,9 +102,17 @@ struct daemon_state { */ static struct daemon_state g_ds =3D { .lock =3D PTHREAD_MUTEX_INITIALIZER, + .cache =3D false, }; =20 /* ---- FUSE lowlevel callbacks ------------------------------------------= -- */ +static void fs_init(void *userdata, struct fuse_conn_info *conn) +{ + pthread_mutex_lock(&g_ds.lock); + if (g_ds.cache) + fuse_set_feature_flag(conn, FUSE_CAP_POSIX_ACL); + pthread_mutex_unlock(&g_ds.lock); +} =20 static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { @@ -125,6 +134,8 @@ static void fs_lookup(fuse_req_t req, fuse_ino_t parent= , const char *name) e.attr.st_ino =3D FILE_INO; e.attr.st_mode =3D S_IFREG | 0644; e.attr.st_nlink =3D 1; + e.attr.st_uid =3D getuid(); + e.attr.st_gid =3D getgid(); fuse_reply_entry(req, &e); } =20 @@ -185,10 +196,38 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t in= o, const char *name, free(acl); } =20 +static void fs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + int ret =3D 0; + uint8_t *acl; + + if (ino !=3D FILE_INO) + ret =3D ENOENT; + else if (!strcmp(name, "system.posix_acl_access")) { + acl =3D malloc(size); + if (acl) { + memcpy(acl, value, size); + pthread_mutex_lock(&g_ds.lock); + if (g_ds.acl) + free(g_ds.acl); + g_ds.acl =3D acl; + g_ds.acl_size =3D size; + pthread_mutex_unlock(&g_ds.lock); + } else + ret =3D ENOMEM; + } else + ret =3D ENOTSUP; + + fuse_reply_err(req, ret); +} + static const struct fuse_lowlevel_ops fs_ops =3D { + .init =3D fs_init, .lookup =3D fs_lookup, .getattr =3D fs_getattr, .getxattr =3D fs_getxattr, + .setxattr =3D fs_setxattr, }; =20 /* ---- Daemon thread ----------------------------------------------------= --- */ @@ -269,6 +308,7 @@ FIXTURE_SETUP(acl_cache) ASSERT_NE(g_ds.acl, NULL); memcpy(g_ds.acl, acl_a, g_ds.acl_size); g_ds.getxattr_count =3D 0; + g_ds.cache =3D false; =20 if (fs_setup(&self->se, self->mountpoint, self->file_path, &self->thread, err)) @@ -390,4 +430,141 @@ TEST_F(acl_cache, stale_after_force_sync) EXPECT_EQ(count, 4); } =20 +FIXTURE(acl_cache_onoff) +{ + struct fuse_session *se; + char mountpoint[PATH_MAX]; + char pathname[PATH_MAX]; + pthread_t thread; +}; + +FIXTURE_VARIANT(acl_cache_onoff) { bool cache; }; +FIXTURE_VARIANT_ADD(acl_cache_onoff, nocache) { .cache =3D false, }; +FIXTURE_VARIANT_ADD(acl_cache_onoff, docache) { .cache =3D true, }; + +FIXTURE_SETUP(acl_cache_onoff) +{ + char err[MAX_ERR_MSG]; + + g_ds.acl =3D NULL; + g_ds.acl_size =3D 0; + g_ds.getxattr_count =3D 0; + g_ds.cache =3D variant->cache; + + if (fs_setup(&self->se, self->mountpoint, self->pathname, + &self->thread, err)) + SKIP(return, err); +} + +FIXTURE_TEARDOWN(acl_cache_onoff) +{ + fs_teardown(self->se, self->thread, self->mountpoint); + free(g_ds.acl); +} + +/* + * This is the most basic ACL caching test: verify that, when reading ACLs= for + * an inode, user-space is called: + * - Only once if ACLs cache is enabled, or + * - Once per access if cache i disabled. + */ +TEST_F(acl_cache_onoff, test_acl_cache_enable_disable) +{ + char buf[512]; + ssize_t sz; + bool cache; + int counter; + int i; + + ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access", + acl_a, sizeof(acl_a), 0), 0); + + for (i =3D 0; i < 100; i++) { + sz =3D lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_a)); + ASSERT_EQ(memcmp(buf, acl_a, sz), 0); + } + + pthread_mutex_lock(&g_ds.lock); + counter =3D g_ds.getxattr_count; + cache =3D g_ds.cache; + pthread_mutex_unlock(&g_ds.lock); + + if (cache) { + ASSERT_EQ(counter, 1); + } else { + ASSERT_EQ(counter, 100); + } + + TH_LOG("User-space called %d time(s) with ACL caching %s", + counter, cache ? "enabled" : "disabled"); +} + +/* + * Test caching invalidation for several scenarios: + * 1. When a new ACL is set + * 2. When invalidating an inode (NOTIFY_INODE_INVAL) + */ +TEST_F(acl_cache_onoff, test_acl_cache_invalidation) +{ + char buf[512]; + ssize_t sz; + int counter; + bool cache; + int i; + + /* Set an ACL */ + ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access", + acl_a, sizeof(acl_a), 0), 0); + + for (i =3D 0; i < 100; i++) { + sz =3D lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_a)); + ASSERT_EQ(memcmp(buf, acl_a, sz), 0); + } + + /* 1. force cache invalidation by setting a new ACL */ + ASSERT_EQ(lsetxattr(self->pathname, "system.posix_acl_access", + acl_b, sizeof(acl_b), 0), 0); + + sz =3D lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_b)); + ASSERT_EQ(memcmp(buf, acl_b, sz), 0); + + pthread_mutex_lock(&g_ds.lock); + counter =3D g_ds.getxattr_count; + cache =3D g_ds.cache; + pthread_mutex_unlock(&g_ds.lock); + + if (cache) { + ASSERT_EQ(counter, 2); + } else { + ASSERT_EQ(counter, 101); + } + TH_LOG("Invalidation by setting new ACL: OK"); + + /* 2. send FUSE_NOTIFY_INVAL_INODE */ + fuse_lowlevel_notify_inval_inode(self->se, FILE_INO, 0, 0); + + sz =3D lgetxattr(self->pathname, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, sizeof(acl_b)); + ASSERT_EQ(memcmp(buf, acl_b, sz), 0); + + pthread_mutex_lock(&g_ds.lock); + counter =3D g_ds.getxattr_count; + cache =3D g_ds.cache; + pthread_mutex_unlock(&g_ds.lock); + + if (cache) { + ASSERT_EQ(counter, 3); + } else { + ASSERT_EQ(counter, 102); + } + TH_LOG("Invalidation through FUSE_NOTIFY_INVAL_INODE: OK"); +} + TEST_HARNESS_MAIN From nobody Mon Sep 28 21:03:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 54958418A39; Mon, 17 Aug 2026 14:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; cv=none; b=sbrbVXP6XDA9/Up9muZe7mgXG8XJsJs32UQiA7M2HP65H8YkhvOPFqwMPp1g6mQcOstVUsDrZnQKhvPN6Xl6cQ6JTOKIVL9KhmZ/jPHRZFog4cCLiJVhmGrlz8WAc9qwM1qln6b5LdVtk8zmLfDyeoMxc81SUrTR+8ivRH7oab4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786975905; c=relaxed/simple; bh=EdkVfWWVIBsnI7+jEPrkxyrlpgsSVb4/vxyDf63d/38=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AbWsyPMbiNHkxrzYUL+/t0DYowZSvyrbbylTvOdhhdlA8UxgnFOFcm9ePmcxpKxC550kuviO9U45ilyGaGhG0xYRH35G3ki3H4LlTF/FzOr2/KlT1yqlmSm4tZQZoSecGauiM/xniCu6xBIP7zL6kX8Hvod7EiAyagSAHb6oFkA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=jteKXTfa; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="jteKXTfa" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=WCMQDNc2nK8EKBHf0iDMqF+QAhCW7epGtgewEhDk/oM=; b= jteKXTfaMS/U8iUzOp0ZHNoqRbmSYKrHprXknao0vnk25uwg39TmFse5inbEIetMl/FpKS/wZwMga ZmoVoqR82q+t+mJW/Dl91rkRW9PQV2JGYgfw26lWYy02h3p8dlrt8lcJYIBNZHWXYG2gZAUab9N7J a4vFE3vDhUTkZr8nsdIpMb41lIxMnELkfctY/0YrI6yPo/8owOTCuta8qfBmouUOQ0Jx3lI8px4Mq 1PX0+eCFyZZJJ44J3ESFr7lNWmkMUNLg4MTgIE5gh18Nl26D0TyZO+g2vQ82k5ayOb2m+sBaZXVkJ BMW3FayjSGJXloICW5rIbSEMbWWvGCPb9w==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1wvy3r-004Xyy-IU; Mon, 17 Aug 2026 16:11:35 +0200 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Chen Linxuan , Jonathan Corbet , Shuah Khan Cc: fuse-devel@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v2 8/8] selftests/fuse: add fuse readdir caching test Date: Mon, 17 Aug 2026 15:11:56 +0100 Message-ID: <20260817141156.6079-9-luis@igalia.com> In-Reply-To: <20260817141156.6079-1-luis@igalia.com> References: <20260817141156.6079-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This new test will check the caching behaviour using combinations of two opendir flags: FOPEN_KEEP_CACHE and FOPEN_CACHE_DIR. Signed-off-by: Luis Henriques --- .../selftests/filesystems/fuse/.gitignore | 1 + .../selftests/filesystems/fuse/Makefile | 6 +- .../fuse/fuse_readdir_cache_test.c | 314 ++++++++++++++++++ 3 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_readdir_c= ache_test.c diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/te= sting/selftests/filesystems/fuse/.gitignore index d5061752e2b0..7c9fc24a0b61 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -3,3 +3,4 @@ fuse_mnt fusectl_test fuse_symlink_cache_test fuse_acl_cache_test +fuse_readdir_cache_test diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index 4091b1cc939e..6edcd25169de 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -2,7 +2,8 @@ =20 CFLAGS +=3D -Wall -O2 -g $(KHDR_INCLUDES) =20 -TEST_GEN_PROGS :=3D fusectl_test fuse_acl_cache_test fuse_symlink_cache_te= st +TEST_GEN_PROGS :=3D fusectl_test fuse_acl_cache_test fuse_symlink_cache_te= st \ + fuse_readdir_cache_test TEST_GEN_FILES :=3D fuse_mnt =20 include ../../lib.mk @@ -25,3 +26,6 @@ $(OUTPUT)/fuse_acl_cache_test: LDLIBS +=3D $(VAR_LDLIBS) =20 $(OUTPUT)/fuse_symlink_cache_test: CFLAGS +=3D $(VAR_CFLAGS) $(OUTPUT)/fuse_symlink_cache_test: LDLIBS +=3D $(VAR_LDLIBS) + +$(OUTPUT)/fuse_readdir_cache_test: CFLAGS +=3D $(VAR_CFLAGS) +$(OUTPUT)/fuse_readdir_cache_test: LDLIBS +=3D $(VAR_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_te= st.c b/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c new file mode 100644 index 000000000000..181c08d9391a --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Simple filesystem to test FUSE readdir cache + * + * It will simply perform readdir operations on a directory checking how m= any + * times a request is sent to user-space using all the possible caching + * combination setting (FOPEN_KEEP_CACHE and FOPEN_CACHE_DIR flags). + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +#define DIRNAME "mydir" +#define FILENAME "myfile" + +#define DIR_INO 42 +#define FILE_INO 43 +#define DOT_INO 40 +#define DOTDOT_INO 41 + +#define TIMEOUT 86400.0f + +#define FS_MOUNTPOINT "/tmp/readdir_cache_test_XXXXXX" + +struct test_state { + pthread_mutex_t lock; + bool cache_readdir; + bool keep_cache; + int readdir_counter; +} test_state =3D { + .lock =3D PTHREAD_MUTEX_INITIALIZER, +}; + +static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e =3D {}; + + if (parent !=3D FUSE_ROOT_ID || strcmp(name, DIRNAME) !=3D 0) + fuse_reply_err(req, ENOENT); + else { + if (!strcmp(name, DIRNAME)) { + e.ino =3D DIR_INO; + e.attr.st_mode =3D S_IFDIR | 0755; + e.attr.st_nlink =3D 1; + } else { + e.ino =3D FUSE_ROOT_ID; + e.attr.st_mode =3D S_IFDIR | 0755; + e.attr.st_nlink =3D 2; + } + e.attr.st_mtime =3D time(NULL); + e.attr_timeout =3D TIMEOUT; + e.entry_timeout =3D TIMEOUT; + fuse_reply_entry(req, &e); + } +} + +static int fill_stat(fuse_ino_t ino, struct stat *st) +{ + int ret =3D 0; + + st->st_ino =3D ino; + st->st_mtime =3D time(NULL); + + switch (ino) { + case FUSE_ROOT_ID: + st->st_mode =3D S_IFDIR | 0755; + st->st_nlink =3D 2; + break; + case DOT_INO: + case DOTDOT_INO: + case DIR_INO: + st->st_mode =3D S_IFDIR | 0755; + st->st_nlink =3D 1; + break; + case FILE_INO: + st->st_mode =3D S_IFREG | 0444; + st->st_nlink =3D 1; + break; + default: + ret =3D -1; + break; + } + + return ret; +} + +static void fs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat st =3D {}; + + if (fill_stat(ino, &st) < 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &st, TIMEOUT); +} + +static void fs_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + pthread_mutex_lock(&test_state.lock); + fi->keep_cache =3D test_state.keep_cache; + fi->cache_readdir =3D test_state.cache_readdir; + pthread_mutex_unlock(&test_state.lock); + fuse_reply_open(req, fi); +} + +static void fs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + struct stat st =3D {}; + char buf[1024]; + char *pbuf; + size_t rem =3D size; + size_t sz; + int nextoff =3D 0; + + if (ino !=3D DIR_INO) { + fuse_reply_err(req, ENOTDIR); + return; + } + if (offset) { + fuse_reply_buf(req, NULL, 0); + return; + } + pbuf =3D buf; + fill_stat(DOT_INO, &st); + sz =3D fuse_add_direntry(req, pbuf, rem, ".", &st, nextoff++); + rem -=3D sz; + pbuf +=3D sz; + fill_stat(DOTDOT_INO, &st); + sz =3D fuse_add_direntry(req, pbuf, rem, "..", &st, nextoff++); + rem -=3D sz; + pbuf +=3D sz; + fill_stat(FILE_INO, &st); + sz =3D fuse_add_direntry(req, pbuf, rem, FILENAME, &st, nextoff++); + rem -=3D sz; + + fuse_reply_buf(req, buf, size - rem); + + pthread_mutex_lock(&test_state.lock); + test_state.readdir_counter++; + pthread_mutex_unlock(&test_state.lock); +} + +static const struct fuse_lowlevel_ops fs_ops =3D { + .lookup =3D fs_lookup, + .getattr =3D fs_getattr, + .opendir =3D fs_opendir, + .readdir =3D fs_readdir, +}; + +static void *run_daemon(void *arg) +{ + struct fuse_session *se =3D (struct fuse_session *)arg; + + fuse_session_loop(se); + + return NULL; +} + +FIXTURE(readdir_cache) +{ + struct fuse_session *se; + char mountpoint[PATH_MAX]; + pthread_t thread; +}; + +FIXTURE_VARIANT(readdir_cache) +{ + bool cache_readdir; + bool keep_cache; +}; +FIXTURE_VARIANT_ADD(readdir_cache, nocache) +{ + .cache_readdir =3D false, + .keep_cache =3D false, +}; +FIXTURE_VARIANT_ADD(readdir_cache, cache_readdir) +{ + .cache_readdir =3D true, + .keep_cache =3D false, +}; +FIXTURE_VARIANT_ADD(readdir_cache, keep_cache) +{ + .cache_readdir =3D false, + .keep_cache =3D true, +}; +FIXTURE_VARIANT_ADD(readdir_cache, cache) +{ + .cache_readdir =3D true, + .keep_cache =3D true, +}; + +FIXTURE_SETUP(readdir_cache) +{ + char *fuse_argv[] =3D { "fuse_readdir_cache_test", NULL }; + struct fuse_args args =3D FUSE_ARGS_INIT(1, fuse_argv); + + pthread_mutex_lock(&test_state.lock); + test_state.readdir_counter =3D 0; + test_state.cache_readdir =3D variant->cache_readdir; + test_state.keep_cache =3D variant->keep_cache; + pthread_mutex_unlock(&test_state.lock); + + strcpy(self->mountpoint, FS_MOUNTPOINT); + if (!mkdtemp(self->mountpoint)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + self->se =3D fuse_session_new(&args, &fs_ops, + sizeof(fs_ops), NULL); + if (!self->se) { + rmdir(self->mountpoint); + SKIP(return, "Failed to created FUSE session"); + } + if (fuse_session_mount(self->se, self->mountpoint)) { + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "Failed to mount FUSE session"); + } + if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { + fuse_session_unmount(self->se); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "pthread_create: %s", strerror(errno)); + } + + fuse_opt_free_args(&args); +} + +FIXTURE_TEARDOWN(readdir_cache) +{ + fuse_session_exit(self->se); + fuse_session_unmount(self->se); + pthread_join(self->thread, NULL); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); +} + +TEST_F(readdir_cache, test_readdir_cache) +{ + struct dirent *dentry; + DIR *dir; + char pathname[PATH_MAX]; + int total_counter, rewind_counter; + int dentrycount; + + sprintf(pathname, "%s/%s", self->mountpoint, DIRNAME); + + dir =3D opendir(pathname); + if (dir =3D=3D NULL) + TH_LOG("opendir(): %s", strerror(errno)); + ASSERT_NE(dir, NULL); + + errno =3D 0; + dentrycount =3D 0; + while ((dentry =3D readdir(dir))) + dentrycount++; + ASSERT_EQ(errno, 0); + ASSERT_EQ(dentrycount, 3); + + rewinddir(dir); + errno =3D 0; + dentrycount =3D 0; + while ((dentry =3D readdir(dir))) + dentrycount++; + ASSERT_EQ(errno, 0); + ASSERT_EQ(dentrycount, 3); + + ASSERT_EQ(closedir(dir), 0); + + pthread_mutex_lock(&test_state.lock); + rewind_counter =3D test_state.readdir_counter; + pthread_mutex_unlock(&test_state.lock); + + dir =3D opendir(pathname); + if (dir =3D=3D NULL) + TH_LOG("opendir(): %s", strerror(errno)); + ASSERT_NE(dir, NULL); + + errno =3D 0; + dentrycount =3D 0; + while ((dentry =3D readdir(dir))) + dentrycount++; + ASSERT_EQ(errno, 0); + ASSERT_EQ(dentrycount, 3); + + ASSERT_EQ(closedir(dir), 0); + + pthread_mutex_lock(&test_state.lock); + total_counter =3D test_state.readdir_counter; + pthread_mutex_unlock(&test_state.lock); + + if (!variant->cache_readdir) { + ASSERT_EQ(rewind_counter, 2); + ASSERT_EQ(total_counter, 3); + } else if (!variant->keep_cache) { + ASSERT_EQ(rewind_counter, 1); + ASSERT_EQ(total_counter, 2); + } else { + ASSERT_EQ(rewind_counter, 1); + ASSERT_EQ(total_counter, 1); + } +} + +TEST_HARNESS_MAIN