diff mbox series

Revert series: virtiofsd: Announce submounts to the guest

Message ID 160385090886.20017.13382256442750027666.stgit@gimli.home (mailing list archive)
State New, archived
Headers show
Series Revert series: virtiofsd: Announce submounts to the guest | expand

Commit Message

Alex Williamson Oct. 28, 2020, 2:13 a.m. UTC
This reverts the following commits due to their basis on a bogus
linux kernel header update:

c93a656f7b65 ("tests/acceptance: Add virtiofs_submounts.py")
45ced7ca2f27 ("tests/acceptance/boot_linux: Accept SSH pubkey")
08dce386e77e ("virtiofsd: Announce sub-mount points")
eba8b096c17c ("virtiofsd: Store every lo_inode's parent_dev")
ede24b6be798 ("virtiofsd: Add fuse_reply_attr_with_flags()")
e2577435d343 ("virtiofsd: Add attr_flags to fuse_entry_param")
2f10415abfc5 ("virtiofsd: Announce FUSE_ATTR_FLAGS")
97d741cc96dd ("linux/fuse.h: Pull in from Linux")

Cc: Max Reitz <mreitz@redhat.com>
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Cc: Dr. David Alan Gilbert <dgilbert@redhat.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
---

The following pull request, based on legitimate v5.10-rc1 kernel
headers, applies cleanly after this revert:

https://lists.nongnu.org/archive/html/qemu-devel/2020-10/msg07849.html


 include/standard-headers/linux/fuse.h              |   11 -
 tests/acceptance/boot_linux.py                     |   13 -
 tests/acceptance/virtiofs_submounts.py             |  289 --------------------
 .../virtiofs_submounts.py.data/cleanup.sh          |   46 ---
 .../virtiofs_submounts.py.data/guest-cleanup.sh    |   30 --
 .../acceptance/virtiofs_submounts.py.data/guest.sh |  138 ----------
 .../acceptance/virtiofs_submounts.py.data/host.sh  |  127 ---------
 tools/virtiofsd/fuse_common.h                      |    8 -
 tools/virtiofsd/fuse_lowlevel.c                    |   34 --
 tools/virtiofsd/fuse_lowlevel.h                    |   20 -
 tools/virtiofsd/helper.c                           |    1 
 tools/virtiofsd/passthrough_ll.c                   |   84 +-----
 12 files changed, 21 insertions(+), 780 deletions(-)
 delete mode 100644 tests/acceptance/virtiofs_submounts.py
 delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
 delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
 delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest.sh
 delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/host.sh

Comments

Stefan Hajnoczi Oct. 28, 2020, 7:55 a.m. UTC | #1
On Tue, Oct 27, 2020 at 08:13:28PM -0600, Alex Williamson wrote:
> This reverts the following commits due to their basis on a bogus
> linux kernel header update:

What was the issue? Did the sub-mounts series include kernel header
changes that haven't landed in Linux yet?
Max Reitz Oct. 28, 2020, 9:39 a.m. UTC | #2
On 28.10.20 08:55, Stefan Hajnoczi wrote:
> On Tue, Oct 27, 2020 at 08:13:28PM -0600, Alex Williamson wrote:
>> This reverts the following commits due to their basis on a bogus
>> linux kernel header update:
> 
> What was the issue? Did the sub-mounts series include kernel header
> changes that haven't landed in Linux yet?

Yes.

The kernel series was applied, however, Miklos fixed some things in the
process, and then Dave applied the virtiofsd series assuming that it
would be OK now that the kernel series is in.

So what could have gone better is e.g.:

(1) I could have marked the virtiofsd series as RFC until I know the
definitive state of the kernel series.

