From nobody Fri Sep 25 04:40:50 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 EDF4D4AB3B8; Wed, 16 Sep 2026 15:55:51 +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=1789574169; cv=none; b=GvFsylIH8qBiPyf47sdqRZRYPwsCLm+pOlBDOemj6gDbIph7L1ILeNGV2O7nUPbXwYnwX1dF1g+jigS4Sx+zQ6SydxBukIaKyACc3YPCsaUPkpwxlINGJUum5rbUdAgqTEyjjGf911E0nLWjKf0dXrHaF0nBNvb/avV02OwaVes= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574169; c=relaxed/simple; bh=SJ1dQmglYm8zeefIb3WBPDxbjCnAGi1AF8SZ+vDra48=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OxZZElVQfJt+dPMpzt1ehkUsKUqhrwZnIeOtwljqcspjD3mueSJ9B++TUeHSK/oFJFODLjY4qZsA+0kTTRXvpYAO8uAeSXnHNM78tbsIxr2EetGO5gTookIiaRVbfDURSn5sV3r7YnhZZYEv8Kgw2jfJZGwOlLy/6a6aEMKWsHo= 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=JENy3fMF; 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="JENy3fMF" 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=kS+gOOxZwZTiTZdXzBZnDBM6FaeqB/roZcfxX4lzLsQ=; b= JENy3fMFNr9FR/0tkbzJf58khr2gkMyx5h/TdK8YJ0hq7aken/cG6+x7qbQTtpRSPvDfpzVOHHs8R BRzy4nbhZldXGhjnVTOZjTpIzTlOtnfnOLj2hAP7psS/LNB+Sa2czxYCOLY/5TrruBQvL7t0TD5BK dzc6eJTRB+zL1nnXYjd1HkFaKW1HulZyAUKJ6jrOGUi0wMUovhoOPEx5vYJGbM16NnGBpItOrTy+Q Lkl7n5IxLYS8h6kCZi1on5xgOykChG3vgbh6Udpx2NhGn26cvAyTRQ30FXoiU0q7TBqhyXGf4hJJB 7nlBLiAMKKoYoCHT1upF6I2B3ql3+HHkFQ==; 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 1x6ryw-0031JV-3R; Wed, 16 Sep 2026 17:55: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 v4 1/8] Documentation: fuse: add document on caches being used by FUSE Date: Wed, 16 Sep 2026 16:56:22 +0100 Message-ID: <20260916155629.32421-2-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 Reviewed-by: Amir Goldstein --- .../filesystems/fuse/fuse-caches.rst | 148 ++++++++++++++++++ Documentation/filesystems/fuse/index.rst | 1 + 2 files changed, 149 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..b133066429b1 --- /dev/null +++ b/Documentation/filesystems/fuse/fuse-caches.rst @@ -0,0 +1,148 @@ +.. 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. For +example, upon a user-space request for invalidation, through +``FUSE_NOTIFY_INVAL_INODE``, ``FUSE_NOTIFY_INVAL_ENTRY``, or +``FUSE_NOTIFY_DELETE`` requests. + +FUSE uses fine-grained invalidation masks rather than invalidating all +attributes at once. The principle is that each operation only invalidates = the +specific attributes that the operation could have changed on the server. T= he +masks used are: + +- ``STATX_ATIME`` - after reads and readlink, since the server may update = access + time +- ``STATX_CTIME`` - after xattr changes (including ACL set/remove) and ren= ame +- ``STATX_BLOCKS`` - after a successful flush with writeback cache, since = the + server's block count may differ from the local one +- ``FUSE_STATX_MODIFY`` (``STATX_MTIME | STATX_CTIME | STATX_BLOCKS``) - a= fter + writeback completion (without writeback cache), since the server may have + updated modification metadata +- ``FUSE_STATX_MODSIZE`` (``FUSE_STATX_MODIFY | STATX_SIZE``) - after writ= es, + truncate-on-open, and fallocate, since the server's size and modification + metadata may have changed +- ``FUSE_STATX_MODDIR`` (``FUSE_STATX_MODSIZE | STATX_NLINK``) - after dir= ectory + modifications (create, unlink, mkdir, rmdir, rename), since the server m= ay + have updated the directory's size, timestamps, and link count +- ``STATX_BASIC_STATS`` - as a full invalidation, used for server-initiated + invalidation (FUSE\ :sub:`NOTIFY`\ \_INVAL\ :sub:`INODE`), interrupted + setattr, and interrupted link + +The full set of invalidation points can be found by searching for +``fuse_invalidate_attr_mask()`` in the FUSE source. + +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. +- 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. +- Whenever attributes are refreshed from the server. For example, when whi= le + revalidating a dentry (``->d_revalidate()``), or when updating a dentry = during + while processing a ``FUSE_READDIRPLUS``. +- 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. +``FOPEN_KEEP_CACHE`` is about keeping the cache on **this** open, not on s= ome +**next** open. + +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 + diff --git a/Documentation/filesystems/fuse/index.rst b/Documentation/files= ystems/fuse/index.rst index 3dada6c4057a..c03c8b7095ed 100644 --- a/Documentation/filesystems/fuse/index.rst +++ b/Documentation/filesystems/fuse/index.rst @@ -12,4 +12,5 @@ FUSE (Filesystem in Userspace) Technical Documentation fuse-io fuse-io-uring fuse-passthrough + fuse-caches uapi/fuse-uapi-io-uring From nobody Fri Sep 25 04:40:50 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 723D246A611; Wed, 16 Sep 2026 15:55:51 +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=1789574166; cv=none; b=imnOxCzDONUH2K7O8NrTnf9RejKD4WEuiQ+dLeWPuwxdnpzeUblBiYi4QZn6alurbS4OnLJ0hb7doXKDmMgNltk0MUlSqxdskVu2+1+xjWckP6QHajhv+9CUbkF4Lye/N5BPkjB6QIEcXBRf2MG9AWBg/10UXkRIW2JZqvAl9xE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574166; c=relaxed/simple; bh=+HuJRv9MxBWoIl1CqVAhOCJ8Nb9U+afeGbiwDFQ1MhE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UPF/QlE3R5kMybNc2O/bzXs6DPx3hNypGO49mC1ho15PRHdXsUKQEZ4nvd1+vPf7hRIC0U3EzCmjXvNHYqie49KPcILyQyoDDxVBHDAap5mUpHQgl++zXxP/BSbI2M+Mh/xfBo+6M9NvT8T7NG/jq5Lw/QArt/5Kg/8hFDRSfmk= 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=p36Y2I9L; 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="p36Y2I9L" 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= p36Y2I9LJj/bsFni3acAq1GmwH0OjskgeZUXZo+6gCiR8B4fAyqJRONSYqsGRZ7irYkFQQn+TEhCr 1aNyxL/gZRjRipCETTITooZqOVubkhM/7n2TfXmM3vyoiuXlrbhnJQ2BVmTzLMpU4BnBn/ie8XlqP Dki9GDVdrzJm8jo13mvulO5wc2S2NjhdiXBp8aKaT4LjG+oDOTKx2FaXEoyV31CPuGKcV2q6XegIA 5dWXn+R98tYOz0XA99khC/wGBJQT6DV3YQmb+a7EKi10KxbHfGvc0LAJAa27Vgtvrpu3RM1zNKgbn jMTQN230QUnm/KQ9uh0EhQpHQyLJ0QOdZg==; 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 1x6ryw-0031JX-Ix; Wed, 16 Sep 2026 17:55: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 v4 2/8] selftests/fuse: convert fusectl test to fuse3 Date: Wed, 16 Sep 2026 16:56:23 +0100 Message-ID: <20260916155629.32421-3-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 Fri Sep 25 04:40:50 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 00CEA4AB3C6; Wed, 16 Sep 2026 15:55:51 +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=1789574170; cv=none; b=XhSUpT6zGVUmrEAbuv/19x93219dui99mR/Pch3vDweWgPXp9FP4u6R75qNZVMft5tJEy87Qik5/zWvWNrnbuX9IYHEYLaAKRkZd7tNobYUY0sJJOST4MOy4328ho8lC/8XpjrcVotHbDtVhuaa3dKBNmPvuSXPPVFzp6bocTog= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574170; c=relaxed/simple; bh=uMLJBD9Kv4j1nt0LixvB8eNP1+A2sfNvXCh0Ue6yQcw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KpQcLjjCr/9hlshDGUr2O1W/v+FMSLgJNV0wfjw/CS6XarA5CoVtLmThOFhDMKCcc+9bDmwj1xzuyT1pIRXeWod0dsnErXEWYnZPl+tyIZlDDnHBKyUHzEdBRLpYBlqSzrcalJnQViSz3rVpuXr7zeojqVfoNU7hOuQk7aIG7VQ= 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=YbjSxQYd; 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="YbjSxQYd" 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=1ujvHLceBOFHXCHnX203gkfVGTvDsHWbfSLb1csGpk4=; b= YbjSxQYdnN2XScla70/61s9SPVmUuuKYpO56eKtVXcZygKKlhARPpXCZBJlRO+YJtvirPIxn00Kl/ 8iLMNhEWOWYS/Txl1LwQPI6qtyIGbXeYTs158hlXWzAO0TYyhD6iMLanydaslCsGI2MXohR/C0YWL 3JbR0pTK4d1sVAydhI7nBPP8ij/FF4diJaXUeWJLNyeQ1qU/lwJUQlQ9VyGKKU6y5CRrMyhErsA+v V+nJtjVJKbsOZyfiMwPn7TjVCMUA8Y7vFABFi8u4Aq+q/JC2jQWmXR32aGOx//Of62uJbIdd3xz9l 1tmtCLkgHInPikLJcXbi6GAlRZVvSlKs+Q==; 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 1x6ryx-0031JZ-2F; Wed, 16 Sep 2026 17:55: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 v4 3/8] selftests/fuse: check that fusectlfs is mounted Date: Wed, 16 Sep 2026 16:56:24 +0100 Message-ID: <20260916155629.32421-4-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 Fri Sep 25 04:40:50 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 720993A875A; Wed, 16 Sep 2026 15:55:51 +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=1789574159; cv=none; b=eDoXC5912VPzn8kggLe94HaSenEpV29CAEvIrEnAASy6rwwBWt9gFcuGs02PJv3Vc4Kr8ZEJ2nJcSmanTB/S6o1F3z/oiYeKIND2XQH6+nOsyFXav4aDIqdw/ZKm+tI+XDFaB9iqyfmHRGyydxlcqv1dW8qLioS4oPxuFgis2l4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574159; c=relaxed/simple; bh=L0zUr94stmbcNPjH3QA9eCx5/weIr846LNnSJdSgoio=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pambvg2JcB8kHe2XR5pEetd3EQnGgNziN/cqK5Jbp8eadu8KrDH2Ntq62MBekWUktl+v3uCiFUq65yFKHkMYmmPs3v41WPqVd0aYshtFjE7fBd85FJkGushvVAvT/nwptq7Tp1MIkAnBcPWvH96eJ9RY3emMRIZCgAJY58qUqe0= 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=gU2d/TOZ; 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="gU2d/TOZ" 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=TALft4xRNDXuClx/1xEEpG0D7kjKccpP6MW0ddcBMho=; b= gU2d/TOZxQ1W4ts8Oa31qsRT5EByxtXgrbAFlHmpKY87ohVhREoaXv2rpNCYLugVmRo8dOcsZvUO3 uAcb74tuTPQ9QvAijqm9sqdLWMsd1SdvEQ8vmVYKbfkZ9RkWcKA4ox+XwUB1g6Lf5NTX8mTBDQHWv qnCJ+pDScQ3x5/DSbE04wKSDqaIeIrVj7XxP5YIalnwGq4mZsWyK2EbktNruKFYWbBiuXqu2rPrKk BzUfz7+sVK+9bUNTAmXBuu8TpibK7bS24FBHGgTch6vc4XCpWyqOnX0U9Ztl7XocU6xOxy5HEyqGS i3imN1UU+ngE1rFguQdquT0/bw/NfXxZMg==; 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 1x6ryx-0031Jb-G6; Wed, 16 Sep 2026 17:55: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 v4 4/8] selftests/fuse: factor-out test fixture setup/teardown Date: Wed, 16 Sep 2026 16:56:25 +0100 Message-ID: <20260916155629.32421-5-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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. While there, also move some of the FIXTURE members into a fuse_common_ctx structure that can be passed into setup/teardown instead of passing them by reference. Signed-off-by: Luis Henriques Reviewed-by: Amir Goldstein --- .../selftests/filesystems/fuse/Makefile | 8 +- .../filesystems/fuse/fuse_acl_cache_test.c | 73 ++++--------------- .../selftests/filesystems/fuse/fuse_common.c | 58 +++++++++++++++ .../selftests/filesystems/fuse/fuse_common.h | 31 ++++++++ 4 files changed, 111 insertions(+), 59 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..d5a966e7fc15 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): @@ -88,6 +80,7 @@ static const uint8_t acl_b[] =3D { =20 struct daemon_state { pthread_mutex_t lock; + struct fuse_common_ctx ctx; const uint8_t *acl; size_t acl_size; int getxattr_count; @@ -176,69 +169,35 @@ 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 file_path[PATH_MAX]; - pthread_t thread; + char file_path[PATH_MAX]; }; =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); + pthread_mutex_lock(&g_ds.lock); + 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(&fs_ops, &g_ds.ctx, err)) + SKIP(goto out, 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); + "%s/" FILE_NAME, g_ds.ctx.mountpoint); +out: + pthread_mutex_unlock(&g_ds.lock); } =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); + pthread_mutex_lock(&g_ds.lock); + fs_teardown(&g_ds.ctx); + pthread_mutex_unlock(&g_ds.lock); } =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..d1d3dfd2ea67 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_common.c @@ -0,0 +1,58 @@ +// 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(const struct fuse_lowlevel_ops *fs_ops, + struct fuse_common_ctx *ctx, char *err) +{ + char *fuse_argv[] =3D { "fuse_test", NULL }; + struct fuse_args args =3D FUSE_ARGS_INIT(1, fuse_argv); + + strcpy(ctx->mountpoint, MOUNTPOINT_TEMPLATE); + if (!mkdtemp(ctx->mountpoint)) { + snprintf(err, MAX_ERR_MSG, "mkdtemp: %s", strerror(errno)); + return -1; + } + + ctx->se =3D fuse_session_new(&args, fs_ops, sizeof(*fs_ops), NULL); + if (!ctx->se) { + rmdir(ctx->mountpoint); + snprintf(err, MAX_ERR_MSG, "fuse_session_new failed"); + return -1; + } + + if (fuse_session_mount(ctx->se, ctx->mountpoint)) { + fuse_session_destroy(ctx->se); + rmdir(ctx->mountpoint); + snprintf(err, MAX_ERR_MSG, "fuse_session_mount failed " + "(missing fusermount3 or insufficient privileges)"); + return -1; + } + + if (pthread_create(&ctx->thread, NULL, run_daemon, ctx->se)) { + fuse_session_unmount(ctx->se); + fuse_session_destroy(ctx->se); + rmdir(ctx->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_common_ctx *ctx) +{ + fuse_session_exit(ctx->se); + fuse_session_unmount(ctx->se); + pthread_join(ctx->thread, NULL); + fuse_session_destroy(ctx->se); + rmdir(ctx->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..e3dd85daa4b0 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_common.h @@ -0,0 +1,31 @@ +#ifndef __SELFTEST_FUSE_COMMON_H__ +#define __SELFTEST_FUSE_COMMON_H__ + +#define _GNU_SOURCE +#include +#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 + +struct fuse_common_ctx { + struct fuse_session *se; + char mountpoint[MOUNTPOINT_SZ]; + pthread_t thread; +}; + +int fs_setup(const struct fuse_lowlevel_ops *fs_ops, + struct fuse_common_ctx *ctx, char *err); +void fs_teardown(struct fuse_common_ctx *ctx); + +#endif /* __SELFTEST_FUSE_COMMON_H__ */ From nobody Fri Sep 25 04:40:50 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 64B2849B46B; Wed, 16 Sep 2026 15:55:52 +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=1789574171; cv=none; b=XWzOcBLeakydEZjqOeMWSGNPJf/YmUUnXW7onKKVf/LScoyfFCHxNWMG6lWUBGyfHH1mupfl9vv/9XJQLfR9KS9GOutThzmszmc8EM/bLRcwRpoRnEGCSEJ9BpTJpFlHizmueO03TywrZdNXPUVaTS0VdBS1BT8P8uX5q2WqWLY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574171; c=relaxed/simple; bh=VWrxC+F7RNQED608TByTU9wAn+Ih9zmDUqi5DpoG1Wo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Uo96JwuiMKi69vDcWNQTD7is90yhZOsGvM+FMLk7hh7OWl4NyDeQrU699dasKEJfTgTbgcZNVMDBX7BdFL+CqRl0gCFkPZ+vaC3kgz4wF1Ay2U2UdJy4v+UbtOiFL0y0YcBqzOc66R4xg7EG6JsYof3XjbnakXHFC0UtsPgN8+o= 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=p4AilE3n; 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="p4AilE3n" 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=rxheP1ERmFlAX9lnQeyNk/8Tcf/EPLnd+vP9RKEHo74=; b= p4AilE3n3abzhCHp1JlNLpJfhGOoLDj67XYwRPpqfUDNmli8+Or5jVyIBAs5R0CZDlshZhSQsdiTc 2z4JwRH+FOyQ48OFS1Fj4OjSZgnYpvVYZH64FTmkXn0hAG0RoirwbPPTCrod4EF7VL37Gz0Pj/WVx Bsxf+j6T312ZW3LTaWs4f2d6ch80XFg59rFnoUx8qXEmqZVVv14y/TMcYMAbr3qWc5SjeMCrwgyW4 KSoIjM0m2Bpem/GfY98pERFr5jMtx0vrbzpI+9fN5XkD40iAkRfim85SAqbayhI4eHq0nj90aD8KX xvdbXEZbX0rjIwXKbAm8kiCf2Os3PH+8Hg==; 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 1x6ryx-0031Jd-UL; Wed, 16 Sep 2026 17:55: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 v4 5/8] selftests/fuse: use dynamically allocated memory to store ACLs Date: Wed, 16 Sep 2026 16:56:26 +0100 Message-ID: <20260916155629.32421-6-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 d5a966e7fc15..fb4e3554e3c9 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -81,7 +81,7 @@ static const uint8_t acl_b[] =3D { struct daemon_state { pthread_mutex_t lock; struct fuse_common_ctx ctx; - const uint8_t *acl; + uint8_t *acl; size_t acl_size; int getxattr_count; }; @@ -143,15 +143,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 @@ -161,6 +172,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 { @@ -180,8 +193,10 @@ FIXTURE_SETUP(acl_cache) char err[MAX_ERR_MSG]; =20 pthread_mutex_lock(&g_ds.lock); - 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(&fs_ops, &g_ds.ctx, err)) @@ -198,6 +213,7 @@ FIXTURE_TEARDOWN(acl_cache) pthread_mutex_lock(&g_ds.lock); fs_teardown(&g_ds.ctx); pthread_mutex_unlock(&g_ds.lock); + free(g_ds.acl); } =20 static int do_force_statx(const char *path) @@ -213,6 +229,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). @@ -273,8 +290,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 Fri Sep 25 04:40:50 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 F26F24AB3B9; Wed, 16 Sep 2026 15:55:51 +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=1789574178; cv=none; b=Q77uxxz/bgUG+qAd+HAVTXGYAMDAalacvFsBzF+mcXdwThbTFPvHLspEjaY240KMqGaa0CHxxY3IRL+pTfR67OcwVhP91c6T2GERcSfw8Z8e75upne/w/BqmTW4Rnnqb/ubm5+OaIXiPxV/YktOXRwCvtGYyKZyB0V9d0rHd974= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574178; c=relaxed/simple; bh=z3zjr0XsNuHaANYr0dL6PUWN/3yZajpQbLCKUWbd+ps=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pGJ5SO0H5QjHhDf6YpU0cPxQvKREfzoOcQFGI/jc7vXtCqJ2Km5MCjY0OMLKl1ynh+pXeVuniN5L0P5zuqJvxtDFlaYJjZQAXOESjDz0mqypmNA1NjhwkSFQ0sGVXiEScPyPn9+oaedVgIpHC0IQmOG6sPSGb2KTNYdYZ+xRoLk= 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=OrBMENQc; 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="OrBMENQc" 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=rIMeXjQ7oism9g1OPJrivzQXheiMOSH8xcFeWnoY0Hc=; b= OrBMENQc4cJDM938i7GRiUMHsuAhlCaH9K5zWWxpbu+2W4PL2SsUL5nEMGlRV73pGePUdis8NBN3U yMkkh5us6/LYmYiK9lJF/odwM/oZIH+astpm9MHbffyvxfZg0JET73h6moIuLD9HI2VVQzbc7vy9t rd/dOoJ+g3sDFzqL5K9zkIUVJ6oPhAzQMTOfnvGRQshubFpQFCuA4kBBB9HEZvaRYuWmhvNKTmXib v18JNIxLp1ItnO1gCuQllgXqBD4ci+fddMwpsh/2RTKJYWmGxTTOTZK2yHeuBu5n+H9gkluRk631V KTos77qpmqLU5bQZqVOs6mzzxxj1W829Ng==; 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 1x6ryy-0031Jr-Dv; Wed, 16 Sep 2026 17:55:36 +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 v4 6/8] selftests/fuse: add some extra ACL caching tests Date: Wed, 16 Sep 2026 16:56:27 +0100 Message-ID: <20260916155629.32421-7-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 Reviewed-by: Amir Goldstein --- .../selftests/filesystems/fuse/.gitignore | 1 + .../filesystems/fuse/fuse_acl_cache_test.c | 203 +++++++++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) 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 fb4e3554e3c9..173063efeee8 100644 --- a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -84,6 +84,9 @@ struct daemon_state { uint8_t *acl; size_t acl_size; int getxattr_count; + uid_t uid; + uid_t gid; + bool cache; }; =20 /* @@ -92,9 +95,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) { @@ -116,6 +127,10 @@ static void fs_lookup(fuse_req_t req, fuse_ino_t paren= t, const char *name) e.attr.st_ino =3D FILE_INO; e.attr.st_mode =3D S_IFREG | 0644; e.attr.st_nlink =3D 1; + pthread_mutex_lock(&g_ds.lock); + e.attr.st_uid =3D g_ds.uid; + e.attr.st_gid =3D g_ds.gid; + pthread_mutex_unlock(&g_ds.lock); fuse_reply_entry(req, &e); } =20 @@ -133,6 +148,10 @@ static void fs_getattr(fuse_req_t req, fuse_ino_t ino, st.st_ino =3D FILE_INO; st.st_mode =3D S_IFREG | 0644; st.st_nlink =3D 1; + pthread_mutex_lock(&g_ds.lock); + st.st_uid =3D g_ds.uid; + st.st_gid =3D g_ds.gid; + pthread_mutex_unlock(&g_ds.lock); } else { fuse_reply_err(req, ENOENT); return; @@ -161,12 +180,14 @@ static void fs_getxattr(fuse_req_t req, fuse_ino_t in= o, const char *name, } pthread_mutex_lock(&g_ds.lock); acl_size =3D g_ds.acl_size; - if (acl && (size >=3D acl_size)) + if (acl && acl_size && (size >=3D acl_size)) memcpy(acl, g_ds.acl, acl_size); g_ds.getxattr_count++; pthread_mutex_unlock(&g_ds.lock); =20 - if (size =3D=3D 0) + if (acl_size =3D=3D 0) + fuse_reply_err(req, ENODATA); + else if (size =3D=3D 0) fuse_reply_xattr(req, acl_size); else if (size < acl_size) fuse_reply_err(req, ERANGE); @@ -176,10 +197,42 @@ 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; + goto out; + } else if (strcmp(name, "system.posix_acl_access") !=3D 0) { + ret =3D ENOTSUP; + goto out; + } + acl =3D malloc(size); + if (!acl) { + ret =3D ENOMEM; + goto out; + } + 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); + +out: + 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 ------------------------------------------------= --- */ @@ -198,6 +251,9 @@ 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.uid =3D getuid(); + g_ds.gid =3D getgid(); + g_ds.cache =3D false; =20 if (fs_setup(&fs_ops, &g_ds.ctx, err)) SKIP(goto out, err); @@ -325,4 +381,147 @@ TEST_F(acl_cache, stale_after_force_sync) EXPECT_EQ(count, 4); } =20 +FIXTURE(acl_cache_onoff) +{ + char pathname[PATH_MAX]; +}; + +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]; + + pthread_mutex_lock(&g_ds.lock); + g_ds.acl =3D NULL; + g_ds.acl_size =3D 0; + g_ds.getxattr_count =3D 0; + g_ds.cache =3D variant->cache; + g_ds.uid =3D getuid(); + g_ds.gid =3D getgid(); + + if (fs_setup(&fs_ops, &g_ds.ctx, err)) + SKIP(goto out, err); + + snprintf(self->pathname, sizeof(self->pathname), + "%s/" FILE_NAME, g_ds.ctx.mountpoint); +out: + pthread_mutex_unlock(&g_ds.lock); +} + +FIXTURE_TEARDOWN(acl_cache_onoff) +{ + pthread_mutex_lock(&g_ds.lock); + fs_teardown(&g_ds.ctx); + pthread_mutex_unlock(&g_ds.lock); + 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(g_ds.ctx.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 Fri Sep 25 04:40:50 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 299463B6362; Wed, 16 Sep 2026 15:55:52 +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=1789574166; cv=none; b=NsKEFqyLqZCNx/IMkv31UFgg0vJsPzVXWZ8WWXWd0DXDIcSfJBrNwAVfFWBvfitQB+Qr1fVS8r99Ex9rPSnmgMXO7ea86GSmEVMQaRelE/W4ybZhC201Qf9fh+9+0/TnrEk3sU0vSDa6XsvpQltOtVHwtQuSnl7sZF3GLpDCf6U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574166; c=relaxed/simple; bh=S3yiUGo74MZYLKfatQvdKRDjKX+AEGa8XuqANprEQrg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hmlMYKDntLoSC7V6xVyYOVKr176o44P7JyillBGCVsoSW2P6Nq5e5EmgirCvagBr0ktUjVp16HGH9L8ji2jJ2w3LAN4u/Dlj/lmxnGXcxufsetAGiS0A+RVqODy9eiWkofu4J21omwFDCWqxofqf4M2BGSc1GXuMTGi/RVwGMB8= 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=McVaEY3Y; 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="McVaEY3Y" 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=k6wyE5sVu4T19/0ruj9Rq3mkqvb6mznMUt0SJ5BVkDg=; b= McVaEY3YRX+dJMpx8sxfASmZoF6/EbggnmXKnIBaqi0BimY6/4sNWqjaQs+o5Lqd8DWKI1zb2iTk1 m4SnN3ehIMycpl9ME33ExGL8b967AXVOH2cW+MjaKeWegMSvrsjmZkH4CN3A6p8AH4gaOfj0LV270 XO8oWlW5pRQAwKVku1Jn5fLLX52cCRlTnEFx6cuI/ILARBczBD6Imt/RqkjnImaJZaOGCQ36VyvYc cwyWRHPVdRQJqCehq3JQhJmFWc39ItTtTMwNMPAbeb7F7d7KmjAkoXvX8RBpYYBCuhAzFi+Jvq9/7 Q9x3dDnti1P5pVv9RW9jjRE9Hft2UCT+iQ==; 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 1x6ryy-0031KC-U9; Wed, 16 Sep 2026 17:55:36 +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 v4 7/8] selftests/fuse: add fuse symlink caching test Date: Wed, 16 Sep 2026 16:56:28 +0100 Message-ID: <20260916155629.32421-8-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 Reviewed-by: Amir Goldstein --- .../selftests/filesystems/fuse/.gitignore | 1 + .../selftests/filesystems/fuse/Makefile | 2 + .../fuse/fuse_symlink_cache_test.c | 163 ++++++++++++++++++ 3 files changed, 166 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..dd001736ebca --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_symlink_cache_test.c @@ -0,0 +1,163 @@ +// 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 "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; + struct fuse_common_ctx ctx; + int readlink_counter; + bool cache; +} 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) !=3D 0 && strcmp(name, LINKNAME) !=3D 0)) { + fuse_reply_err(req, ENOENT); + return; + } + + 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); + } + 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) {}; +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; + + if (fs_setup(&fs_ops, &test_state.ctx, err)) + SKIP(goto out, err); +out: + pthread_mutex_unlock(&test_state.lock); +} + +FIXTURE_TEARDOWN(symlink_cache) +{ + pthread_mutex_lock(&test_state.lock); + fs_teardown(&test_state.ctx); + pthread_mutex_unlock(&test_state.lock); +} + +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", test_state.ctx.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 Fri Sep 25 04:40:50 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 1D7B33B47CA; Wed, 16 Sep 2026 15:55:51 +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=1789574164; cv=none; b=YLf9rNcmRH7JuXKyJjGKhwrOwQ6Ef9wtRnMVUY84Zrn/8YoaI2Oo7biUDfhN1HHr1hEo1BIPCeQJZcuaY5E0tdUbAuVkn+J7OpiFrUUNQfXHEHU3RUTB6SG+ycu0b6wcxqdHwJmeIeHc5O3xTB1qWnwqxc7NuV2bbkMZ4vrQaWc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789574164; c=relaxed/simple; bh=kyWRjd1c0CVvxAiTGmx5HgdJ6SXn5cNlGFy58R6ztgs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=H2eiyEAwVeqfOo8ksyVFn6f0jrFzoYyjRGJ1Od/amQU45W2tfy78isjiUWvOZmksZVnvlfp4CawfDHfDlRVIBUuIG0vcdsfu0dDSPggQQWiWp8J9nJJMpNw/2vp1Y2an2sFog3G1nD5f7Hcotwbwkz27vpzCKxxlCJNz+5kYrRU= 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=egSxCQm7; 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="egSxCQm7" 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=vd0tWNXr2Iefep8kuK6/9zmSDrvKu2e/ierW5Ebi1kc=; b= egSxCQm7GY3pVXNtcpCrWbYETmkmks/VgnaXvZcD4+da7+xawucftHKBnWnXya0x4rNANfYeqFg7x TMtuXxuRv2n+WrN82XCRFHSK8aui1KehB5Eadx+04DZOKcuC0KOECAFIrj5wAliyJ0zfT2SAnFKJo UqqONI2CB8oVS3JpCYMrWZVmTgbe3aZk3gfmz6STskqiMInRJPEr+8dQRj33qCwS1iF87B+5/OjfB 1fRJkpEB2OKfftgpC0i/NgslN4cnLtWEGk0zN5T5oTq2o0AIOxKya40/azq6xM5Rss7ceXa61aIas zG/e7L57YxzVfdh4ys//B6E5bHMBiLPnqg==; 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 1x6ryz-0031KH-KT; Wed, 16 Sep 2026 17:55:37 +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 v4 8/8] selftests/fuse: add fuse readdir caching test Date: Wed, 16 Sep 2026 16:56:29 +0100 Message-ID: <20260916155629.32421-9-luis@igalia.com> In-Reply-To: <20260916155629.32421-1-luis@igalia.com> References: <20260916155629.32421-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 Reviewed-by: Amir Goldstein --- .../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..1fdf422cf75b --- /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 "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; + struct fuse_common_ctx ctx; + time_t mtime; + int readdir_counter; + bool cache_readdir; + bool keep_cache; +} 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); + return; + } + + e.ino =3D DIR_INO; + e.attr.st_mode =3D S_IFDIR | 0755; + e.attr.st_nlink =3D 1; + pthread_mutex_lock(&test_state.lock); + e.attr.st_mtime =3D test_state.mtime; + pthread_mutex_unlock(&test_state.lock); + 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; + pthread_mutex_lock(&test_state.lock); + st->st_mtime =3D test_state.mtime; + pthread_mutex_unlock(&test_state.lock); + + 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) {}; + +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.mtime =3D time(NULL); + test_state.readdir_counter =3D 0; + test_state.cache_readdir =3D variant->cache_readdir; + test_state.keep_cache =3D variant->keep_cache; + + if (fs_setup(&fs_ops, &test_state.ctx, err)) + SKIP(goto out, err); +out: + pthread_mutex_unlock(&test_state.lock); +} + +FIXTURE_TEARDOWN(readdir_cache) +{ + pthread_mutex_lock(&test_state.lock); + fs_teardown(&test_state.ctx); + pthread_mutex_unlock(&test_state.lock); +} + +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", test_state.ctx.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