From nobody Sun Sep 27 06:24:53 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 5200F46C4CD; Fri, 4 Sep 2026 10:38:54 +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=1788518338; cv=none; b=j6IdTs2sCt8bUFNWy9ovfi4zf2IhcnlsEPQhs8h3IBxAsLOwBjBA7SkywvWxO/hFRkZJZUiGz0v6vrs2R/xbHSFjcvRGPv5z+sQtyZ65QSiHUwK9AuekJiQ5UsRl4+dJZ6rI1LWMIlw/b39STWzr19abJHhLPrdDShvNevUg7W0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518338; c=relaxed/simple; bh=SyR2SgiiKaDf8DwjVf//zwy1H59Q/wnToVU222J1pVc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=u0pzn1pKghJiswv6ziegs2H08ZGbu1cRKaGAHmu4NwtGel970UTncS2qQhzo/iEIk8aKuqwKFruiCOTZMuvbPmlVAl//+/vAx+sInNyzD5qcw/ydjlEMFEWrjT9qQzvK0/1XKUnpnysDYqCfvws5H0qc1g3O7U266UZbzuO1Y3A= 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=akFQ41tg; 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="akFQ41tg" 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=gVTEyq0Vf66zV88lZcZuSzCziAx/EaV2pyuTvSCeSPY=; b= akFQ41tgCgHu1DHeg4zXcgDcObmzbLzGZoTU2jip9nYOqlohS0KqMCvu10CJF8BoRQl3/KETwSBUZ SiYUJyB0aAbgcGHQLXTNQlM2Vk62vve8fEMajNam+GPKfCihtI2Q/475QtXaCHg0ODXMeFuAiDHHO wLe2x2yIaRXOJBRMZnbqxbbXbUfJSuMyMJ5y2g1pWHd4C37TFxRry9h4vZEYPPxh6+GAZsAe8g8Ku uJx/rWQuBUtsWVqiD7es5sIqyYmEyemuxjzEriEOm8FDBASrsDuSTdRhhxK4DzT2Xfk6b4Lqe8/Ov 271BH9y2GM+LSGSANOX3wtftZdhWJEkOuw==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJk-00Eq0k-RH; Fri, 04 Sep 2026 12:38:44 +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 v3 1/8] Documentation: fuse: add document on caches being used by FUSE Date: Fri, 4 Sep 2026 11:39:13 +0100 Message-ID: <20260904103920.4471-2-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 | 158 ++++++++++++++++++ 1 file changed, 158 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..e9f60355d7ff --- /dev/null +++ b/Documentation/filesystems/fuse/fuse-caches.rst @@ -0,0 +1,158 @@ +.. 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 used in FUSE. For e= ach +cache type, it documents the rules to insert data into it. It also documen= ts the +rules for validating and invalidating data in 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 for a FUSE filesystem, the VFS = will +call into ``fuse_get_link()``, the ``->get_link()`` inode operation. This +function will then send a ``FUSE_READLINK`` request to the user-space FUSE +server. + +The server can ask the kernel to cache all link resolutions by setting the +``FUSE_CACHE_SYMLINKS`` flag during the ``FUSE_INIT`` negotiation. If this= flag +is set, when the VFS calls into the ``->get_link()`` operation, FUSE will +immediately call ``__page_get_link()``. The first time this is done for a +specific inode, it will result in sending the ``FUSE_READLINK`` request to +user-space. But the result returned from this request will then be added i= nto +the page-cache. The next time this link needs to be resolved, it will use = the +link resolution already cached, and will only fallback to user-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 + +Inode attributes may be obtained from user-space by different FUSE operati= ons. +For example, ``FUSE_LOOKUP``, ``FUSE_GETATTR``, and also several other +operations that create file system objects (e.g. ``FUSE_MKDIR``). These +attributes obtained from user-space are cached by the kernel. They have, +however, a timeout associated and once it expires, they are invalidated. T= he +next time the attributes are needed, a request (``FUSE_GETATTR``) will be = sent +to the FUSE server. + +The ``FUSE_GETATTR`` request can be sent to user-space in three different +scenarios: + +#. if the attributes for the inode aren't yet available in the kernel; +#. if they are not valid any more (timed-out, or have been invalidated), or +#. if there is an explicit request for forcing the request to be sent (for + example, by using the ``AT_STATX_FORCE_SYNC`` flag in ``statx``). + +Regarding the attributes invalidation, they may happen in several occasion= s: + +- Upon user-space request for invalidation: + + - A ``FUSE_NOTIFY_INVAL_INODE`` will invalidate ``STATX_BASIC_STATS``; + - ``FUSE_NOTIFY_DELETE`` and ``FUSE_NOTIFY_INVAL_ENTRY`` invalidate + ``FUSE_STATX_MODDIR``. + +- When setting (or removing) an ACL on an inode ``STATX_CTIME`` is invalid= ated; + if the ``FUSE_POSIX_ACL`` flag was set by the FUSE server, + ``STATX_BASIC_STATS`` will also be invalidated. +- On a ``->rename()`` operation, both the old and the new entities will ha= ve + it's ctime invalidated (``STATX_CTIME``). Also, the directories for both= the + old and the new entities will also have their attributes invalidated + (``FUSE_STATX_MODDIR``) +- When creating or deleting a new file system object (``->link()/->unlink(= )``, + ``->symlink()``, ``->mkdir()/->rmdir()``, ``->tmpfile()``, or + ``->atomic_open()``), the directory where the object is created/deleted = will + have it's attributes invalidated (``FUSE_STATX_MODDIR``). +- If a ``->link()`` operation is interrupted by a signal (``EINTR``) the i= node + being linked will have it's attributes invalidated (``STATX_BASIC_STATS`= `). +- When doing a readdir (``->iterate_shared()`` operation) and the directory + contents is not cached, ``STATX_ATIME`` attributes will be invalidated. +- When doing a symlink resolution (by sending a ``FUSE_READLINK`` request) + ``STATX_ATIME`` will be invalidated. +- When doing a ``->flush()`` (i.e. sending a ``FUSE_FLUSH`` request) and + writeback cache is enabled, ``STATX_BLOCKS`` will be invalidated. +- When truncating a file on open using ``O_TRUNC`` open flag (and the FUSE + server has set ``FUSE_ATOMIC_O_TRUNC`` during ``FUSE_INIT``), then + ``FUSE_STATX_MODSIZE`` will be invalidated. +- When setting attributes in an inode (``->setattr()``) and there's a sign= al + that interrupts the operation (``EINTR``), then ``STATX_BASIC_STATS`` wi= ll be + invalidated. +- When data is read from a file, ``STATX_ATIME`` will be invalidated (unle= ss the + file system is read-only). +- When data is written info a file, ``FUSE_STATX_MODSIZE`` is invalidated. + +ACL caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +FUSE has allowed the usage of POSIX Access Control Lists (ACLs) for a long= time, +as they can be set and accessed simply as extended attributes. However, it= was +only with the introduction of the ``FUSE_POSIX_ACL`` flag that ACLs starte= d to +be fully supported. Without this flag being set during the ``FUSE_INIT`` +negotiation, ACLs can still be set, but the VFS won't use them for perform= ing +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 the ``FUSE_POSIX_ACL`` flag is set then, when an ino= de ACL +is accessed, VFS will first check if it's already cached. If it is not, FU= SE +``->get_acl()`` operation (``fuse_get_acl()``) is called, which will event= ually +send a user-space request. Future accesses to this inode ACL will use the = cached +data. + +Setting an ACL in an inode will also result in sending a request to the FU= SE +server for setting it. But this operation won't immediately cache the ACL = -- it +will only be cached after it is accessed again and requested from user-spa= ce. + +On the other hand, ACLs will be removed from the cache in the following +situations: + +- When setting an ACL in an inode (and the ``FUSE_POSIX_ACL`` flag is set), + previously cached ACLs for this inode will be invalidated. +- When invalidating an inode through the ``FUSE_NOTIFY_INVAL_INODE`` opera= tion. +- 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 happen= s, any + cached ACLs for that inode will be invalidated. +- After setting an inode attribute (i.e. operation ``FUSE_SETATTR`` is sen= t to + user-space), the user-space server may have also updated the ACLs. Thus,= any + cached ACLs for this inode are also invalidated. +- While processing ``FUSE_READDIRPLUS`` and an already existing dentry nee= ds to + be updated. +- In general, when there is the need to send a ``FUSE_STATX`` or + ``FUSE_GETATTR`` to user-space (e.g. when attributes expired). + +readdir caching +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +When opening a directory a ``FUSE_OPENDIR`` will be sent to the FUSE serve= r, and +server will be responsible for setting the open flags related 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 for the current open. If +``FOPEN_KEEP_CACHE`` is also set, the cache will be kept and reused in the +future, when the directory is open again for reading. + +The readdir cache will also expire and reset if the inode's ``mtime`` or +``iversion`` don't match the cached values, or if the FUSE connection ``ep= och`` +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 Sun Sep 27 06:24:53 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 51F3146AF2C; Fri, 4 Sep 2026 10:38:54 +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=1788518337; cv=none; b=P4nAJUoRYTuHliUy7A83RdXnMQedEaB/jPt5ZFfPN8Q/dsPvtXxZKGTnW2WToIsc3QsAwwLOwATdL2FOGRarPu/zAooLgPQfhqIKbPMmdk1mCJWzWWs+AvTu1ft7/AbG55eTRvPIo93v/NRNKM1HhSOftFHRxo6iMBkuS5HX++8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518337; c=relaxed/simple; bh=+HuJRv9MxBWoIl1CqVAhOCJ8Nb9U+afeGbiwDFQ1MhE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PGVJFQJMp63VvtwGR8+m5DuUXsqXLqOUGvGyXP9ctQQj4Cf1wwtDkMGs+ulGsr4YdsU328US10d1x6HMi/dN2rs0U4dTPdvVqWR/Jr3BsaZBHDK4ZWxi1xBFDNSXZNre12qtXMQZ0rF+z2giHc/brfEEn9HKyVPwkssbURO/bpU= 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=YmhHk22J; 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="YmhHk22J" 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=f/JuPB5ODbW76qoUQlyBQI5XfcE9nTaSz3hiDI+DXao=; b= YmhHk22JBrUbGDHpYRNbBL0jBnVM0MOr95Bu0oe7NIGMZZWZlGiDw+zFiAO2IVmOA1TlYVF4M8RBY 8O/tSk6N75BnNKNGlyHNIkqWu00sPR6YDTnkDen26wkpmRqetBSxxM0Gj2HhdUt5u/NMVn4RWnpV6 iCYMOMnk9nJwzHZk6npKQasXCHsLPVJl04lj2J8EBrP7jRF/ZRYVuIKpd6GBdhb7t2GyhaI8I0J8O xK9lU94i32GcArR+Q7mMG/9i2+waGmdgRlWe1JKWcdGv2vPMB38zGfb66r9/PPf9rYM8P3KYXuFH0 O8TTshyX8nFtbuBkCsRHusUFzmFcdp6KqA==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJl-00Eq0n-A4; Fri, 04 Sep 2026 12:38:45 +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 v3 2/8] selftests/fuse: convert fusectl test to fuse3 Date: Fri, 4 Sep 2026 11:39:14 +0100 Message-ID: <20260904103920.4471-3-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 | 20 +++++++------------ .../selftests/filesystems/fuse/fuse_mnt.c | 17 +++++++++------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index 95a1ee947ca7..a3ee9b3a2f5d 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -4,31 +4,25 @@ CFLAGS +=3D -Wall -O2 -g $(KHDR_INCLUDES) =20 TEST_GEN_PROGS :=3D fusectl_test TEST_GEN_PROGS +=3D write_extend_eof_test -TEST_GEN_FILES :=3D fuse_mnt - -# 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 +TEST_GEN_FILES :=3D fuse_mnt =20 include ../../lib.mk =20 $(OUTPUT)/write_extend_eof_test: LDLIBS +=3D -lpthread =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 Sun Sep 27 06:24:53 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 5E0BB46D579; Fri, 4 Sep 2026 10:38:56 +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=1788518339; cv=none; b=fIQsNGXxqIK2OYctPsXqTVLPd9hWAO64AKis4gmOrOA6ZEFpEmjJNF7q//JlQ7ZTWcBZ4HL+6205nRCGWc9IdsRlFlUVWuYhvF7YjQ0M/7okwRHcxE5jqTb27BgfY4x6gcFYigYH5+QmRgN5/TcqL6J2N34wYQvLHt3/vK0mZn8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518339; c=relaxed/simple; bh=QHq06vIBI/zcCiYamSeW0cj0CU94kxJhrIBliQgxEL0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Fv/0rUCKv+WCO6jGigX0UX82qoQHpbjgyrYG4LI20jdD4AZ5lIIBOtWEwG2nvV/qL4xGxwE6XPu3/mO5hsBSL5iGY2GziJbaLH1rh0GN9KZl5qJusfSimcDBKqgZClRXqb4e7mk1lszt8gO2K9cf2kuLt2D94XllkH6P/ZxSoec= 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=JB09mUd+; 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="JB09mUd+" 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=i/McfkNPYafi7JUieLT42Z73GlZoO7bNl/Ko8hzAWGI=; b= JB09mUd+VtNh1OIyZGZT8PoRnbLb5Bm+w2KAIKsojPCfalJPNF4Ji1tbAZE8Rhm3SVYQK0QLM5swd bRkIQSpAeUZfpvLBexZxGiwNhofQB2Mkw+ilh8yfadWM5BQ4aKXj4Tt363eB3Tt9CHb31bZokqmCZ bO2ZQr8NOzSaCwDdCxf6QMD7Mf8bFgDXdV+V3LTizPA7CGfB/2f9cVI1HUty9Guu7xI5YQb0LKXu1 8cuQQoBgHAHGVayotn1/f5xHDoTH3uN7OHpCoaVl5mMK94HjygtV5aDtkm3nPAP/NDtgkLQUwIFV5 vSeI3mf3iB7dEv3lvNh6tVOXHxFkj3YbEA==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJl-00Eq0p-PM; Fri, 04 Sep 2026 12:38:45 +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 v3 3/8] selftests/fuse: check that fusectlfs is mounted Date: Fri, 4 Sep 2026 11:39:15 +0100 Message-ID: <20260904103920.4471-4-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/filesystems/fuse/fusectl_test.c b/tool= s/testing/selftests/filesystems/fuse/fusectl_test.c index 0d1d012c35ed..b828173c01bd 100644 --- a/tools/testing/selftests/filesystems/fuse/fusectl_test.c +++ b/tools/testing/selftests/filesystems/fuse/fusectl_test.c @@ -106,8 +106,15 @@ TEST_F(fusectl, abort) char path_buf[PATH_MAX]; int abort_fd, test_fd, ret; =20 - sprintf(path_buf, "/sys/fs/fuse/connections/%d/abort", self->connection); + snprintf(path_buf, PATH_MAX, "%s/%d", FUSECTL_MOUNTPOINT, + self->connection); + if (access(path_buf, F_OK) !=3D 0) + SKIP(return, + "fusectl doesn't seem to be mounted: %s", + strerror(errno)); =20 + snprintf(path_buf, PATH_MAX, "%s/%d/abort", FUSECTL_MOUNTPOINT, + self->connection); ASSERT_EQ(0, access(path_buf, F_OK)); =20 abort_fd =3D open(path_buf, O_WRONLY); From nobody Sun Sep 27 06:24:53 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 42602380FD8; Fri, 4 Sep 2026 10:38:57 +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=1788518340; cv=none; b=IgBRv/htDm5sLC/u5WEQrnYTmm+b0SupjBZ/Y1oDeJfIFKaf0GqlWMODXmt3uRshBnOHjYYIei2B3V7LTYNBHH/TyYg6TzgoaIpfMw1QBlLFzMvHbRQwybQdsLt6jOmmkbFOn/qHRA/+juq6F4w6ee3esWVOYOHTIju2+4ewlHc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518340; c=relaxed/simple; bh=nw6mIujBeClYX4dT4OPexYZ0VjOJlDPqMQqYdnyoGTI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=iUHPtjD7njGTubzHEjN2UnxSPqhuuBxS31TT5haHtUFcZgC33F5AMTfxK2xTN9PegRvnHdb3rmgK0CJVU1T30BEdZS/3JwD26f+vE44rrgkPZ6BupwI4IYmWaek7GRFcLLoqlG8fBI2YTkmBZ/xn3Z+i/CI6uY/cV8svq4X3spE= 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=OhuFcunD; 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="OhuFcunD" 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=ncYYqHSPATM3shxVDXYtrkuLd4kiP7qvMgwaSP7QFm4=; b= OhuFcunD+oXebSA1JB2inZpPU/hZoYXc0caZcwSSo/M04PBXGT8X89J1AzIkJxQislZv2so7LGZrd TJgAkadImqTXbgZxg1tCEnPPbpvdqfnOTVfOEK/c5yl4/LXMmVU/g0ZC2Aot+U0zvR4jVqEPSSrpT Ey3kQmgsYFydqsw+57VJrKmYiMuDaB0aBlBNFzD3L9XfnDlJIOg8JlUbFt2MHemsvtE0+b9RrF9n3 w40enhvyVa7eQ4g73Y+EEhaG3B0yg74uLlHI68ZWnHIKg+7mrLiW63bHCBw3N70kTCOL7EKVW5yxj E1NL+Gm5q5ygvbI/fgU7hPH6nF3QxPwOFg==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJm-00Eq0t-8J; Fri, 04 Sep 2026 12:38:46 +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 v3 4/8] selftests/fuse: factor-out test fixture setup/teardown Date: Fri, 4 Sep 2026 11:39:16 +0100 Message-ID: <20260904103920.4471-5-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 into a new fuse_common.c file that can be reused in other tests. Signed-off-by: Luis Henriques --- .../selftests/filesystems/fuse/Makefile | 8 ++- .../filesystems/fuse/fuse_acl_cache_test.c | 62 +++---------------- .../selftests/filesystems/fuse/fuse_common.c | 60 ++++++++++++++++++ .../selftests/filesystems/fuse/fuse_common.h | 25 ++++++++ 4 files changed, 100 insertions(+), 55 deletions(-) create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_common.c create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_common.h diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index a3ee9b3a2f5d..7744f796eb06 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -21,8 +21,12 @@ ifeq ($(VAR_LDLIBS),) VAR_LDLIBS :=3D -lfuse3 -pthread endif =20 +CFLAGS +=3D $(VAR_CFLAGS) +LDLIBS +=3D $(VAR_LDLIBS) + $(OUTPUT)/fuse_mnt: CFLAGS +=3D $(VAR_CFLAGS) $(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_acl_cache_test: fuse_common.c fuse_acl_cache_test.c + +EXTRA_CLEAN :=3D fuse_common.o 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..12cbf9753d03 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -33,23 +33,15 @@ */ =20 #define _GNU_SOURCE -#include #include #include -#include #include -#include -#include -#include -#include #include -#include - -#define FUSE_USE_VERSION 31 -#include =20 #include "kselftest_harness.h" =20 +#include "fuse_common.h" + /* ---- ACL binary encoding ----------------------------------------------= -- */ /* * POSIX ACL v2 xattr format (little-endian): @@ -176,69 +168,33 @@ static const struct fuse_lowlevel_ops fs_ops =3D { .getxattr =3D fs_getxattr, }; =20 -/* ---- Daemon thread ----------------------------------------------------= --- */ - -static void *run_daemon(void *arg) -{ - fuse_session_loop((struct fuse_session *)arg); - return NULL; -} - /* ---- kselftest harness ------------------------------------------------= --- */ =20 FIXTURE(acl_cache) { struct fuse_session *se; - char mountpoint[PATH_MAX]; + char mountpoint[MOUNTPOINT_SZ]; char file_path[PATH_MAX]; pthread_t thread; }; =20 FIXTURE_SETUP(acl_cache) { - char *fuse_argv[] =3D { "fuse_acl_cache_test", NULL }; - struct fuse_args args =3D FUSE_ARGS_INIT(1, fuse_argv); + char err[MAX_ERR_MSG]; =20 - g_ds.acl =3D acl_a; - g_ds.acl_size =3D sizeof(acl_a); + g_ds.acl =3D acl_a; + g_ds.acl_size =3D sizeof(acl_a); g_ds.getxattr_count =3D 0; =20 - strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX"); - if (!mkdtemp(self->mountpoint)) - SKIP(return, "mkdtemp: %s", strerror(errno)); + if (fs_setup(&self->se, self->mountpoint, &fs_ops, &self->thread, err)) + SKIP(return, err); =20 snprintf(self->file_path, sizeof(self->file_path), "%s/" FILE_NAME, self->mountpoint); - - 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"); - } - - 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 (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); } =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) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_common.c b/tools= /testing/selftests/filesystems/fuse/fuse_common.c new file mode 100644 index 000000000000..3a91cac25b81 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_common.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "fuse_common.h" + +static void *run_daemon(void *arg) +{ + fuse_session_loop((struct fuse_session *)arg); + return NULL; +} + +int fs_setup(struct fuse_session **se, char *mountpoint, + const struct fuse_lowlevel_ops *fs_ops, + pthread_t *thread, char *err) +{ + char *fuse_argv[] =3D { "fuse_test", NULL }; + struct fuse_args args =3D FUSE_ARGS_INIT(1, fuse_argv); + + strcpy(mountpoint, MOUNTPOINT_TEMPLATE); + if (!mkdtemp(mountpoint)) { + snprintf(err, MAX_ERR_MSG, "mkdtemp: %s", strerror(errno)); + return -1; + } + + *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; + } + + 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; + } + + 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; + } + + fuse_opt_free_args(&args); + + return 0; +} + +void fs_teardown(struct fuse_session *se, pthread_t thread, char *mountpoi= nt) +{ + fuse_session_exit(se); + fuse_session_unmount(se); + pthread_join(thread, NULL); + fuse_session_destroy(se); + rmdir(mountpoint); +} + diff --git a/tools/testing/selftests/filesystems/fuse/fuse_common.h b/tools= /testing/selftests/filesystems/fuse/fuse_common.h new file mode 100644 index 000000000000..77d5eb58550d --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_common.h @@ -0,0 +1,25 @@ +#ifndef __SELFTEST_FUSE_COMMON_H__ +#define __SELFTEST_FUSE_COMMON_H__ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#define FUSE_USE_VERSION 31 +#include + +#define MAX_ERR_MSG 256 + +#define MOUNTPOINT_TEMPLATE "/tmp/fuse_test_XXXXXX" +#define MOUNTPOINT_SZ 64 + +int fs_setup(struct fuse_session **se, char *mountpoint, + const struct fuse_lowlevel_ops *fs_ops, + pthread_t *thread, char *err); +void fs_teardown(struct fuse_session *se, pthread_t thread, char *mountpoi= nt); + +#endif /* __SELFTEST_FUSE_COMMON_H__ */ From nobody Sun Sep 27 06:24:53 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 56BD946C4D0; Fri, 4 Sep 2026 10:38:56 +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=1788518338; cv=none; b=WXUwpyn+ShdQU8gOjNFIZq3YEZ8PLlh9fO58xQMDHlqp8LoCeXo5dHVdxxZpx9oGw+Lk6LFxUrWUugYZDAZ7wAO4BfRtvxfybnCKpHznIo6rLQK+tqEOUUJrXUsBfjnY6poy6woevLosqpQzPjo9Sz9IlGmZ4tS+5QcFhxNApH0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518338; c=relaxed/simple; bh=p9g1PUouT6Erw84QHlBFOLCTCegUKxVucfE3SQgpLT0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gbf0zNN6Z/ORGYMOj/eyXjT35zJp6cALjOrYq6in49cgoed/pWkOhCV7hXlbfE3rGzojknNsKBVscCxg7G/wLW700+WS49UKvshHCEImYJpu7DLcBhrklWCnbzqGwGvtPGhNeGM8bv22jDXvWKvkPKI7Be91QvhXiwWqNmdPnpQ= 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=CBHlhqqu; 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="CBHlhqqu" 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=EOI1gXGQqauK+VMxJAvurkqReNkZikt+yZ4YgoEKb80=; b= CBHlhqquF+Nk7ixCS4IFHzcrn0Rx9IZGRSk1w08+SWg/lCbFB1V/+JCJM4xR4EBoS9QJUpZAcB33I HZQFWrd+fhw49COf5UZIXNrylRt0q6ihjcNnskoJbBfza4/18CCjFbaDfilU7toe5cQ7faPB2i2XB lxw2Du1eW3o6Un6ycXNnGjSWRR75RRMG1GryO6CoWUGBpOX9GDIStYCEDooKMLqLyd22qdwZi9o1a ivDK4IihJ2cGsnsSvUI360SSxWT4BuVLBnHm8aFpRCPtCAgpvLfs20zzhYEOJOf3gMGjVD7N+Oh+W vqhUUPU0oaT5fU+etS1K5sLipC21P1etcw==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJm-00Eq0v-OW; Fri, 04 Sep 2026 12:38:46 +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 v3 5/8] selftests/fuse: use dynamically allocated memory to store ACLs Date: Fri, 4 Sep 2026 11:39:17 +0100 Message-ID: <20260904103920.4471-6-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 Reviewed-by: Amir Goldstein --- .../filesystems/fuse/fuse_acl_cache_test.c | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 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 12cbf9753d03..c2d6658ff7de 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -80,7 +80,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; }; @@ -142,15 +142,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 @@ -160,6 +171,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 { @@ -181,8 +194,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 =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, &fs_ops, &self->thread, err)) @@ -195,6 +210,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) @@ -210,6 +226,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). @@ -270,8 +287,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 Sun Sep 27 06:24:53 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 5DEF346D2BF; Fri, 4 Sep 2026 10:38:56 +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=1788518339; cv=none; b=bTcaMpjddMylkOa7x0+beaHNxm2V49p3D9y4TBIcnQAbrfQU8F2SoqQ2D8YhT8Yg/oaoO05XzhkxlcnaGvbVNE8y0JW84Y+iQEcIClJfBeYXuCGvkaNP2gkxzNSH8ibxW8zbT2cwmyyL7ciaRMuCpO2Fw9wcoPRaX5zzw+zu4kw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518339; c=relaxed/simple; bh=sYIHcPCrYS4KyEQfcFyUdFQhlq70gQCjq5hSX/4UNno=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=udk7kMCzOlrKXv06MNUk0EdoKPeYUeC+CQTnH7+sxk1//EFwF3Vig/rVy2webh1Bn1yFf4Vzt4zxInbW2jdjT5yvAInJCMDZVFObEUp07G9PVYr1ZJApqFESU3gSUKF2NUcTSFNC16PxG8IeZ9M7F2pyVcj9RvMiHo/eOE6kZAc= 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=WEXo5fy8; 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="WEXo5fy8" 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=8WOnr4RN6rzP95WWHSW4bXDIpnOb0dKKHyJEBh7/hHU=; b= WEXo5fy8clMP8LdbwyVXmUVTI4VAIAfPitsKG9htiL700CknUFqmyYIUMUTfYLMFM3bGO6xn2/gKY 1BCy0C8cJb+tTaAO6cBqf50ut5gLpDOnbVbzEFcKBBEdEEgIdoizVPwQK6cN/ZgTbvdtplJCYPXb9 JSF3RPmq9iNxGEfHK69eYjM0Bon3OYvLue44xpUZqLsxiUiL/9tdC6whlB9RTVXqpNziGv8oCU/3t 05LZaPz4iB3v+r2PU21aM4BxgeGqcKmqh15IsTy+wQnrJw+yzcf/ov7wc/NY5fz6cBtVROcBpqc+c Dw6sUA5pDHHZIvdOgMHbPignaORyhWMOeA==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJn-00Eq0y-7y; Fri, 04 Sep 2026 12:38:47 +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 v3 6/8] selftests/fuse: add some extra ACL caching tests Date: Fri, 4 Sep 2026 11:39:18 +0100 Message-ID: <20260904103920.4471-7-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 | 179 ++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/te= sting/selftests/filesystems/fuse/.gitignore index fb51603fe419..f7f3dd345a50 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -2,3 +2,4 @@ fuse_mnt fusectl_test write_extend_eof_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 c2d6658ff7de..9608a0adb967 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -83,6 +83,7 @@ struct daemon_state { uint8_t *acl; size_t acl_size; int getxattr_count; + bool cache; }; =20 /* @@ -91,9 +92,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) { @@ -115,6 +124,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 @@ -175,10 +186,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 /* ---- kselftest harness ------------------------------------------------= --- */ @@ -199,6 +238,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, &fs_ops, &self->thread, err)) SKIP(return, err); @@ -322,4 +362,143 @@ TEST_F(acl_cache, stale_after_force_sync) EXPECT_EQ(count, 4); } =20 +FIXTURE(acl_cache_onoff) +{ + struct fuse_session *se; + char mountpoint[MOUNTPOINT_SZ]; + 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, &fs_ops, &self->thread, err)) + SKIP(return, err); + + snprintf(self->pathname, sizeof(self->pathname), + "%s/" FILE_NAME, self->mountpoint); +} + +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 Sun Sep 27 06:24:53 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 5DF9F46D552; Fri, 4 Sep 2026 10:38:56 +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=1788518339; cv=none; b=i0yDnwpfCfCIfoX23sZny5mT1bX2eA8nSmFHkknUgpvuaH8v9SNgDXQ2Z7dkC+UsfX24fND1N/SqxLKzAmGUIA8MbWka5mDdCP1qdzBsYVJzlnshUCavTlUwk1WErAjc30DiDibdVd09TWAkjp9yQ/tdKgcMjizlKJKGYZ/jU+s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518339; c=relaxed/simple; bh=HVNKdp8QPBnhWmS003IJ4brEGD7nc6YWJDSBz5Z80LU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JNQATFWpPdliKnlx1xrrpXI/DCYzH/rqiKRkoOHC1Ds8b37eYdq2P1BKVq3djDRiJTf63plj1372FMBXUdAP+MFLDQgyFnoPPdsUaU6Ve3XkYJy24cVZKBf5jTpGKoV3kfO46cgruPmFPt8gJ/RPeIoiq2LJ0VnCbYmI/USbdko= 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=faJXZTdc; 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="faJXZTdc" 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=v8GJCPhpqC0YaeiDUyqTquvAFBuyMrVORsJ8zBdozZw=; b= faJXZTdc3s36z2IQGJUSQ7HI+OpG16cw7UnJVNhUi/FmqLPWTNL8UgQhxR06NBq1x6DZcw/RzTZKe 5Mt4pVrah3khRhbjGGEdDqmdTh227+JneBS7G0HjBJwe6I86zrDv0QAl2DvHRvqAaV6Xkm21p2wIk nQOUyd1DGVLAzvQJbznFLjWVphBtK7TMAUIaVv2oS2ipWb0dU0sBg0IfQm5yWe6R8AN/EmO31Yc8+ MS9IE9LWy3hu9eC9zK57e2UiOFVbvHKvxIf2vhSUrDWntQE0jE1r6slXopeNZ7nzvYLuNhnqSVceh 7FeqLUvoc7Q7Q4Us8bcSBnO3cdzfyI74dA==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJn-00Eq1I-Mt; Fri, 04 Sep 2026 12:38:47 +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 v3 7/8] selftests/fuse: add fuse symlink caching test Date: Fri, 4 Sep 2026 11:39:19 +0100 Message-ID: <20260904103920.4471-8-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 | 2 + .../fuse/fuse_symlink_cache_test.c | 167 ++++++++++++++++++ 3 files changed, 170 insertions(+) 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 f7f3dd345a50..ebfe7133d811 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -3,3 +3,4 @@ fuse_mnt fusectl_test write_extend_eof_test fuse_acl_cache_test +fuse_symlink_cache_test diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index 7744f796eb06..3a5a557dde7a 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -5,6 +5,7 @@ CFLAGS +=3D -Wall -O2 -g $(KHDR_INCLUDES) TEST_GEN_PROGS :=3D fusectl_test TEST_GEN_PROGS +=3D write_extend_eof_test TEST_GEN_PROGS +=3D fuse_acl_cache_test +TEST_GEN_PROGS +=3D fuse_symlink_cache_test TEST_GEN_FILES :=3D fuse_mnt =20 include ../../lib.mk @@ -28,5 +29,6 @@ $(OUTPUT)/fuse_mnt: CFLAGS +=3D $(VAR_CFLAGS) $(OUTPUT)/fuse_mnt: LDLIBS +=3D $(VAR_LDLIBS) =20 $(OUTPUT)/fuse_acl_cache_test: fuse_common.c fuse_acl_cache_test.c +$(OUTPUT)/fuse_symlink_cache_test: fuse_common.c fuse_symlink_cache_test.c =20 EXTRA_CLEAN :=3D fuse_common.o 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..ca3c5cdaf578 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c @@ -0,0 +1,167 @@ +// 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 "kselftest_harness.h" + +#include "fuse_common.h" + +#define FILENAME "file" +#define FILE_INO 42 + +#define LINKNAME "link" +#define LINK_INO 43 + +#define TIMEOUT 86400.0f + +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 fs_ops =3D { + .init =3D fs_init, + .lookup =3D fs_lookup, + .readlink =3D fs_readlink, +}; + +FIXTURE(symlink_cache) +{ + struct fuse_session *se; + char mountpoint[MOUNTPOINT_SZ]; + 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 err[MAX_ERR_MSG]; + + pthread_mutex_lock(&test_state.lock); + test_state.readlink_counter =3D 0; + test_state.cache =3D variant->cache; + pthread_mutex_unlock(&test_state.lock); + + if (fs_setup(&self->se, self->mountpoint, &fs_ops, &self->thread, err)) + SKIP(return, err); +} + +FIXTURE_TEARDOWN(symlink_cache) +{ + fs_teardown(self->se, self->thread, 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 Sun Sep 27 06:24:53 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 B81EE4078EA; Fri, 4 Sep 2026 10:38:55 +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=1788518338; cv=none; b=KYE5eC+8tJ/2jcKp6FTAgbkZXQ6vjw9FyPEzJpSzHGUiBTZgkZHaHThsgRd7DAftLNfLMvQZbttJZv5okYtvvaKa/NeItPrFfPaBfm8TMW5fqv/c0EWdtHfYpUcc0nO7RZIGXWw5UR26hcAk0UtEcLFs8P2MlqUcznTzSYOd5Xg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788518338; c=relaxed/simple; bh=M6VPZSP4N7Tdl7alnu4MXD0zXNdQWm8noHuUEkT12l0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OaCwgaeKiYIdguZkM5lBlLSpCIFVXvUiqscYZ7DDjn6qrRTr8PsxU5qC9f5cDFxH+Hxy8T1AUNkumzZjofTzx9GVkDE2DpGrYYXaQtTJjApU0m+fDR3Oo7cTcX0juVWZQQTVHoz0uSg/g+pUbkph4KNiDFzqgFW5iBozhEFT5xg= 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=qoUjr8yF; 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="qoUjr8yF" 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=NM1eSMoCqUMUlgdICsVdSOO0iW8qsu55z6uJhM9FWVU=; b= qoUjr8yFtOeWyPrlWrxqWfpO27QX6LzwC3KFrKTV93GO+HVW8T5sVtxK3UATbMtw2FXk+67q/zkue 17wL/NpJbQKCvTO9BCVmlTxw86xU9lOjG72I1ICHKOfA+9W6B+YhKf6ZiGFtAYkKK6CcOdM7l90PA AMx2Lv9FFhnTWqfqHx+6MReeJMQJdVZzx4mywgdcSRLTLPVtqV7KnBdVE/WxXFX9NDn0zD9N0hQuY ZUh5jAI8ewxbdbdiwAYQtrX6sg7J//UelKdhfpfL0lvJh46K9/jFcFz8VzggrvGi03qmzNiXnyBpi 2ycpkRw4kpkTkQ0u6s+O69iNo9EWe3DCsQ==; Received: from bl21-120-122.dsl.telepac.pt ([2.82.120.122] helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1x2RJo-00Eq1O-5D; Fri, 04 Sep 2026 12:38:48 +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 v3 8/8] selftests/fuse: add fuse readdir caching test Date: Fri, 4 Sep 2026 11:39:20 +0100 Message-ID: <20260904103920.4471-9-luis@igalia.com> In-Reply-To: <20260904103920.4471-1-luis@igalia.com> References: <20260904103920.4471-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 | 2 + .../fuse/fuse_readdir_cache_test.c | 274 ++++++++++++++++++ 3 files changed, 277 insertions(+) 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 ebfe7133d811..ffa356f23e8c 100644 --- a/tools/testing/selftests/filesystems/fuse/.gitignore +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -4,3 +4,4 @@ fusectl_test write_extend_eof_test fuse_acl_cache_test fuse_symlink_cache_test +fuse_readdir_cache_test diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index 3a5a557dde7a..570ced181168 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -6,6 +6,7 @@ TEST_GEN_PROGS :=3D fusectl_test TEST_GEN_PROGS +=3D write_extend_eof_test TEST_GEN_PROGS +=3D fuse_acl_cache_test TEST_GEN_PROGS +=3D fuse_symlink_cache_test +TEST_GEN_PROGS +=3D fuse_readdir_cache_test TEST_GEN_FILES :=3D fuse_mnt =20 include ../../lib.mk @@ -30,5 +31,6 @@ $(OUTPUT)/fuse_mnt: LDLIBS +=3D $(VAR_LDLIBS) =20 $(OUTPUT)/fuse_acl_cache_test: fuse_common.c fuse_acl_cache_test.c $(OUTPUT)/fuse_symlink_cache_test: fuse_common.c fuse_symlink_cache_test.c +$(OUTPUT)/fuse_readdir_cache_test: fuse_common.c fuse_readdir_cache_test.c =20 EXTRA_CLEAN :=3D fuse_common.o 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..7c49cc6a23e3 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_readdir_cache_test.c @@ -0,0 +1,274 @@ +// 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). + */ + +#include +#include +#include + +#include "kselftest_harness.h" + +#include "fuse_common.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 + +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, +}; + +FIXTURE(readdir_cache) +{ + struct fuse_session *se; + char mountpoint[MOUNTPOINT_SZ]; + 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 err[MAX_ERR_MSG]; + + 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); + + if (fs_setup(&self->se, self->mountpoint, &fs_ops, &self->thread, err)) + SKIP(return, err); +} + +FIXTURE_TEARDOWN(readdir_cache) +{ + fs_teardown(self->se, self->thread, 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