(2) I could have given an explicit NAK to the virtiofsd series once I
noted that I need to write a v2, and not bury that somewhere deep in the
kernel side thread (https://marc.info/?l=fuse-devel&m=160087601428180).

(3) I should have noticed Dave applying the virtiofsd series (Monday
evening) and intervened.  But my inbox full from PTO plus trying to
perhaps get the FUSE export series done for 5.2 led to me just not noticing.

Max
Dr. David Alan Gilbert Oct. 28, 2020, 9:40 a.m. UTC | #3
* Alex Williamson (alex.williamson@redhat.com) wrote:
> This reverts the following commits due to their basis on a bogus
> linux kernel header update:
> 
> c93a656f7b65 ("tests/acceptance: Add virtiofs_submounts.py")
> 45ced7ca2f27 ("tests/acceptance/boot_linux: Accept SSH pubkey")
> 08dce386e77e ("virtiofsd: Announce sub-mount points")
> eba8b096c17c ("virtiofsd: Store every lo_inode's parent_dev")
> ede24b6be798 ("virtiofsd: Add fuse_reply_attr_with_flags()")
> e2577435d343 ("virtiofsd: Add attr_flags to fuse_entry_param")
> 2f10415abfc5 ("virtiofsd: Announce FUSE_ATTR_FLAGS")
> 97d741cc96dd ("linux/fuse.h: Pull in from Linux")
> 
> Cc: Max Reitz <mreitz@redhat.com>
> Cc: Stefan Hajnoczi <stefanha@redhat.com>
> Cc: Dr. David Alan Gilbert <dgilbert@redhat.com>
> Signed-off-by: Alex Williamson <alex.williamson@redhat.com>

Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>

Peter: Can you take this directly please

> ---
> 
> The following pull request, based on legitimate v5.10-rc1 kernel
> headers, applies cleanly after this revert:
> 
> https://lists.nongnu.org/archive/html/qemu-devel/2020-10/msg07849.html
> 
> 
>  include/standard-headers/linux/fuse.h              |   11 -
>  tests/acceptance/boot_linux.py                     |   13 -
>  tests/acceptance/virtiofs_submounts.py             |  289 --------------------
>  .../virtiofs_submounts.py.data/cleanup.sh          |   46 ---
>  .../virtiofs_submounts.py.data/guest-cleanup.sh    |   30 --
>  .../acceptance/virtiofs_submounts.py.data/guest.sh |  138 ----------
>  .../acceptance/virtiofs_submounts.py.data/host.sh  |  127 ---------
>  tools/virtiofsd/fuse_common.h                      |    8 -
>  tools/virtiofsd/fuse_lowlevel.c                    |   34 --
>  tools/virtiofsd/fuse_lowlevel.h                    |   20 -
>  tools/virtiofsd/helper.c                           |    1 
>  tools/virtiofsd/passthrough_ll.c                   |   84 +-----
>  12 files changed, 21 insertions(+), 780 deletions(-)
>  delete mode 100644 tests/acceptance/virtiofs_submounts.py
>  delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
>  delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
>  delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest.sh
>  delete mode 100644 tests/acceptance/virtiofs_submounts.py.data/host.sh
> 
> diff --git a/include/standard-headers/linux/fuse.h b/include/standard-headers/linux/fuse.h
> index 7dd7a3b992c8..f4df0a40f604 100644
> --- a/include/standard-headers/linux/fuse.h
> +++ b/include/standard-headers/linux/fuse.h
> @@ -227,7 +227,7 @@ struct fuse_attr {
>  	uint32_t	gid;
>  	uint32_t	rdev;
>  	uint32_t	blksize;
> -	uint32_t	flags;
> +	uint32_t	padding;
>  };
>  
>  struct fuse_kstatfs {
> @@ -310,7 +310,6 @@ struct fuse_file_lock {
>   * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir
>   * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request
>   * FUSE_MAP_ALIGNMENT: map_alignment field is valid
> - * FUSE_ATTR_FLAGS: fuse_attr.flags is present and valid
>   */
>  #define FUSE_ASYNC_READ		(1 << 0)
>  #define FUSE_POSIX_LOCKS	(1 << 1)
> @@ -339,7 +338,6 @@ struct fuse_file_lock {
>  #define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
>  #define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
>  #define FUSE_MAP_ALIGNMENT	(1 << 26)
> -#define FUSE_ATTR_FLAGS		(1 << 27)
>  
>  /**
>   * CUSE INIT request/reply flags
> @@ -415,13 +413,6 @@ struct fuse_file_lock {
>   */
>  #define FUSE_FSYNC_FDATASYNC	(1 << 0)
>  
> -/**
> - * fuse_attr flags
> - *
> - * FUSE_ATTR_SUBMOUNT: File/directory is a submount point
> - */
> -#define FUSE_ATTR_SUBMOUNT      (1 << 0)
> -
>  enum fuse_opcode {
>  	FUSE_LOOKUP		= 1,
>  	FUSE_FORGET		= 2,  /* no reply */
> diff --git a/tests/acceptance/boot_linux.py b/tests/acceptance/boot_linux.py
> index ad997c3f2eed..0055dc7cee77 100644
> --- a/tests/acceptance/boot_linux.py
> +++ b/tests/acceptance/boot_linux.py
> @@ -57,7 +57,7 @@ class BootLinuxBase(Test):
>              self.cancel('Failed to download/prepare boot image')
>          return boot.path
>  
> -    def download_cloudinit(self, ssh_pubkey=None):
> +    def download_cloudinit(self):
>          self.log.info('Preparing cloudinit image')
>          try:
>              cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
> @@ -67,8 +67,7 @@ class BootLinuxBase(Test):
>                            password='password',
>                            # QEMU's hard coded usermode router address
>                            phone_home_host='10.0.2.2',
> -                          phone_home_port=self.phone_home_port,
> -                          authorized_key=ssh_pubkey)
> +                          phone_home_port=self.phone_home_port)
>          except Exception:
>              self.cancel('Failed to prepared cloudinit image')
>          return cloudinit_iso
> @@ -81,19 +80,19 @@ class BootLinux(BootLinuxBase):
>      timeout = 900
>      chksum = None
>  
> -    def setUp(self, ssh_pubkey=None):
> +    def setUp(self):
>          super(BootLinux, self).setUp()
>          self.vm.add_args('-smp', '2')
>          self.vm.add_args('-m', '1024')
>          self.prepare_boot()
> -        self.prepare_cloudinit(ssh_pubkey)
> +        self.prepare_cloudinit()
>  
>      def prepare_boot(self):
>          path = self.download_boot()
>          self.vm.add_args('-drive', 'file=%s' % path)
>  
> -    def prepare_cloudinit(self, ssh_pubkey=None):
> -        cloudinit_iso = self.download_cloudinit(ssh_pubkey)
> +    def prepare_cloudinit(self):
> +        cloudinit_iso = self.download_cloudinit()
>          self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
>  
>      def launch_and_wait(self):
> diff --git a/tests/acceptance/virtiofs_submounts.py b/tests/acceptance/virtiofs_submounts.py
> deleted file mode 100644
> index 8b207b3e577f..000000000000
> --- a/tests/acceptance/virtiofs_submounts.py
> +++ /dev/null
> @@ -1,289 +0,0 @@
> -import logging
> -import re
> -import os
> -import subprocess
> -import time
> -
> -from avocado import skipUnless
> -from avocado_qemu import Test, BUILD_DIR
> -from avocado_qemu import wait_for_console_pattern
> -from avocado.utils import ssh
> -
> -from qemu.accel import kvm_available
> -
> -from boot_linux import BootLinux
> -
> -
> -def run_cmd(args):
> -    subp = subprocess.Popen(args,
> -                            stdout=subprocess.PIPE,
> -                            stderr=subprocess.PIPE,
> -                            universal_newlines=True)
> -    stdout, stderr = subp.communicate()
> -    ret = subp.returncode
> -
> -    return (stdout, stderr, ret)
> -
> -def has_passwordless_sudo():
> -    """
> -    This function is for use in a @avocado.skipUnless decorator, e.g.:
> -
> -        @skipUnless(*has_passwordless_sudo())
> -        def test_something_that_needs_sudo(self):
> -            ...
> -    """
> -
> -    _, stderr, exitcode = run_cmd(('sudo', '-n', 'true'))
> -    if exitcode != 0:
> -        return (False, f'Failed to use sudo -n: {stderr.strip()}')
> -    else:
> -        return (True, '')
> -
> -
> -class VirtiofsSubmountsTest(BootLinux):
> -    """
> -    :avocado: tags=arch:x86_64
> -    """
> -
> -    def get_portfwd(self):
> -        port = None
> -
> -        res = self.vm.command('human-monitor-command',
> -                              command_line='info usernet')
> -        for line in res.split('\r\n'):
> -            match = \
> -                re.search(r'TCP.HOST_FORWARD.*127\.0\.0\.1\s*(\d+)\s+10\.',
> -                          line)
> -            if match is not None:
> -                port = match[1]
> -                break
> -
> -        self.assertIsNotNone(port)
> -        self.log.debug('sshd listening on port: ' + port)
> -        return port
> -
> -    def ssh_connect(self, username, keyfile):
> -        self.ssh_logger = logging.getLogger('ssh')
> -        port = self.get_portfwd()
> -        self.ssh_session = ssh.Session('127.0.0.1', port=int(port),
> -                                       user=username, key=keyfile)
> -        for i in range(10):
> -            try:
> -                self.ssh_session.connect()
> -                return
> -            except:
> -                time.sleep(4)
> -                pass
> -        self.fail('sshd timeout')
> -
> -    def ssh_command(self, command):
> -        self.ssh_logger.info(command)
> -        result = self.ssh_session.cmd(command)
> -        stdout_lines = [line.rstrip() for line
> -                        in result.stdout_text.splitlines()]
> -        for line in stdout_lines:
> -            self.ssh_logger.info(line)
> -        stderr_lines = [line.rstrip() for line
> -                        in result.stderr_text.splitlines()]
> -        for line in stderr_lines:
> -            self.ssh_logger.warning(line)
> -
> -        self.assertEqual(result.exit_status, 0,
> -                         f'Guest command failed: {command}')
> -        return stdout_lines, stderr_lines
> -
> -    def run(self, args, ignore_error=False):
> -        stdout, stderr, ret = run_cmd(args)
> -
> -        if ret != 0:
> -            cmdline = ' '.join(args)
> -            if not ignore_error:
> -                self.fail(f'{cmdline}: Returned {ret}: {stderr}')
> -            else:
> -                self.log.warn(f'{cmdline}: Returned {ret}: {stderr}')
> -
> -        return (stdout, stderr, ret)
> -
> -    def set_up_shared_dir(self):
> -        atwd = os.getenv('AVOCADO_TEST_WORKDIR')
> -        self.shared_dir = os.path.join(atwd, 'virtiofs-shared')
> -
> -        os.mkdir(self.shared_dir)
> -
> -        self.run(('cp', self.get_data('guest.sh'),
> -                 os.path.join(self.shared_dir, 'check.sh')))
> -
> -        self.run(('cp', self.get_data('guest-cleanup.sh'),
> -                 os.path.join(self.shared_dir, 'cleanup.sh')))
> -
> -    def set_up_virtiofs(self):
> -        attmp = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR')
> -        self.vfsdsock = os.path.join(attmp, 'vfsdsock')
> -
> -        self.run(('sudo', '-n', 'rm', '-f', self.vfsdsock), ignore_error=True)
> -
> -        self.virtiofsd = \
> -            subprocess.Popen(('sudo', '-n',
> -                              'tools/virtiofsd/virtiofsd',
> -                              f'--socket-path={self.vfsdsock}',
> -                              '-o', f'source={self.shared_dir}',
> -                              '-o', 'cache=always',
> -                              '-o', 'xattr',
> -                              '-o', 'announce_submounts',
> -                              '-f'),
> -                             stdout=subprocess.DEVNULL,
> -                             stderr=subprocess.PIPE,
> -                             universal_newlines=True)
> -
> -        while not os.path.exists(self.vfsdsock):
> -            if self.virtiofsd.poll() is not None:
> -                self.fail('virtiofsd exited prematurely: ' +
> -                          self.virtiofsd.communicate()[1])
> -            time.sleep(0.1)
> -
> -        self.run(('sudo', '-n', 'chmod', 'go+rw', self.vfsdsock))
> -
> -        self.vm.add_args('-chardev',
> -                         f'socket,id=vfsdsock,path={self.vfsdsock}',
> -                         '-device',
> -                         'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \
> -                             ',tag=host',
> -                         '-object',
> -                         'memory-backend-file,id=mem,size=1G,' \
> -                             'mem-path=/dev/shm,share=on',
> -                         '-numa',
> -                         'node,memdev=mem')
> -
> -    def launch_vm(self):
> -        self.launch_and_wait()
> -        self.ssh_connect('root', self.ssh_key)
> -
> -    def set_up_nested_mounts(self):
> -        scratch_dir = os.path.join(self.shared_dir, 'scratch')
> -        try:
> -            os.mkdir(scratch_dir)
> -        except FileExistsError:
> -            pass
> -
> -        args = ['bash', self.get_data('host.sh'), scratch_dir]
> -        if self.seed:
> -            args += [self.seed]
> -
> -        out, _, _ = self.run(args)
> -        seed = re.search(r'^Seed: \d+', out)
> -        self.log.info(seed[0])
> -
> -    def mount_in_guest(self):
> -        self.ssh_command('mkdir -p /mnt/host')
> -        self.ssh_command('mount -t virtiofs host /mnt/host')
> -
> -    def check_in_guest(self):
> -        self.ssh_command('bash /mnt/host/check.sh /mnt/host/scratch/share')
> -
> -    def live_cleanup(self):
> -        self.ssh_command('bash /mnt/host/cleanup.sh /mnt/host/scratch')
> -
> -        # It would be nice if the above was sufficient to make virtiofsd clear
> -        # all references to the mounted directories (so they can be unmounted
> -        # on the host), but unfortunately it is not.  To do so, we have to
> -        # resort to a remount.
> -        self.ssh_command('mount -o remount /mnt/host')
> -
> -        scratch_dir = os.path.join(self.shared_dir, 'scratch')
> -        self.run(('bash', self.get_data('cleanup.sh'), scratch_dir))
> -
> -    @skipUnless(*has_passwordless_sudo())
> -    def setUp(self):
> -        vmlinuz = self.params.get('vmlinuz')
> -        if vmlinuz is None:
> -            self.cancel('vmlinuz parameter not set; you must point it to a '
> -                        'Linux kernel binary to test (to run this test with ' \
> -                        'the on-image kernel, set it to an empty string)')
> -
> -        self.seed = self.params.get('seed')
> -
> -        atwd = os.getenv('AVOCADO_TEST_WORKDIR')
> -        self.ssh_key = os.path.join(atwd, 'id_ed25519')
> -
> -        self.run(('ssh-keygen', '-t', 'ed25519', '-f', self.ssh_key))
> -
> -        pubkey = open(self.ssh_key + '.pub').read()
> -
> -        super(VirtiofsSubmountsTest, self).setUp(pubkey)
> -
> -        if len(vmlinuz) > 0:
> -            self.vm.add_args('-kernel', vmlinuz,
> -                             '-append', 'console=ttyS0 root=/dev/sda1')
> -
> -        # Allow us to connect to SSH
> -        self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22',
> -                         '-device', 'e1000,netdev=vnet')
> -
> -        if not kvm_available(self.arch, self.qemu_bin):
> -            self.cancel(KVM_NOT_AVAILABLE)
> -        self.vm.add_args('-accel', 'kvm')
> -
> -    def tearDown(self):
> -        try:
> -            self.vm.shutdown()
> -        except:
> -            pass
> -
> -        scratch_dir = os.path.join(self.shared_dir, 'scratch')
> -        self.run(('bash', self.get_data('cleanup.sh'), scratch_dir),
> -                 ignore_error=True)
> -
> -    def test_pre_virtiofsd_set_up(self):
> -        self.set_up_shared_dir()
> -
> -        self.set_up_nested_mounts()
> -
> -        self.set_up_virtiofs()
> -        self.launch_vm()
> -        self.mount_in_guest()
> -        self.check_in_guest()
> -
> -    def test_pre_launch_set_up(self):
> -        self.set_up_shared_dir()
> -        self.set_up_virtiofs()
> -
> -        self.set_up_nested_mounts()
> -
> -        self.launch_vm()
> -        self.mount_in_guest()
> -        self.check_in_guest()
> -
> -    def test_post_launch_set_up(self):
> -        self.set_up_shared_dir()
> -        self.set_up_virtiofs()
> -        self.launch_vm()
> -
> -        self.set_up_nested_mounts()
> -
> -        self.mount_in_guest()
> -        self.check_in_guest()
> -
> -    def test_post_mount_set_up(self):
> -        self.set_up_shared_dir()
> -        self.set_up_virtiofs()
> -        self.launch_vm()
> -        self.mount_in_guest()
> -
> -        self.set_up_nested_mounts()
> -
> -        self.check_in_guest()
> -
> -    def test_two_runs(self):
> -        self.set_up_shared_dir()
> -
> -        self.set_up_nested_mounts()
> -
> -        self.set_up_virtiofs()
> -        self.launch_vm()
> -        self.mount_in_guest()
> -        self.check_in_guest()
> -
> -        self.live_cleanup()
> -        self.set_up_nested_mounts()
> -
> -        self.check_in_guest()
> diff --git a/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
> deleted file mode 100644
> index 2a6579a0fea6..000000000000
> --- a/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
> +++ /dev/null
> @@ -1,46 +0,0 @@
> -#!/bin/bash
> -
> -function print_usage()
> -{
> -    if [ -n "$2" ]; then
> -        echo "Error: $2"
> -        echo
> -    fi
> -    echo "Usage: $1 <scratch dir>"
> -}
> -
> -scratch_dir=$1
> -if [ -z "$scratch_dir" ]; then
> -    print_usage "$0" 'Scratch dir not given' >&2
> -    exit 1
> -fi
> -
> -cd "$scratch_dir/share" || exit 1
> -mps=(mnt*)
> -mp_i=0
> -for mp in "${mps[@]}"; do
> -    mp_i=$((mp_i + 1))
> -    printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}"
> -
> -    sudo umount -R "$mp"
> -    rm -rf "$mp"
> -done
> -echo
> -
> -rm some-file
> -cd ..
> -rmdir share
> -
> -imgs=(fs*.img)
> -img_i=0
> -for img in "${imgs[@]}"; do
> -    img_i=$((img_i + 1))
> -    printf "Detaching and deleting %i/%i...\r" "$img_i" "${#imgs[@]}"
> -
> -    dev=$(losetup -j "$img" | sed -e 's/:.*//')
> -    sudo losetup -d "$dev"
> -    rm -f "$img"
> -done
> -echo
> -
> -echo 'Done.'
> diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
> deleted file mode 100644
> index 729cb2d1a5ec..000000000000
> --- a/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
> +++ /dev/null
> @@ -1,30 +0,0 @@
> -#!/bin/bash
> -
> -function print_usage()
> -{
> -    if [ -n "$2" ]; then
> -        echo "Error: $2"
> -        echo
> -    fi
> -    echo "Usage: $1 <scratch dir>"
> -}
> -
> -scratch_dir=$1
> -if [ -z "$scratch_dir" ]; then
> -    print_usage "$0" 'Scratch dir not given' >&2
> -    exit 1
> -fi
> -
> -cd "$scratch_dir/share" || exit 1
> -
> -mps=(mnt*)
> -mp_i=0
> -for mp in "${mps[@]}"; do
> -    mp_i=$((mp_i + 1))
> -    printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}"
> -
> -    sudo umount -R "$mp"
> -done
> -echo
> -
> -echo 'Done.'
> diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest.sh b/tests/acceptance/virtiofs_submounts.py.data/guest.sh
> deleted file mode 100644
> index 59ba40fde179..000000000000
> --- a/tests/acceptance/virtiofs_submounts.py.data/guest.sh
> +++ /dev/null
> @@ -1,138 +0,0 @@
> -#!/bin/bash
> -
> -function print_usage()
> -{
> -    if [ -n "$2" ]; then
> -        echo "Error: $2"
> -        echo
> -    fi
> -    echo "Usage: $1 <shared dir>"
> -    echo '(The shared directory is the "share" directory in the scratch' \
> -         'directory)'
> -}
> -
> -shared_dir=$1
> -if [ -z "$shared_dir" ]; then
> -    print_usage "$0" 'Shared dir not given' >&2
> -    exit 1
> -fi
> -
> -cd "$shared_dir"
> -
> -# FIXME: This should not be necessary, but it is.  In order for all
> -# submounts to be proper mount points, we need to visit them.
> -# (Before we visit them, they will not be auto-mounted, and so just
> -# appear as normal directories, with the catch that their st_ino will
> -# be the st_ino of the filesystem they host, while the st_dev will
> -# still be the st_dev of the parent.)
> -# `find` does not work, because it will refuse to touch the mount
> -# points as long as they are not mounted; their st_dev being shared
> -# with the parent and st_ino just being the root node's inode ID
> -# will practically ensure that this node exists elsewhere on the
> -# filesystem, and `find` is required to recognize loops and not to
> -# follow them.
> -# Thus, we have to manually visit all nodes first.
> -
> -mnt_i=0
> -
> -function recursively_visit()
> -{
> -    pushd "$1" >/dev/null
> -    for entry in *; do
> -        if [[ "$entry" == mnt* ]]; then
> -            mnt_i=$((mnt_i + 1))
> -            printf "Triggering auto-mount $mnt_i...\r"
> -        fi
> -
> -        if [ -d "$entry" ]; then
> -            recursively_visit "$entry"
> -        fi
> -    done
> -    popd >/dev/null
> -}
> -
> -recursively_visit .
> -echo
> -
> -
> -if [ -n "$(find -name not-mounted)" ]; then
> -    echo "Error: not-mounted files visible on mount points:" >&2
> -    find -name not-mounted >&2
> -    exit 1
> -fi
> -
> -if [ ! -f some-file -o "$(cat some-file)" != 'root' ]; then
> -    echo "Error: Bad file in the share root" >&2
> -    exit 1
> -fi
> -
> -shopt -s nullglob
> -
> -function check_submounts()
> -{
> -    local base_path=$1
> -
> -    for mp in mnt*; do
> -        printf "Checking submount %i...\r" "$((${#devs[@]} + 1))"
> -
> -        mp_i=$(echo "$mp" | sed -e 's/mnt//')
> -        dev=$(stat -c '%D' "$mp")
> -
> -        if [ -n "${devs[mp_i]}" ]; then
> -            echo "Error: $mp encountered twice" >&2
> -            exit 1
> -        fi
> -        devs[mp_i]=$dev
> -
> -        pushd "$mp" >/dev/null
> -        path="$base_path$mp"
> -        while true; do
> -            expected_content="$(printf '%s\n%s\n' "$mp_i" "$path")"
> -            if [ ! -f some-file ]; then
> -                echo "Error: $PWD/some-file does not exist" >&2
> -                exit 1
> -            fi
> -
> -            if [ "$(cat some-file)" != "$expected_content" ]; then
> -                echo "Error: Bad content in $PWD/some-file:" >&2
> -                echo '--- found ---'
> -                cat some-file
> -                echo '--- expected ---'
> -                echo "$expected_content"
> -                exit 1
> -            fi
> -            if [ "$(stat -c '%D' some-file)" != "$dev" ]; then
> -                echo "Error: $PWD/some-file has the wrong device ID" >&2
> -                exit 1
> -            fi
> -
> -            if [ -d sub ]; then
> -                if [ "$(stat -c '%D' sub)" != "$dev" ]; then
> -                    echo "Error: $PWD/some-file has the wrong device ID" >&2
> -                    exit 1
> -                fi
> -                cd sub
> -                path="$path/sub"
> -            else
> -                if [ -n "$(echo mnt*)" ]; then
> -                    check_submounts "$path/"
> -                fi
> -                break
> -            fi
> -        done
> -        popd >/dev/null
> -    done
> -}
> -
> -root_dev=$(stat -c '%D' some-file)
> -devs=()
> -check_submounts ''
> -echo
> -
> -reused_devs=$(echo "$root_dev ${devs[@]}" | tr ' ' '\n' | sort | uniq -d)
> -if [ -n "$reused_devs" ]; then
> -    echo "Error: Reused device IDs: $reused_devs" >&2
> -    exit 1
> -fi
> -
> -echo "Test passed for ${#devs[@]} submounts."
> diff --git a/tests/acceptance/virtiofs_submounts.py.data/host.sh b/tests/acceptance/virtiofs_submounts.py.data/host.sh
> deleted file mode 100644
> index d8a9afebdb54..000000000000
> --- a/tests/acceptance/virtiofs_submounts.py.data/host.sh
> +++ /dev/null
> @@ -1,127 +0,0 @@
> -#!/bin/bash
> -
> -mount_count=128
> -
> -function print_usage()
> -{
> -    if [ -n "$2" ]; then
> -        echo "Error: $2"
> -        echo
> -    fi
> -    echo "Usage: $1 <scratch dir> [seed]"
> -    echo "(If no seed is given, it will be randomly generated.)"
> -}
> -
> -scratch_dir=$1
> -if [ -z "$scratch_dir" ]; then
> -    print_usage "$0" 'No scratch dir given' >&2
> -    exit 1
> -fi
> -
> -if [ ! -d "$scratch_dir" ]; then
> -    print_usage "$0" "$scratch_dir is not a directory" >&2
> -    exit 1
> -fi
> -
> -seed=$2
> -if [ -z "$seed" ]; then
> -    seed=$RANDOM
> -fi
> -RANDOM=$seed
> -
> -echo "Seed: $seed"
> -
> -set -e
> -shopt -s nullglob
> -
> -cd "$scratch_dir"
> -if [ -d share ]; then
> -    echo 'Error: This directory seems to be in use already' >&2
> -    exit 1
> -fi
> -
> -for ((i = 0; i < $mount_count; i++)); do
> -    printf "Setting up fs %i/%i...\r" "$((i + 1))" "$mount_count"
> -
> -    rm -f fs$i.img
> -    truncate -s 512M fs$i.img
> -    mkfs.xfs -q fs$i.img
> -    devs[i]=$(sudo losetup -f --show fs$i.img)
> -done
> -echo
> -
> -top_level_mounts=$((RANDOM % mount_count + 1))
> -
> -mkdir -p share
> -echo 'root' > share/some-file
> -
> -for ((i = 0; i < $top_level_mounts; i++)); do
> -    printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count"
> -
> -    mkdir -p share/mnt$i
> -    touch share/mnt$i/not-mounted
> -    sudo mount "${devs[i]}" share/mnt$i
> -    sudo chown "$(id -u):$(id -g)" share/mnt$i
> -
> -    pushd share/mnt$i >/dev/null
> -    path=mnt$i
> -    nesting=$((RANDOM % 4))
> -    for ((j = 0; j < $nesting; j++)); do
> -        cat > some-file <<EOF
> -$i
> -$path
> -EOF
> -        mkdir sub
> -        cd sub
> -        path="$path/sub"
> -    done
> -cat > some-file <<EOF
> -$i
> -$path
> -EOF
> -    popd >/dev/null
> -done
> -
> -for ((; i < $mount_count; i++)); do
> -    printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count"
> -
> -    mp_i=$((i % top_level_mounts))
> -
> -    pushd share/mnt$mp_i >/dev/null
> -    path=mnt$mp_i
> -    while true; do
> -        sub_mp="$(echo mnt*)"
> -        if cd sub 2>/dev/null; then
> -            path="$path/sub"
> -        elif [ -n "$sub_mp" ] && cd "$sub_mp" 2>/dev/null; then
> -            path="$path/$sub_mp"
> -        else
> -            break
> -        fi
> -    done
> -    mkdir mnt$i
> -    touch mnt$i/not-mounted
> -    sudo mount "${devs[i]}" mnt$i
> -    sudo chown "$(id -u):$(id -g)" mnt$i
> -
> -    cd mnt$i
> -    path="$path/mnt$i"
> -    nesting=$((RANDOM % 4))
> -    for ((j = 0; j < $nesting; j++)); do
> -        cat > some-file <<EOF
> -$i
> -$path
> -EOF
> -        mkdir sub
> -        cd sub
> -        path="$path/sub"
> -    done
> -    cat > some-file <<EOF
> -$i
> -$path
> -EOF
> -    popd >/dev/null
> -done
> -echo
> -
> -echo 'Done.'
> diff --git a/tools/virtiofsd/fuse_common.h b/tools/virtiofsd/fuse_common.h
> index 870544fe132f..686c42c0a528 100644
> --- a/tools/virtiofsd/fuse_common.h
> +++ b/tools/virtiofsd/fuse_common.h
> @@ -352,14 +352,6 @@ struct fuse_file_info {
>   */
>  #define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
>  
> -/**
> - * Indicates that the client will provide fuse_attr.flags, and the kernel will
> - * interpret it.
> - *
> - * This feature is enabled by default when supported by the kernel.
> - */
> -#define FUSE_CAP_ATTR_FLAGS (1 << 27)
> -
>  /**
>   * Ioctl flags
>   *
> diff --git a/tools/virtiofsd/fuse_lowlevel.c b/tools/virtiofsd/fuse_lowlevel.c
> index 5198f627bcb3..4d1ba2925d1b 100644
> --- a/tools/virtiofsd/fuse_lowlevel.c
> +++ b/tools/virtiofsd/fuse_lowlevel.c
> @@ -329,8 +329,7 @@ static unsigned int calc_timeout_nsec(double t)
>      }
>  }
>  
> -static void fill_entry(struct fuse_session *se,
> -                       struct fuse_entry_out *arg,
> +static void fill_entry(struct fuse_entry_out *arg,
>                         const struct fuse_entry_param *e)
>  {
>      *arg = (struct fuse_entry_out){
> @@ -342,10 +341,6 @@ static void fill_entry(struct fuse_session *se,
>          .attr_valid_nsec = calc_timeout_nsec(e->attr_timeout),
>      };
>      convert_stat(&e->attr, &arg->attr);
> -
> -    if (se->conn.capable & FUSE_CAP_ATTR_FLAGS) {
> -        arg->attr.flags = e->attr_flags;
> -    }
>  }
>  
>  /*
> @@ -370,7 +365,7 @@ size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
>  
>      struct fuse_direntplus *dp = (struct fuse_direntplus *)buf;
>      memset(&dp->entry_out, 0, sizeof(dp->entry_out));
> -    fill_entry(req->se, &dp->entry_out, e);
> +    fill_entry(&dp->entry_out, e);
>  
>      struct fuse_dirent *dirent = &dp->dirent;
>      *dirent = (struct fuse_dirent){
> @@ -408,7 +403,7 @@ int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
>      size_t size = sizeof(arg);
>  
>      memset(&arg, 0, sizeof(arg));
> -    fill_entry(req->se, &arg, e);
> +    fill_entry(&arg, e);
>      return send_reply_ok(req, &arg, size);
>  }
>  
> @@ -421,13 +416,13 @@ int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
>      struct fuse_open_out *oarg = (struct fuse_open_out *)(buf + entrysize);
>  
>      memset(buf, 0, sizeof(buf));
> -    fill_entry(req->se, earg, e);
> +    fill_entry(earg, e);
>      fill_open(oarg, f);
>      return send_reply_ok(req, buf, entrysize + sizeof(struct fuse_open_out));
>  }
>  
> -int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
> -                               double attr_timeout, uint32_t attr_flags)
> +int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
> +                    double attr_timeout)
>  {
>      struct fuse_attr_out arg;
>      size_t size = sizeof(arg);
> @@ -437,19 +432,9 @@ int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
>      arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
>      convert_stat(attr, &arg.attr);
>  
> -    if (req->se->conn.capable & FUSE_CAP_ATTR_FLAGS) {
> -        arg.attr.flags = attr_flags;
> -    }
> -
>      return send_reply_ok(req, &arg, size);
>  }
>  
> -int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
> -                    double attr_timeout)
> -{
> -    return fuse_reply_attr_with_flags(req, attr, attr_timeout, 0);
> -}
> -
>  int fuse_reply_readlink(fuse_req_t req, const char *linkname)
>  {
>      return send_reply_ok(req, linkname, strlen(linkname));
> @@ -2003,9 +1988,6 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid,
>              bufsize = max_bufsize;
>          }
>      }
> -    if (arg->flags & FUSE_ATTR_FLAGS) {
> -        se->conn.capable |= FUSE_CAP_ATTR_FLAGS;
> -    }
>  #ifdef HAVE_SPLICE
>  #ifdef HAVE_VMSPLICE
>      se->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
> @@ -2032,7 +2014,6 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid,
>      LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
>      LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
>      LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
> -    LL_SET_DEFAULT(1, FUSE_CAP_ATTR_FLAGS);
>      LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
>      LL_SET_DEFAULT(se->op.getlk && se->op.setlk, FUSE_CAP_POSIX_LOCKS);
>      LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
> @@ -2122,9 +2103,6 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid,
>      if (se->conn.want & FUSE_CAP_POSIX_ACL) {
>          outarg.flags |= FUSE_POSIX_ACL;
>      }
> -    if (se->conn.want & FUSE_CAP_ATTR_FLAGS) {
> -        outarg.flags |= FUSE_ATTR_FLAGS;
> -    }
>      outarg.max_readahead = se->conn.max_readahead;
>      outarg.max_write = se->conn.max_write;
>      if (se->conn.max_background >= (1 << 16)) {
> diff --git a/tools/virtiofsd/fuse_lowlevel.h b/tools/virtiofsd/fuse_lowlevel.h
> index 1ff6ba1e4f1e..562fd5241ed4 100644
> --- a/tools/virtiofsd/fuse_lowlevel.h
> +++ b/tools/virtiofsd/fuse_lowlevel.h
> @@ -102,11 +102,6 @@ struct fuse_entry_param {
>       *  large value.
>       */
>      double entry_timeout;
> -
> -    /**
> -     * Flags for fuse_attr.flags that do not fit into attr.
> -     */
> -    uint32_t attr_flags;
>  };
>  
>  /**
> @@ -1313,21 +1308,6 @@ int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
>  int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
>                      double attr_timeout);
>  
> -/**
> - * Reply with attributes and set fuse_attr.flags
> - *
> - * Possible requests:
> - *   getattr, setattr
> - *
> - * @param req request handle
> - * @param attr the attributes
> - * @param attr_timeout validity timeout (in seconds) for the attributes
> - * @param attr_flags flags to put into fuse_attr.flags
> - * @return zero for success, -errno for failure to send reply
> - */
> -int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
> -                               double attr_timeout, uint32_t attr_flags);
> -
>  /**
>   * Reply with the contents of a symbolic link
>   *
> diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c
> index a212f329317b..2e181a49b5ff 100644
> --- a/tools/virtiofsd/helper.c
> +++ b/tools/virtiofsd/helper.c
> @@ -178,7 +178,6 @@ void fuse_cmdline_help(void)
>             "                               default: depends on cache= option.\n"
>             "    -o writeback|no_writeback  enable/disable writeback cache\n"
>             "                               default: no_writeback\n"
> -           "    -o announce_submounts      Announce sub-mount points to the guest\n"
>             "    -o xattr|no_xattr          enable/disable xattr\n"
>             "                               default: no_xattr\n"
>             "    -o modcaps=CAPLIST         Modify the list of capabilities\n"
> diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
> index 4db50046d49f..a0beb986f36a 100644
> --- a/tools/virtiofsd/passthrough_ll.c
> +++ b/tools/virtiofsd/passthrough_ll.c
> @@ -40,7 +40,6 @@
>  #include "fuse_virtio.h"
>  #include "fuse_log.h"
>  #include "fuse_lowlevel.h"
> -#include "standard-headers/linux/fuse.h"
>  #include <assert.h>
>  #include <cap-ng.h>
>  #include <dirent.h>
> @@ -126,14 +125,6 @@ struct lo_inode {
>      GHashTable *posix_locks; /* protected by lo_inode->plock_mutex */
>  
>      mode_t filetype;
> -
> -    /*
> -     * So we can detect crossmount roots
> -     * (As such, this only needs to be valid for directories.  Note
> -     * that files can have multiple parents due to hard links, and so
> -     * their parent_dev may fluctuate.)
> -     */
> -    dev_t parent_dev;
>  };
>  
>  struct lo_cred {
> @@ -174,7 +165,6 @@ struct lo_data {
>      int timeout_set;
>      int readdirplus_set;
>      int readdirplus_clear;
> -    int announce_submounts;
>      int allow_direct_io;
>      struct lo_inode root;
>      GHashTable *inodes; /* protected by lo->mutex */
> @@ -213,7 +203,6 @@ static const struct fuse_opt lo_opts[] = {
>      { "cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS },
>      { "readdirplus", offsetof(struct lo_data, readdirplus_set), 1 },
>      { "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 },
> -    { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 },
>      { "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
>      { "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
>      FUSE_OPT_END
> @@ -611,52 +600,22 @@ static void lo_init(void *userdata, struct fuse_conn_info *conn)
>      }
>  }
>  
> -/**
> - * Call fstatat() and set st_rdev whenever a directory's st_dev
> - * differs from the rparent's st_dev (@parent_dev).  This will
> - * announce submounts to the FUSE client (unless @announce_submounts
> - * is false).
> - */
> -static int do_fstatat(int dirfd, const char *pathname, struct stat *statbuf,
> -                      int flags, dev_t parent_dev, uint32_t *fuse_attr_flags)
> -{
> -    int res = fstatat(dirfd, pathname, statbuf, flags);
> -    if (res == -1) {
> -        return res;
> -    }
> -
> -    if (statbuf->st_dev != parent_dev && S_ISDIR(statbuf->st_mode) &&
> -        fuse_attr_flags)
> -    {
> -        *fuse_attr_flags |= FUSE_ATTR_SUBMOUNT;
> -    }
> -
> -    return 0;
> -}
> -
>  static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
>                         struct fuse_file_info *fi)
>  {
>      int res;
>      struct stat buf;
>      struct lo_data *lo = lo_data(req);
> -    struct lo_inode *inode = lo_inode(req, ino);
> -    uint32_t fuse_attr_flags = 0;
>  
>      (void)fi;
>  
> -    res = do_fstatat(inode->fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
> -                     inode->parent_dev, &fuse_attr_flags);
> -    lo_inode_put(lo, &inode);
> +    res =
> +        fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
>      if (res == -1) {
>          return (void)fuse_reply_err(req, errno);
>      }
>  
> -    if (!lo->announce_submounts) {
> -        fuse_attr_flags &= ~FUSE_ATTR_SUBMOUNT;
> -    }
> -
> -    fuse_reply_attr_with_flags(req, &buf, lo->timeout, fuse_attr_flags);
> +    fuse_reply_attr(req, &buf, lo->timeout);
>  }
>  
>  static int lo_fi_fd(fuse_req_t req, struct fuse_file_info *fi)
> @@ -852,16 +811,11 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
>          goto out_err;
>      }
>  
> -    res = do_fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
> -                     dir->key.dev, &e->attr_flags);
> +    res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
>      if (res == -1) {
>          goto out_err;
>      }
>  
> -    if (!lo->announce_submounts) {
> -        e->attr_flags &= ~FUSE_ATTR_SUBMOUNT;
> -    }
> -
>      inode = lo_find(lo, &e->attr);
>      if (inode) {
>          close(newfd);
> @@ -893,7 +847,6 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
>          g_hash_table_insert(lo->inodes, &inode->key, inode);
>          pthread_mutex_unlock(&lo->mutex);
>      }
> -    inode->parent_dev = dir->key.dev;
>      e->ino = inode->fuse_ino;
>      lo_inode_put(lo, &inode);
>      lo_inode_put(lo, &dir);
> @@ -1107,17 +1060,11 @@ static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
>          goto out_err;
>      }
>  
> -    res = do_fstatat(inode->fd, "", &e.attr,
> -                     AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
> -                     parent_inode->key.dev, &e.attr_flags);
> +    res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
>      if (res == -1) {
>          goto out_err;
>      }
>  
> -    if (!lo->announce_submounts) {
> -        e.attr_flags &= ~FUSE_ATTR_SUBMOUNT;
> -    }
> -
>      pthread_mutex_lock(&lo->mutex);
>      inode->nlookup++;
>      pthread_mutex_unlock(&lo->mutex);
> @@ -1126,14 +1073,6 @@ static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
>      fuse_log(FUSE_LOG_DEBUG, "  %lli/%s -> %lli\n", (unsigned long long)parent,
>               name, (unsigned long long)e.ino);
>  
> -    /*
> -     * No need to update inode->parent_dev, because
> -     * (1) We cannot, the inode now has more than one parent,
> -     * (2) Directories cannot have more than one parent, so link()
> -     *     does not work for them; but parent_dev only needs to be
> -     *     valid for directories.
> -     */
> -
>      fuse_reply_entry(req, &e);
>      lo_inode_put(lo, &parent_inode);
>      lo_inode_put(lo, &inode);
> @@ -1152,21 +1091,14 @@ static struct lo_inode *lookup_name(fuse_req_t req, fuse_ino_t parent,
>  {
>      int res;
>      struct stat attr;
> -    struct lo_data *lo = lo_data(req);
> -    struct lo_inode *dir = lo_inode(req, parent);
>  
> -    if (!dir) {
> -        return NULL;
> -    }
> -
> -    res = do_fstatat(dir->fd, name, &attr,
> -                     AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, dir->key.dev, NULL);
> -    lo_inode_put(lo, &dir);
> +    res = fstatat(lo_fd(req, parent), name, &attr,
> +                  AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
>      if (res == -1) {
>          return NULL;
>      }
>  
> -    return lo_find(lo, &attr);
> +    return lo_find(lo_data(req), &attr);
>  }
>  
>  static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
>
Max Reitz Oct. 28, 2020, 9:44 a.m. UTC | #4
On 28.10.20 03:13, Alex Williamson wrote:
> This reverts the following commits due to their basis on a bogus
> linux kernel header update:
> 
> c93a656f7b65 ("tests/acceptance: Add virtiofs_submounts.py")
> 45ced7ca2f27 ("tests/acceptance/boot_linux: Accept SSH pubkey")
> 08dce386e77e ("virtiofsd: Announce sub-mount points")
> eba8b096c17c ("virtiofsd: Store every lo_inode's parent_dev")
> ede24b6be798 ("virtiofsd: Add fuse_reply_attr_with_flags()")
> e2577435d343 ("virtiofsd: Add attr_flags to fuse_entry_param")
> 2f10415abfc5 ("virtiofsd: Announce FUSE_ATTR_FLAGS")
> 97d741cc96dd ("linux/fuse.h: Pull in from Linux")
> 
> Cc: Max Reitz <mreitz@redhat.com>
> Cc: Stefan Hajnoczi <stefanha@redhat.com>
> Cc: Dr. David Alan Gilbert <dgilbert@redhat.com>
> Signed-off-by: Alex Williamson <alex.williamson@redhat.com>

Reviewed-by: Max Reitz <mreitz@redhat.com>

Thanks again, and sorry.

Max
Dr. David Alan Gilbert Oct. 28, 2020, 9:45 a.m. UTC | #5
* Max Reitz (mreitz@redhat.com) wrote:
> On 28.10.20 08:55, Stefan Hajnoczi wrote:
> > On Tue, Oct 27, 2020 at 08:13:28PM -0600, Alex Williamson wrote:
> >> This reverts the following commits due to their basis on a bogus
> >> linux kernel header update:
> > 
> > What was the issue? Did the sub-mounts series include kernel header
> > changes that haven't landed in Linux yet?
> 
> Yes.
> 
> The kernel series was applied, however, Miklos fixed some things in the
> process, and then Dave applied the virtiofsd series assuming that it
> would be OK now that the kernel series is in.
> 
> So what could have gone better is e.g.:
> 
> (1) I could have marked the virtiofsd series as RFC until I know the
> definitive state of the kernel series.
> 
> (2) I could have given an explicit NAK to the virtiofsd series once I
> noted that I need to write a v2, and not bury that somewhere deep in the
> kernel side thread (https://marc.info/?l=fuse-devel&m=160087601428180).

Yeh that one would have been useful.
Unfortunately Miklos's changes were such that it didn't break your code;
I did give it a smoke test with 5.10-rc1 and saw that it was doing the submounts OK.

Dave

> (3) I should have noticed Dave applying the virtiofsd series (Monday
> evening) and intervened.  But my inbox full from PTO plus trying to
> perhaps get the FUSE export series done for 5.2 led to me just not noticing.
> 
> Max
>
Peter Maydell Oct. 28, 2020, 2:30 p.m. UTC | #6
On Wed, 28 Oct 2020 at 09:40, Dr. David Alan Gilbert
<dgilbert@redhat.com> wrote:
>
> * Alex Williamson (alex.williamson@redhat.com) wrote:
> > This reverts the following commits due to their basis on a bogus
> > linux kernel header update:
> >
> > c93a656f7b65 ("tests/acceptance: Add virtiofs_submounts.py")
> > 45ced7ca2f27 ("tests/acceptance/boot_linux: Accept SSH pubkey")
> > 08dce386e77e ("virtiofsd: Announce sub-mount points")
> > eba8b096c17c ("virtiofsd: Store every lo_inode's parent_dev")
> > ede24b6be798 ("virtiofsd: Add fuse_reply_attr_with_flags()")
> > e2577435d343 ("virtiofsd: Add attr_flags to fuse_entry_param")
> > 2f10415abfc5 ("virtiofsd: Announce FUSE_ATTR_FLAGS")
> > 97d741cc96dd ("linux/fuse.h: Pull in from Linux")
> >
> > Cc: Max Reitz <mreitz@redhat.com>
> > Cc: Stefan Hajnoczi <stefanha@redhat.com>
> > Cc: Dr. David Alan Gilbert <dgilbert@redhat.com>
> > Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
>
> Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
>
> Peter: Can you take this directly please

Applied to master, thanks.

-- PMM
diff mbox series

Patch

diff --git a/include/standard-headers/linux/fuse.h b/include/standard-headers/linux/fuse.h
index 7dd7a3b992c8..f4df0a40f604 100644
--- a/include/standard-headers/linux/fuse.h
+++ b/include/standard-headers/linux/fuse.h
@@ -227,7 +227,7 @@  struct fuse_attr {
 	uint32_t	gid;
 	uint32_t	rdev;
 	uint32_t	blksize;
-	uint32_t	flags;
+	uint32_t	padding;
 };
 
 struct fuse_kstatfs {
@@ -310,7 +310,6 @@  struct fuse_file_lock {
  * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir
  * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request
  * FUSE_MAP_ALIGNMENT: map_alignment field is valid
- * FUSE_ATTR_FLAGS: fuse_attr.flags is present and valid
  */
 #define FUSE_ASYNC_READ		(1 << 0)
 #define FUSE_POSIX_LOCKS	(1 << 1)
@@ -339,7 +338,6 @@  struct fuse_file_lock {
 #define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
 #define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
 #define FUSE_MAP_ALIGNMENT	(1 << 26)
-#define FUSE_ATTR_FLAGS		(1 << 27)
 
 /**
  * CUSE INIT request/reply flags
@@ -415,13 +413,6 @@  struct fuse_file_lock {
  */
 #define FUSE_FSYNC_FDATASYNC	(1 << 0)
 
-/**
- * fuse_attr flags
- *
- * FUSE_ATTR_SUBMOUNT: File/directory is a submount point
- */
-#define FUSE_ATTR_SUBMOUNT      (1 << 0)
-
 enum fuse_opcode {
 	FUSE_LOOKUP		= 1,
 	FUSE_FORGET		= 2,  /* no reply */
diff --git a/tests/acceptance/boot_linux.py b/tests/acceptance/boot_linux.py
index ad997c3f2eed..0055dc7cee77 100644
--- a/tests/acceptance/boot_linux.py
+++ b/tests/acceptance/boot_linux.py
@@ -57,7 +57,7 @@  class BootLinuxBase(Test):
             self.cancel('Failed to download/prepare boot image')
         return boot.path
 
-    def download_cloudinit(self, ssh_pubkey=None):
+    def download_cloudinit(self):
         self.log.info('Preparing cloudinit image')
         try:
             cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
@@ -67,8 +67,7 @@  class BootLinuxBase(Test):
                           password='password',
                           # QEMU's hard coded usermode router address
                           phone_home_host='10.0.2.2',
-                          phone_home_port=self.phone_home_port,
-                          authorized_key=ssh_pubkey)
+                          phone_home_port=self.phone_home_port)
         except Exception:
             self.cancel('Failed to prepared cloudinit image')
         return cloudinit_iso
@@ -81,19 +80,19 @@  class BootLinux(BootLinuxBase):
     timeout = 900
     chksum = None
 
-    def setUp(self, ssh_pubkey=None):
+    def setUp(self):
         super(BootLinux, self).setUp()
         self.vm.add_args('-smp', '2')
         self.vm.add_args('-m', '1024')
         self.prepare_boot()
-        self.prepare_cloudinit(ssh_pubkey)
+        self.prepare_cloudinit()
 
     def prepare_boot(self):
         path = self.download_boot()
         self.vm.add_args('-drive', 'file=%s' % path)
 
-    def prepare_cloudinit(self, ssh_pubkey=None):
-        cloudinit_iso = self.download_cloudinit(ssh_pubkey)
+    def prepare_cloudinit(self):
+        cloudinit_iso = self.download_cloudinit()
         self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
 
     def launch_and_wait(self):
diff --git a/tests/acceptance/virtiofs_submounts.py b/tests/acceptance/virtiofs_submounts.py
deleted file mode 100644
index 8b207b3e577f..000000000000
--- a/tests/acceptance/virtiofs_submounts.py
+++ /dev/null
@@ -1,289 +0,0 @@ 
-import logging
-import re
-import os
-import subprocess
-import time
-
-from avocado import skipUnless
-from avocado_qemu import Test, BUILD_DIR
-from avocado_qemu import wait_for_console_pattern
-from avocado.utils import ssh
-
-from qemu.accel import kvm_available
-
-from boot_linux import BootLinux
-
-
-def run_cmd(args):
-    subp = subprocess.Popen(args,
-                            stdout=subprocess.PIPE,
-                            stderr=subprocess.PIPE,
-                            universal_newlines=True)
-    stdout, stderr = subp.communicate()
-    ret = subp.returncode
-
-    return (stdout, stderr, ret)
-
-def has_passwordless_sudo():
-    """
-    This function is for use in a @avocado.skipUnless decorator, e.g.:
-
-        @skipUnless(*has_passwordless_sudo())
-        def test_something_that_needs_sudo(self):
-            ...
-    """
-
-    _, stderr, exitcode = run_cmd(('sudo', '-n', 'true'))
-    if exitcode != 0:
-        return (False, f'Failed to use sudo -n: {stderr.strip()}')
-    else:
-        return (True, '')
-
-
-class VirtiofsSubmountsTest(BootLinux):
-    """
-    :avocado: tags=arch:x86_64
-    """
-
-    def get_portfwd(self):
-        port = None
-
-        res = self.vm.command('human-monitor-command',
-                              command_line='info usernet')
-        for line in res.split('\r\n'):
-            match = \
-                re.search(r'TCP.HOST_FORWARD.*127\.0\.0\.1\s*(\d+)\s+10\.',
-                          line)
-            if match is not None:
-                port = match[1]
-                break
-
-        self.assertIsNotNone(port)
-        self.log.debug('sshd listening on port: ' + port)
-        return port
-
-    def ssh_connect(self, username, keyfile):
-        self.ssh_logger = logging.getLogger('ssh')
-        port = self.get_portfwd()
-        self.ssh_session = ssh.Session('127.0.0.1', port=int(port),
-                                       user=username, key=keyfile)
-        for i in range(10):
-            try:
-                self.ssh_session.connect()
-                return
-            except:
-                time.sleep(4)
-                pass
-        self.fail('sshd timeout')
-
-    def ssh_command(self, command):
-        self.ssh_logger.info(command)
-        result = self.ssh_session.cmd(command)
-        stdout_lines = [line.rstrip() for line
-                        in result.stdout_text.splitlines()]
-        for line in stdout_lines:
-            self.ssh_logger.info(line)
-        stderr_lines = [line.rstrip() for line
-                        in result.stderr_text.splitlines()]
-        for line in stderr_lines:
-            self.ssh_logger.warning(line)
-
-        self.assertEqual(result.exit_status, 0,
-                         f'Guest command failed: {command}')
-        return stdout_lines, stderr_lines
-
-    def run(self, args, ignore_error=False):
-        stdout, stderr, ret = run_cmd(args)
-
-        if ret != 0:
-            cmdline = ' '.join(args)
-            if not ignore_error:
-                self.fail(f'{cmdline}: Returned {ret}: {stderr}')
-            else:
-                self.log.warn(f'{cmdline}: Returned {ret}: {stderr}')
-
-        return (stdout, stderr, ret)
-
-    def set_up_shared_dir(self):
-        atwd = os.getenv('AVOCADO_TEST_WORKDIR')
-        self.shared_dir = os.path.join(atwd, 'virtiofs-shared')
-
-        os.mkdir(self.shared_dir)
-
-        self.run(('cp', self.get_data('guest.sh'),
-                 os.path.join(self.shared_dir, 'check.sh')))
-
-        self.run(('cp', self.get_data('guest-cleanup.sh'),
-                 os.path.join(self.shared_dir, 'cleanup.sh')))
-
-    def set_up_virtiofs(self):
-        attmp = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR')
-        self.vfsdsock = os.path.join(attmp, 'vfsdsock')
-
-        self.run(('sudo', '-n', 'rm', '-f', self.vfsdsock), ignore_error=True)
-
-        self.virtiofsd = \
-            subprocess.Popen(('sudo', '-n',
-                              'tools/virtiofsd/virtiofsd',
-                              f'--socket-path={self.vfsdsock}',
-                              '-o', f'source={self.shared_dir}',
-                              '-o', 'cache=always',
-                              '-o', 'xattr',
-                              '-o', 'announce_submounts',
-                              '-f'),
-                             stdout=subprocess.DEVNULL,
-                             stderr=subprocess.PIPE,
-                             universal_newlines=True)
-
-        while not os.path.exists(self.vfsdsock):
-            if self.virtiofsd.poll() is not None:
-                self.fail('virtiofsd exited prematurely: ' +
-                          self.virtiofsd.communicate()[1])
-            time.sleep(0.1)
-
-        self.run(('sudo', '-n', 'chmod', 'go+rw', self.vfsdsock))
-
-        self.vm.add_args('-chardev',
-                         f'socket,id=vfsdsock,path={self.vfsdsock}',
-                         '-device',
-                         'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \
-                             ',tag=host',
-                         '-object',
-                         'memory-backend-file,id=mem,size=1G,' \
-                             'mem-path=/dev/shm,share=on',
-                         '-numa',
-                         'node,memdev=mem')
-
-    def launch_vm(self):
-        self.launch_and_wait()
-        self.ssh_connect('root', self.ssh_key)
-
-    def set_up_nested_mounts(self):
-        scratch_dir = os.path.join(self.shared_dir, 'scratch')
-        try:
-            os.mkdir(scratch_dir)
-        except FileExistsError:
-            pass
-
-        args = ['bash', self.get_data('host.sh'), scratch_dir]
-        if self.seed:
-            args += [self.seed]
-
-        out, _, _ = self.run(args)
-        seed = re.search(r'^Seed: \d+', out)
-        self.log.info(seed[0])
-
-    def mount_in_guest(self):
-        self.ssh_command('mkdir -p /mnt/host')
-        self.ssh_command('mount -t virtiofs host /mnt/host')
-
-    def check_in_guest(self):
-        self.ssh_command('bash /mnt/host/check.sh /mnt/host/scratch/share')
-
-    def live_cleanup(self):
-        self.ssh_command('bash /mnt/host/cleanup.sh /mnt/host/scratch')
-
-        # It would be nice if the above was sufficient to make virtiofsd clear
-        # all references to the mounted directories (so they can be unmounted
-        # on the host), but unfortunately it is not.  To do so, we have to
-        # resort to a remount.
-        self.ssh_command('mount -o remount /mnt/host')
-
-        scratch_dir = os.path.join(self.shared_dir, 'scratch')
-        self.run(('bash', self.get_data('cleanup.sh'), scratch_dir))
-
-    @skipUnless(*has_passwordless_sudo())
-    def setUp(self):
-        vmlinuz = self.params.get('vmlinuz')
-        if vmlinuz is None:
-            self.cancel('vmlinuz parameter not set; you must point it to a '
-                        'Linux kernel binary to test (to run this test with ' \
-                        'the on-image kernel, set it to an empty string)')
-
-        self.seed = self.params.get('seed')
-
-        atwd = os.getenv('AVOCADO_TEST_WORKDIR')
-        self.ssh_key = os.path.join(atwd, 'id_ed25519')
-
-        self.run(('ssh-keygen', '-t', 'ed25519', '-f', self.ssh_key))
-
-        pubkey = open(self.ssh_key + '.pub').read()
-
-        super(VirtiofsSubmountsTest, self).setUp(pubkey)
-
-        if len(vmlinuz) > 0:
-            self.vm.add_args('-kernel', vmlinuz,
-                             '-append', 'console=ttyS0 root=/dev/sda1')
-
-        # Allow us to connect to SSH
-        self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22',
-                         '-device', 'e1000,netdev=vnet')
-
-        if not kvm_available(self.arch, self.qemu_bin):
-            self.cancel(KVM_NOT_AVAILABLE)
-        self.vm.add_args('-accel', 'kvm')
-
-    def tearDown(self):
-        try:
-            self.vm.shutdown()
-        except:
-            pass
-
-        scratch_dir = os.path.join(self.shared_dir, 'scratch')
-        self.run(('bash', self.get_data('cleanup.sh'), scratch_dir),
-                 ignore_error=True)
-
-    def test_pre_virtiofsd_set_up(self):
-        self.set_up_shared_dir()
-
-        self.set_up_nested_mounts()
-
-        self.set_up_virtiofs()
-        self.launch_vm()
-        self.mount_in_guest()
-        self.check_in_guest()
-
-    def test_pre_launch_set_up(self):
-        self.set_up_shared_dir()
-        self.set_up_virtiofs()
-
-        self.set_up_nested_mounts()
-
-        self.launch_vm()
-        self.mount_in_guest()
-        self.check_in_guest()
-
-    def test_post_launch_set_up(self):
-        self.set_up_shared_dir()
-        self.set_up_virtiofs()
-        self.launch_vm()
-
-        self.set_up_nested_mounts()
-
-        self.mount_in_guest()
-        self.check_in_guest()
-
-    def test_post_mount_set_up(self):
-        self.set_up_shared_dir()
-        self.set_up_virtiofs()
-        self.launch_vm()
-        self.mount_in_guest()
-
-        self.set_up_nested_mounts()
-
-        self.check_in_guest()
-
-    def test_two_runs(self):
-        self.set_up_shared_dir()
-
-        self.set_up_nested_mounts()
-
-        self.set_up_virtiofs()
-        self.launch_vm()
-        self.mount_in_guest()
-        self.check_in_guest()
-
-        self.live_cleanup()
-        self.set_up_nested_mounts()
-
-        self.check_in_guest()
diff --git a/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
deleted file mode 100644
index 2a6579a0fea6..000000000000
--- a/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
+++ /dev/null
@@ -1,46 +0,0 @@ 
-#!/bin/bash
-
-function print_usage()
-{
-    if [ -n "$2" ]; then
-        echo "Error: $2"
-        echo
-    fi
-    echo "Usage: $1 <scratch dir>"
-}
-
-scratch_dir=$1
-if [ -z "$scratch_dir" ]; then
-    print_usage "$0" 'Scratch dir not given' >&2
-    exit 1
-fi
-
-cd "$scratch_dir/share" || exit 1
-mps=(mnt*)
-mp_i=0
-for mp in "${mps[@]}"; do
-    mp_i=$((mp_i + 1))
-    printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}"
-
-    sudo umount -R "$mp"
-    rm -rf "$mp"
-done
-echo
-
-rm some-file
-cd ..
-rmdir share
-
-imgs=(fs*.img)
-img_i=0
-for img in "${imgs[@]}"; do
-    img_i=$((img_i + 1))
-    printf "Detaching and deleting %i/%i...\r" "$img_i" "${#imgs[@]}"
-
-    dev=$(losetup -j "$img" | sed -e 's/:.*//')
-    sudo losetup -d "$dev"
-    rm -f "$img"
-done
-echo
-
-echo 'Done.'
diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
deleted file mode 100644
index 729cb2d1a5ec..000000000000
--- a/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
+++ /dev/null
@@ -1,30 +0,0 @@ 
-#!/bin/bash
-
-function print_usage()
-{
-    if [ -n "$2" ]; then
-        echo "Error: $2"
-        echo
-    fi
-    echo "Usage: $1 <scratch dir>"
-}
-
-scratch_dir=$1
-if [ -z "$scratch_dir" ]; then
-    print_usage "$0" 'Scratch dir not given' >&2
-    exit 1
-fi
-
-cd "$scratch_dir/share" || exit 1
-
-mps=(mnt*)
-mp_i=0
-for mp in "${mps[@]}"; do
-    mp_i=$((mp_i + 1))
-    printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}"
-
-    sudo umount -R "$mp"
-done
-echo
-
-echo 'Done.'
diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest.sh b/tests/acceptance/virtiofs_submounts.py.data/guest.sh
deleted file mode 100644
index 59ba40fde179..000000000000
--- a/tests/acceptance/virtiofs_submounts.py.data/guest.sh
+++ /dev/null
@@ -1,138 +0,0 @@ 
-#!/bin/bash
-
-function print_usage()
-{
-    if [ -n "$2" ]; then
-        echo "Error: $2"
-        echo
-    fi
-    echo "Usage: $1 <shared dir>"
-    echo '(The shared directory is the "share" directory in the scratch' \
-         'directory)'
-}
-
-shared_dir=$1
-if [ -z "$shared_dir" ]; then
-    print_usage "$0" 'Shared dir not given' >&2
-    exit 1
-fi
-
-cd "$shared_dir"
-
-# FIXME: This should not be necessary, but it is.  In order for all
-# submounts to be proper mount points, we need to visit them.
-# (Before we visit them, they will not be auto-mounted, and so just
-# appear as normal directories, with the catch that their st_ino will
-# be the st_ino of the filesystem they host, while the st_dev will
-# still be the st_dev of the parent.)
-# `find` does not work, because it will refuse to touch the mount
-# points as long as they are not mounted; their st_dev being shared
-# with the parent and st_ino just being the root node's inode ID
-# will practically ensure that this node exists elsewhere on the
-# filesystem, and `find` is required to recognize loops and not to
-# follow them.
-# Thus, we have to manually visit all nodes first.
-
-mnt_i=0
-
-function recursively_visit()
-{
-    pushd "$1" >/dev/null
-    for entry in *; do
-        if [[ "$entry" == mnt* ]]; then
-            mnt_i=$((mnt_i + 1))
-            printf "Triggering auto-mount $mnt_i...\r"
-        fi
-
-        if [ -d "$entry" ]; then
-            recursively_visit "$entry"
-        fi
-    done
-    popd >/dev/null
-}
-
-recursively_visit .
-echo
-
-
-if [ -n "$(find -name not-mounted)" ]; then
-    echo "Error: not-mounted files visible on mount points:" >&2
-    find -name not-mounted >&2
-    exit 1
-fi
-
-if [ ! -f some-file -o "$(cat some-file)" != 'root' ]; then
-    echo "Error: Bad file in the share root" >&2
-    exit 1
-fi
-
-shopt -s nullglob
-
-function check_submounts()
-{
-    local base_path=$1
-
-    for mp in mnt*; do
-        printf "Checking submount %i...\r" "$((${#devs[@]} + 1))"
-
-        mp_i=$(echo "$mp" | sed -e 's/mnt//')
-        dev=$(stat -c '%D' "$mp")
-
-        if [ -n "${devs[mp_i]}" ]; then
-            echo "Error: $mp encountered twice" >&2
-            exit 1
-        fi
-        devs[mp_i]=$dev
-
-        pushd "$mp" >/dev/null
-        path="$base_path$mp"
-        while true; do
-            expected_content="$(printf '%s\n%s\n' "$mp_i" "$path")"
-            if [ ! -f some-file ]; then
-                echo "Error: $PWD/some-file does not exist" >&2
-                exit 1
-            fi
-
-            if [ "$(cat some-file)" != "$expected_content" ]; then
-                echo "Error: Bad content in $PWD/some-file:" >&2
-                echo '--- found ---'
-                cat some-file
-                echo '--- expected ---'
-                echo "$expected_content"
-                exit 1
-            fi
-            if [ "$(stat -c '%D' some-file)" != "$dev" ]; then
-                echo "Error: $PWD/some-file has the wrong device ID" >&2
-                exit 1
-            fi
-
-            if [ -d sub ]; then
-                if [ "$(stat -c '%D' sub)" != "$dev" ]; then
-                    echo "Error: $PWD/some-file has the wrong device ID" >&2
-                    exit 1
-                fi
-                cd sub
-                path="$path/sub"
-            else
-                if [ -n "$(echo mnt*)" ]; then
-                    check_submounts "$path/"
-                fi
-                break
-            fi
-        done
-        popd >/dev/null
-    done
-}
-
-root_dev=$(stat -c '%D' some-file)
-devs=()
-check_submounts ''
-echo
-
-reused_devs=$(echo "$root_dev ${devs[@]}" | tr ' ' '\n' | sort | uniq -d)
-if [ -n "$reused_devs" ]; then
-    echo "Error: Reused device IDs: $reused_devs" >&2
-    exit 1
-fi
-
-echo "Test passed for ${#devs[@]} submounts."
diff --git a/tests/acceptance/virtiofs_submounts.py.data/host.sh b/tests/acceptance/virtiofs_submounts.py.data/host.sh
deleted file mode 100644
index d8a9afebdb54..000000000000
--- a/tests/acceptance/virtiofs_submounts.py.data/host.sh
+++ /dev/null
@@ -1,127 +0,0 @@ 
-#!/bin/bash
-
-mount_count=128
-
-function print_usage()
-{
-    if [ -n "$2" ]; then
-        echo "Error: $2"
-        echo
-    fi
-    echo "Usage: $1 <scratch dir> [seed]"
-    echo "(If no seed is given, it will be randomly generated.)"
-}
-
-scratch_dir=$1
-if [ -z "$scratch_dir" ]; then
-    print_usage "$0" 'No scratch dir given' >&2
-    exit 1
-fi
-
-if [ ! -d "$scratch_dir" ]; then
-    print_usage "$0" "$scratch_dir is not a directory" >&2
-    exit 1
-fi
-
-seed=$2
-if [ -z "$seed" ]; then
-    seed=$RANDOM
-fi
-RANDOM=$seed
-
-echo "Seed: $seed"
-
-set -e
-shopt -s nullglob
-
-cd "$scratch_dir"
-if [ -d share ]; then
-    echo 'Error: This directory seems to be in use already' >&2
-    exit 1
-fi
-
-for ((i = 0; i < $mount_count; i++)); do
-    printf "Setting up fs %i/%i...\r" "$((i + 1))" "$mount_count"
-
-    rm -f fs$i.img
-    truncate -s 512M fs$i.img
-    mkfs.xfs -q fs$i.img
-    devs[i]=$(sudo losetup -f --show fs$i.img)
-done
-echo
-
-top_level_mounts=$((RANDOM % mount_count + 1))
-
-mkdir -p share
-echo 'root' > share/some-file
-
-for ((i = 0; i < $top_level_mounts; i++)); do
-    printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count"
-
-    mkdir -p share/mnt$i
-    touch share/mnt$i/not-mounted
-    sudo mount "${devs[i]}" share/mnt$i
-    sudo chown "$(id -u):$(id -g)" share/mnt$i
-
-    pushd share/mnt$i >/dev/null
-    path=mnt$i
-    nesting=$((RANDOM % 4))
-    for ((j = 0; j < $nesting; j++)); do
-        cat > some-file <<EOF
-$i
-$path
-EOF
-        mkdir sub
-        cd sub
-        path="$path/sub"
-    done
-cat > some-file <<EOF
-$i
-$path
-EOF
-    popd >/dev/null
-done
-
-for ((; i < $mount_count; i++)); do
-    printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count"
-
-    mp_i=$((i % top_level_mounts))
-
-    pushd share/mnt$mp_i >/dev/null
-    path=mnt$mp_i
-    while true; do
-        sub_mp="$(echo mnt*)"
-        if cd sub 2>/dev/null; then
-            path="$path/sub"
-        elif [ -n "$sub_mp" ] && cd "$sub_mp" 2>/dev/null; then
-            path="$path/$sub_mp"
-        else
-            break
-        fi
-    done
-    mkdir mnt$i
-    touch mnt$i/not-mounted
-    sudo mount "${devs[i]}" mnt$i
-    sudo chown "$(id -u):$(id -g)" mnt$i
-
-    cd mnt$i
-    path="$path/mnt$i"
-    nesting=$((RANDOM % 4))
-    for ((j = 0; j < $nesting; j++)); do
-        cat > some-file <<EOF
-$i
-$path
-EOF
-        mkdir sub
-        cd sub
-        path="$path/sub"
-    done
-    cat > some-file <<EOF
-$i
-$path
-EOF
-    popd >/dev/null
-done
-echo
-
-echo 'Done.'
diff --git a/tools/virtiofsd/fuse_common.h b/tools/virtiofsd/fuse_common.h
index 870544fe132f..686c42c0a528 100644
--- a/tools/virtiofsd/fuse_common.h
+++ b/tools/virtiofsd/fuse_common.h
@@ -352,14 +352,6 @@  struct fuse_file_info {
  */
 #define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
 
-/**
- * Indicates that the client will provide fuse_attr.flags, and the kernel will
- * interpret it.
- *
- * This feature is enabled by default when supported by the kernel.
- */
-#define FUSE_CAP_ATTR_FLAGS (1 << 27)
-
 /**
  * Ioctl flags
  *
diff --git a/tools/virtiofsd/fuse_lowlevel.c b/tools/virtiofsd/fuse_lowlevel.c
index 5198f627bcb3..4d1ba2925d1b 100644
--- a/tools/virtiofsd/fuse_lowlevel.c
+++ b/tools/virtiofsd/fuse_lowlevel.c
@@ -329,8 +329,7 @@  static unsigned int calc_timeout_nsec(double t)
     }
 }
 
-static void fill_entry(struct fuse_session *se,
-                       struct fuse_entry_out *arg,
+static void fill_entry(struct fuse_entry_out *arg,
                        const struct fuse_entry_param *e)
 {
     *arg = (struct fuse_entry_out){
@@ -342,10 +341,6 @@  static void fill_entry(struct fuse_session *se,
         .attr_valid_nsec = calc_timeout_nsec(e->attr_timeout),
     };
     convert_stat(&e->attr, &arg->attr);
-
-    if (se->conn.capable & FUSE_CAP_ATTR_FLAGS) {
-        arg->attr.flags = e->attr_flags;
-    }
 }
 
 /*
@@ -370,7 +365,7 @@  size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
 
     struct fuse_direntplus *dp = (struct fuse_direntplus *)buf;
     memset(&dp->entry_out, 0, sizeof(dp->entry_out));
-    fill_entry(req->se, &dp->entry_out, e);
+    fill_entry(&dp->entry_out, e);
 
     struct fuse_dirent *dirent = &dp->dirent;
     *dirent = (struct fuse_dirent){
@@ -408,7 +403,7 @@  int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
     size_t size = sizeof(arg);
 
     memset(&arg, 0, sizeof(arg));
-    fill_entry(req->se, &arg, e);
+    fill_entry(&arg, e);
     return send_reply_ok(req, &arg, size);
 }
 
@@ -421,13 +416,13 @@  int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
     struct fuse_open_out *oarg = (struct fuse_open_out *)(buf + entrysize);
 
     memset(buf, 0, sizeof(buf));
-    fill_entry(req->se, earg, e);
+    fill_entry(earg, e);
     fill_open(oarg, f);
     return send_reply_ok(req, buf, entrysize + sizeof(struct fuse_open_out));
 }
 
-int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
-                               double attr_timeout, uint32_t attr_flags)
+int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+                    double attr_timeout)
 {
     struct fuse_attr_out arg;
     size_t size = sizeof(arg);
@@ -437,19 +432,9 @@  int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
     arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
     convert_stat(attr, &arg.attr);
 
-    if (req->se->conn.capable & FUSE_CAP_ATTR_FLAGS) {
-        arg.attr.flags = attr_flags;
-    }
-
     return send_reply_ok(req, &arg, size);
 }
 
-int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
-                    double attr_timeout)
-{
-    return fuse_reply_attr_with_flags(req, attr, attr_timeout, 0);
-}
-
 int fuse_reply_readlink(fuse_req_t req, const char *linkname)
 {
     return send_reply_ok(req, linkname, strlen(linkname));
@@ -2003,9 +1988,6 @@  static void do_init(fuse_req_t req, fuse_ino_t nodeid,
             bufsize = max_bufsize;
         }
     }
-    if (arg->flags & FUSE_ATTR_FLAGS) {
-        se->conn.capable |= FUSE_CAP_ATTR_FLAGS;
-    }
 #ifdef HAVE_SPLICE
 #ifdef HAVE_VMSPLICE
     se->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
@@ -2032,7 +2014,6 @@  static void do_init(fuse_req_t req, fuse_ino_t nodeid,
     LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
     LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
     LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
-    LL_SET_DEFAULT(1, FUSE_CAP_ATTR_FLAGS);
     LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
     LL_SET_DEFAULT(se->op.getlk && se->op.setlk, FUSE_CAP_POSIX_LOCKS);
     LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
@@ -2122,9 +2103,6 @@  static void do_init(fuse_req_t req, fuse_ino_t nodeid,
     if (se->conn.want & FUSE_CAP_POSIX_ACL) {
         outarg.flags |= FUSE_POSIX_ACL;
     }
-    if (se->conn.want & FUSE_CAP_ATTR_FLAGS) {
-        outarg.flags |= FUSE_ATTR_FLAGS;
-    }
     outarg.max_readahead = se->conn.max_readahead;
     outarg.max_write = se->conn.max_write;
     if (se->conn.max_background >= (1 << 16)) {
diff --git a/tools/virtiofsd/fuse_lowlevel.h b/tools/virtiofsd/fuse_lowlevel.h
index 1ff6ba1e4f1e..562fd5241ed4 100644
--- a/tools/virtiofsd/fuse_lowlevel.h
+++ b/tools/virtiofsd/fuse_lowlevel.h
@@ -102,11 +102,6 @@  struct fuse_entry_param {
      *  large value.
      */
     double entry_timeout;
-
-    /**
-     * Flags for fuse_attr.flags that do not fit into attr.
-     */
-    uint32_t attr_flags;
 };
 
 /**
@@ -1313,21 +1308,6 @@  int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
 int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
                     double attr_timeout);
 
-/**
- * Reply with attributes and set fuse_attr.flags
- *
- * Possible requests:
- *   getattr, setattr
- *
- * @param req request handle
- * @param attr the attributes
- * @param attr_timeout validity timeout (in seconds) for the attributes
- * @param attr_flags flags to put into fuse_attr.flags
- * @return zero for success, -errno for failure to send reply
- */
-int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
-                               double attr_timeout, uint32_t attr_flags);
-
 /**
  * Reply with the contents of a symbolic link
  *
diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c
index a212f329317b..2e181a49b5ff 100644
--- a/tools/virtiofsd/helper.c
+++ b/tools/virtiofsd/helper.c
@@ -178,7 +178,6 @@  void fuse_cmdline_help(void)
            "                               default: depends on cache= option.\n"
            "    -o writeback|no_writeback  enable/disable writeback cache\n"
            "                               default: no_writeback\n"
-           "    -o announce_submounts      Announce sub-mount points to the guest\n"
            "    -o xattr|no_xattr          enable/disable xattr\n"
            "                               default: no_xattr\n"
            "    -o modcaps=CAPLIST         Modify the list of capabilities\n"
diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
index 4db50046d49f..a0beb986f36a 100644
--- a/tools/virtiofsd/passthrough_ll.c
+++ b/tools/virtiofsd/passthrough_ll.c
@@ -40,7 +40,6 @@ 
 #include "fuse_virtio.h"
 #include "fuse_log.h"
 #include "fuse_lowlevel.h"
-#include "standard-headers/linux/fuse.h"
 #include <assert.h>
 #include <cap-ng.h>
 #include <dirent.h>
@@ -126,14 +125,6 @@  struct lo_inode {
     GHashTable *posix_locks; /* protected by lo_inode->plock_mutex */
 
     mode_t filetype;
-
-    /*
-     * So we can detect crossmount roots
-     * (As such, this only needs to be valid for directories.  Note
-     * that files can have multiple parents due to hard links, and so
-     * their parent_dev may fluctuate.)
-     */
-    dev_t parent_dev;
 };
 
 struct lo_cred {
@@ -174,7 +165,6 @@  struct lo_data {
     int timeout_set;
     int readdirplus_set;
     int readdirplus_clear;
-    int announce_submounts;
     int allow_direct_io;
     struct lo_inode root;
     GHashTable *inodes; /* protected by lo->mutex */
@@ -213,7 +203,6 @@  static const struct fuse_opt lo_opts[] = {
     { "cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS },
     { "readdirplus", offsetof(struct lo_data, readdirplus_set), 1 },
     { "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 },
-    { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 },
     { "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
     { "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
     FUSE_OPT_END
@@ -611,52 +600,22 @@  static void lo_init(void *userdata, struct fuse_conn_info *conn)
     }
 }
 
-/**
- * Call fstatat() and set st_rdev whenever a directory's st_dev
- * differs from the rparent's st_dev (@parent_dev).  This will
- * announce submounts to the FUSE client (unless @announce_submounts
- * is false).
- */
-static int do_fstatat(int dirfd, const char *pathname, struct stat *statbuf,
-                      int flags, dev_t parent_dev, uint32_t *fuse_attr_flags)
-{
-    int res = fstatat(dirfd, pathname, statbuf, flags);
-    if (res == -1) {
-        return res;
-    }
-
-    if (statbuf->st_dev != parent_dev && S_ISDIR(statbuf->st_mode) &&
-        fuse_attr_flags)
-    {
-        *fuse_attr_flags |= FUSE_ATTR_SUBMOUNT;
-    }
-
-    return 0;
-}
-
 static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
                        struct fuse_file_info *fi)
 {
     int res;
     struct stat buf;
     struct lo_data *lo = lo_data(req);
-    struct lo_inode *inode = lo_inode(req, ino);
-    uint32_t fuse_attr_flags = 0;
 
     (void)fi;
 
-    res = do_fstatat(inode->fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
-                     inode->parent_dev, &fuse_attr_flags);
-    lo_inode_put(lo, &inode);
+    res =
+        fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
     if (res == -1) {
         return (void)fuse_reply_err(req, errno);
     }
 
-    if (!lo->announce_submounts) {
-        fuse_attr_flags &= ~FUSE_ATTR_SUBMOUNT;
-    }
-
-    fuse_reply_attr_with_flags(req, &buf, lo->timeout, fuse_attr_flags);
+    fuse_reply_attr(req, &buf, lo->timeout);
 }
 
 static int lo_fi_fd(fuse_req_t req, struct fuse_file_info *fi)
@@ -852,16 +811,11 @@  static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
         goto out_err;
     }
 
-    res = do_fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
-                     dir->key.dev, &e->attr_flags);
+    res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
     if (res == -1) {
         goto out_err;
     }
 
-    if (!lo->announce_submounts) {
-        e->attr_flags &= ~FUSE_ATTR_SUBMOUNT;
-    }
-
     inode = lo_find(lo, &e->attr);
     if (inode) {
         close(newfd);
@@ -893,7 +847,6 @@  static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
         g_hash_table_insert(lo->inodes, &inode->key, inode);
         pthread_mutex_unlock(&lo->mutex);
     }
-    inode->parent_dev = dir->key.dev;
     e->ino = inode->fuse_ino;
     lo_inode_put(lo, &inode);
     lo_inode_put(lo, &dir);
@@ -1107,17 +1060,11 @@  static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
         goto out_err;
     }
 
-    res = do_fstatat(inode->fd, "", &e.attr,
-                     AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
-                     parent_inode->key.dev, &e.attr_flags);
+    res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
     if (res == -1) {
         goto out_err;
     }
 
-    if (!lo->announce_submounts) {
-        e.attr_flags &= ~FUSE_ATTR_SUBMOUNT;
-    }
-
     pthread_mutex_lock(&lo->mutex);
     inode->nlookup++;
     pthread_mutex_unlock(&lo->mutex);
@@ -1126,14 +1073,6 @@  static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
     fuse_log(FUSE_LOG_DEBUG, "  %lli/%s -> %lli\n", (unsigned long long)parent,
              name, (unsigned long long)e.ino);
 
-    /*
-     * No need to update inode->parent_dev, because
-     * (1) We cannot, the inode now has more than one parent,
-     * (2) Directories cannot have more than one parent, so link()
-     *     does not work for them; but parent_dev only needs to be
-     *     valid for directories.
-     */
-
     fuse_reply_entry(req, &e);
     lo_inode_put(lo, &parent_inode);
     lo_inode_put(lo, &inode);
@@ -1152,21 +1091,14 @@  static struct lo_inode *lookup_name(fuse_req_t req, fuse_ino_t parent,
 {
     int res;
     struct stat attr;
-    struct lo_data *lo = lo_data(req);
-    struct lo_inode *dir = lo_inode(req, parent);
 
-    if (!dir) {
-        return NULL;
-    }
-
-    res = do_fstatat(dir->fd, name, &attr,
-                     AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, dir->key.dev, NULL);
-    lo_inode_put(lo, &dir);
+    res = fstatat(lo_fd(req, parent), name, &attr,
+                  AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
     if (res == -1) {
         return NULL;
     }
 
-    return lo_find(lo, &attr);
+    return lo_find(lo_data(req), &attr);
 }
 
 static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)