public inbox for v9fs@lists.linux.dev
 help / color / mirror / Atom feed
From: Tingmao Wang <m@maowtm.org>
To: Eric Van Hensbergen <ericvh@kernel.org>,
	Dominique Martinet <asmadeus@codewreck.org>,
	Latchesar Ionkov <lucho@ionkov.net>,
	Christian Schoenebeck <linux_oss@crudebyte.com>
Cc: "Tingmao Wang" <m@maowtm.org>,
	v9fs@lists.linux.dev, "Mickaël Salaün" <mic@digikod.net>
Subject: [PATCH 0/3] fs/9p: d_revalidate changes and using it for uncached mode
Date: Sun,  6 Apr 2025 17:18:41 +0100	[thread overview]
Message-ID: <cover.1743956147.git.m@maowtm.org> (raw)

Hi 9p-fs maintainers,

This patchset fixes a bug I've noticed while testing 9pfs with Landlock,
although this is not otherwise related to Landlock.  The main gist of the
fix is in the first patch, quoting the message here:

> Currently if another process keeps a file open, due to existing dentry in
> the dcache, other processes will not see updated metadata of that file if
> it is changed on the server, even in uncached mode.
>
> This can also manifest as -ENODATA when reading a file that has shrunk on
> the server (even if it's re-opened in another process), or -ENOTSUPP if
> the file has changed type (e.g. regular file to directory) on the server.
> We can end up in a situation where both `readdir` or `read` fails until
> the file is closed by all processes using it.

Here is a sample program that can be used to reproduce this issue.  It
takes a filename as argument and keeps that file open.

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <stdbool.h>

    int main(int argc, char const *argv[])
    {
        if (argc < 2) {
            printf("Usage: %s <filename>\n", argv[0]);
            return 1;
        }
        const char *filename = argv[1];
        int fd = open(filename, O_RDONLY);
        if (fd < 0) {
            perror("open");
            return 1;
        }
        while (true) {
            char buf[4096];
            ssize_t bytes_read = read(fd, buf, sizeof(buf)-1);
            if (bytes_read < 0) {
                perror("read");
                return 1;
            }
            buf[bytes_read] = '\0';
            printf("Read %zd bytes: %s\n", bytes_read, buf);
            printf("Press Enter to read again or Ctrl+C to exit...\n");
            while (getchar() != '\n') {}
            lseek(fd, 0, SEEK_SET);
        }
        return 0;
    }

There are two kinds of error. For -ENODATA:

On the server, prepare a folder with a file that initially has a longer content:

    # mkdir -p /tmp/linux-test
    # echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > /tmp/linux-test/readme

Export the folder with 9pfs, and mount it in a QEMU guest with 9p share:

    guest # mount -t 9p -o trans=virtio,version=9p2000.L,debug=9,cache=none test /mnt
    [   16.254728][  T163] 9pnet: -- v9fs_mount (163):  simple set mount, return 0

Run openread in the background:

    guest # ./openread /mnt/readme &
    [1] 164
    guest # [   27.159295][  T164] 9pnet: -- v9fs_vfs_lookup (164): dir: ffff888105590000 dentry: (readme) ffff888102f0e000 flags: 0
    [   27.159827][  T164] 9pnet: -- v9fs_fid_find (164):  dentry: / (ffff888102f0d780) uid 0 any 0
    [   27.160161][  T164] 9pnet: -- v9fs_fid_find_inode (164):  inode: ffff888105590000
    [   27.160464][  T164] 9pnet: -- v9fs_fid_find (164):  dentry: / (ffff888102f0d780) uid 0 any 0
    [   27.160720][  T164] 9pnet: -- v9fs_fid_find_inode (164):  inode: ffff888105590000
    [   27.160913][  T164] 9pnet: -- v9fs_fid_find (164):  dentry: / (ffff888102f0d780) uid 0 any 0
    [   27.161130][  T164] 9pnet: -- v9fs_fid_find_inode (164):  inode: ffff888105590000
    [   27.162124][  T164] 9pnet: -- v9fs_vfs_lookup (164): readme -> qid->path 51094
    [   27.163190][  T164] 9pnet: -- v9fs_file_open (164): inode: ffff888105590648 file: ffff88810a779c00
    [   27.163679][  T164] 9pnet: -- v9fs_fid_find (164):  dentry: readme (ffff888102f0e000) uid 0 any 0
    [   27.165294][  T164] 9pnet: -- v9fs_file_read_iter (164): fid 3 count 4095 offset 0
    Read 68 bytes: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

    Press Enter to read again or Ctrl+C to exit...

Shrink the file on the server:

    # echo aa > /tmp/linux-test/readme

Try to read it in the client.  Note that this is from a new process -- the
original openread process would also fail with the same error if we `fg`
then press enter on it.

    guest # head /mnt/readme
    [   75.992862][  T165] 9pnet: -- v9fs_file_open (165): inode: ffff888105590648 file: ffff888107aeaf40
    [   75.997456][  T165] 9pnet: -- v9fs_fid_find (165):  dentry: readme (ffff888102f0e000) uid 0 any 0
    [   75.999052][  T165] 9pnet: -- v9fs_file_read_iter (165): fid 4 count 8192 offset 0
    head: error reading '/mnt/readme': No data available
    [   76.001290][  T165] 9pnet: -- v9fs_dir_release (165): inode: ffff888105590648 filp: ffff888107aeaf40 fid: 4

Note that a stat will resolve the situation in this case, since that
updates the metadata.  It also happens that `cat` does a fstat, so using
`cat` to read the file would not reproduce this issue:

    guest # stat /mnt/readme
    [  371.948720][  T172] 9pnet: -- v9fs_vfs_getattr_dotl (172): dentry: ffff888102f0e000
    [  371.952462][  T172] 9pnet: -- v9fs_fid_find (172):  dentry: readme (ffff888102f0e000) uid 0 any 0
    File: /mnt/readme
    Size: 3         	Blocks: 8          IO Block: 131072 regular file
    guest # head /mnt/readme
    [  373.141565][  T173] 9pnet: -- v9fs_file_open (173): inode: ffff888105590648 file: ffff8881071f32c0
    [  373.141969][  T173] 9pnet: -- v9fs_fid_find (173):  dentry: readme (ffff888102f0e000) uid 0 any 0
    [  373.143636][  T173] 9pnet: -- v9fs_file_read_iter (173): fid 4 count 8192 offset 0
    aa
    [  373.145526][  T173] 9pnet: -- v9fs_file_read_iter (173): fid 4 count 8192 offset 3
    [  373.146358][  T173] 9pnet: -- v9fs_dir_release (173): inode: ffff888105590648 filp: ffff8881071f32c0 fid: 4

The other failure mode, which is perhaps less common but more problematic,
is when the file changes type on the host.  Continuing the above, on the
server:

    # rm /tmp/linux-test/readme
    # mkdir /tmp/linux-test/readme

Now the file/dir is inaccessible in the client:

    guest # cat /mnt/readme
    [  509.682250][  T177] 9pnet: -- v9fs_file_open (177): inode: ffff888105590648 file: ffff88810b2756c0
    [  509.682836][  T177] 9pnet: -- v9fs_fid_find (177):  dentry: readme (ffff888102f0e000) uid 0 any 0
    [  509.688049][  T177] 9pnet: -- v9fs_vfs_getattr_dotl (177): dentry: ffff888102f0e000
    [  509.691997][  T177] 9pnet: -- v9fs_fid_find (177):  dentry: readme (ffff888102f0e000) uid 0 any 0
    [  509.696235][  T177] 9pnet: -- v9fs_file_read_iter (177): fid 4 count 131072 offset 0
    qemu-system-x86_64: warning: 9p: bad client: T_read request on directory only expected with 9P2000.u protocol version
    cat: /mnt/readme: Operation not supported
    [  509.700970][  T177] 9pnet: -- v9fs_dir_release (177): inode: ffff888105590648 filp: ffff88810b2756c0 fid: 4
    guest # ls /mnt/readme/
    ls: cannot access '/mnt/readme/': Not a directory

Both issues are fixed by this patchset, with the debug prints added in
commit #3 shown below:

    ... Same setup as before, readme is a long file, we "openread" it,
    shortens it on the host, then read it with head ...

    guest # head /mnt/readme
    [  114.024938][  T196] 9pnet: -- v9fs_fid_find (196):  dentry: readme (ffff8881075d0758) uid 0 any 0
    [  114.026228][  T196] 9pnet: -- __v9fs_lookup_revalidate (196): dentry: readme (ffff8881075d0758) is valid
    [  114.026596][  T196] 9pnet: -- v9fs_file_open (196): inode: ffff8881074b8c90 file: ffff88810bee7480
    [  114.026971][  T196] 9pnet: -- v9fs_fid_find (196):  dentry: readme (ffff8881075d0758) uid 0 any 0
    [  114.027970][  T196] 9pnet: -- v9fs_file_read_iter (196): fid 4 count 8192 offset 0
    aa
    [  114.029430][  T196] 9pnet: -- v9fs_file_read_iter (196): fid 4 count 8192 offset 3
    [  114.030204][  T196] 9pnet: -- v9fs_dir_release (196): inode: ffff8881074b8c90 filp: ffff88810bee7480 fid: 4

    ... Now change the file to a dir on the host ...

    root@d8c28a676d72:/# ls -lah /mnt/readme
    [  177.047326][  T197] 9pnet: -- v9fs_fid_find (197):  dentry: readme (ffff8881075d0758) uid 0 any 0
    [  177.048901][  T197] 9pnet: -- __v9fs_lookup_revalidate (197): dentry: readme (ffff8881075d0758) invalidated due to type change
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    [  177.049400][  T197] 9pnet: -- v9fs_vfs_lookup (197): dir: ffff8881074b8000 dentry: (readme) ffff888102ecc000 flags: 0
    [  177.049914][  T197] 9pnet: -- v9fs_fid_find (197):  dentry: / (ffff8881075ce5e0) uid 0 any 0
    ...
    [  177.095001][  T197] 9pnet: -- v9fs_fid_find (197):  dentry: readme (ffff888102ecf8f8) uid 0 any 0
    [  177.096224][  T197] 9pnet: -- v9fs_dir_readdir_dotl (197): name readme
    [  177.097073][  T197] 9pnet: -- v9fs_dir_release (197): inode: ffff888102f192d8 filp: ffff88810aebf640 fid: 5
    [  177.097616][  T197] 9pnet: -- v9fs_dentry_release (197):  dentry: readme (ffff888102ecf8f8)
    total 8.0K
    drwxr-x--- 2 1000 1000 4.0K Apr  6 15:20 .
    drwxr-x--- 7 1000 1000 4.0K Apr  6 15:20 ..

While I haven't measured the impact, I expect there to be performance hit
only when opening a file that's already open by another process in
uncached mode, due to the extra getattr request.  When a file is not
opened by anyone, it won't have a dentry due to the use of
always_delete_dentry, and thus this d_revalidate change isn't applicable.

Also, I decided to reuse the dentry and call v9fs_refresh_inode_dotl if
the type stayed the same, even in uncached mode.  From looking at the
usages of, and the comment in v9fs_refresh_inode_dotl, I believe this is
safe to do.

Let me know if you wants more testing -- I'm happy to do so.

Tingmao

Tingmao Wang (3):
  fs/9p: Refresh metadata in d_revalidate for uncached mode too
  fs/9p: Invalidate dentry if inode type change detected in cached mode
  fs/9p: Add p9_debug(VFS) in d_revalidate

 fs/9p/vfs_dentry.c     | 33 +++++++++++++++++++++++++++++----
 fs/9p/vfs_inode.c      |  8 +++++++-
 fs/9p/vfs_inode_dotl.c |  8 +++++++-
 3 files changed, 43 insertions(+), 6 deletions(-)


base-commit: 38fec10eb60d687e30c8c6b5420d86e8149f7557
--
2.39.5

             reply	other threads:[~2025-04-06 16:19 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-06 16:18 Tingmao Wang [this message]
2025-04-06 16:18 ` [PATCH 1/3] fs/9p: Refresh metadata in d_revalidate for uncached mode too Tingmao Wang
2025-04-06 16:18 ` [PATCH 2/3] fs/9p: Invalidate dentry if inode type change detected in cached mode Tingmao Wang
2025-04-06 16:18 ` [PATCH 3/3] fs/9p: Add p9_debug(VFS) in d_revalidate Tingmao Wang
2025-04-14 21:01 ` [PATCH 0/3] fs/9p: d_revalidate changes and using it for uncached mode Dominique Martinet
2025-08-15  4:12   ` Dominique Martinet
2025-08-19 12:57     ` Mickaël Salaün

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cover.1743956147.git.m@maowtm.org \
    --to=m@maowtm.org \
    --cc=asmadeus@codewreck.org \
    --cc=ericvh@kernel.org \
    --cc=linux_oss@crudebyte.com \
    --cc=lucho@ionkov.net \
    --cc=mic@digikod.net \
    --cc=v9fs@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox