Merge remote-tracking branch 'remotes/dgilbert-gitlab/tags/pull-virtiofs-20201026' into staging

virtiofsd pull 2020-10-26

Misono
   Set default log level to info
   Explicit build option for virtiofsd

Me
   xattr name mapping

Stefan
  Alternative chroot sandbox method

Max
  Submount mechanism

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

# gpg: Signature made Mon 26 Oct 2020 18:41:36 GMT
# gpg:                using RSA key 45F5C71B4A0CB7FB977A9FA90516331EBC5BFDE7
# gpg: Good signature from "Dr. David Alan Gilbert (RH2) <dgilbert@redhat.com>" [full]
# Primary key fingerprint: 45F5 C71B 4A0C B7FB 977A  9FA9 0516 331E BC5B FDE7

* remotes/dgilbert-gitlab/tags/pull-virtiofs-20201026:
  tests/acceptance: Add virtiofs_submounts.py
  tests/acceptance/boot_linux: Accept SSH pubkey
  virtiofsd: Announce sub-mount points
  virtiofsd: Store every lo_inode's parent_dev
  virtiofsd: Add fuse_reply_attr_with_flags()
  virtiofsd: Add attr_flags to fuse_entry_param
  virtiofsd: Announce FUSE_ATTR_FLAGS
  linux/fuse.h: Pull in from Linux
  tools/virtiofsd: xattr name mappings: Simple 'map'
  tools/virtiofsd: xattr name mapping examples
  tools/virtiofsd: xattr name mappings: Map server xattr names
  tools/virtiofsd: xattr name mappings: Map client xattr names
  tools/virtiofsd: xattr name mappings: Add option
  virtiofsd: add container-friendly -o sandbox=chroot option
  virtiofsd: passthrough_ll: set FUSE_LOG_INFO as default log_level
  configure: add option for virtiofsd

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell
2020-10-27 14:29:52 +00:00
17 changed files with 1528 additions and 40 deletions
Vendored
+7 -1
View File
@@ -302,6 +302,7 @@ fdt="auto"
netmap="no"
sdl="auto"
sdl_image="auto"
virtiofsd="auto"
virtfs=""
libudev="auto"
mpath="auto"
@@ -999,6 +1000,10 @@ for opt do
;;
--enable-libudev) libudev="enabled"
;;
--disable-virtiofsd) virtiofsd="disabled"
;;
--enable-virtiofsd) virtiofsd="enabled"
;;
--disable-mpath) mpath="disabled"
;;
--enable-mpath) mpath="enabled"
@@ -1758,6 +1763,7 @@ disabled with --disable-FEATURE, default is enabled if available:
vnc-png PNG compression for VNC server
cocoa Cocoa UI (Mac OS X only)
virtfs VirtFS
virtiofsd build virtiofs daemon (virtiofsd)
libudev Use libudev to enumerate host devices
mpath Multipath persistent reservation passthrough
xen xen backend driver support
@@ -6972,7 +6978,7 @@ NINJA=$ninja $meson setup \
-Dxen=$xen -Dxen_pci_passthrough=$xen_pci_passthrough -Dtcg=$tcg \
-Dcocoa=$cocoa -Dmpath=$mpath -Dsdl=$sdl -Dsdl_image=$sdl_image \
-Dvnc=$vnc -Dvnc_sasl=$vnc_sasl -Dvnc_jpeg=$vnc_jpeg -Dvnc_png=$vnc_png \
-Dgettext=$gettext -Dxkbcommon=$xkbcommon -Du2f=$u2f \
-Dgettext=$gettext -Dxkbcommon=$xkbcommon -Du2f=$u2f -Dvirtiofsd=$virtiofsd \
-Dcapstone=$capstone -Dslirp=$slirp -Dfdt=$fdt \
-Diconv=$iconv -Dcurses=$curses -Dlibudev=$libudev\
-Ddocs=$docs -Dsphinx_build=$sphinx_build -Dinstall_blobs=$blobs \
+186 -7
View File
@@ -17,13 +17,24 @@ This program is designed to work with QEMU's ``--device vhost-user-fs-pci``
but should work with any virtual machine monitor (VMM) that supports
vhost-user. See the Examples section below.
This program must be run as the root user. Upon startup the program will
switch into a new file system namespace with the shared directory tree as its
root. This prevents "file system escapes" due to symlinks and other file
system objects that might lead to files outside the shared directory. The
program also sandboxes itself using seccomp(2) to prevent ptrace(2) and other
vectors that could allow an attacker to compromise the system after gaining
control of the virtiofsd process.
This program must be run as the root user. The program drops privileges where
possible during startup although it must be able to create and access files
with any uid/gid:
* The ability to invoke syscalls is limited using seccomp(2).
* Linux capabilities(7) are dropped.
In "namespace" sandbox mode the program switches into a new file system
namespace and invokes pivot_root(2) to make the shared directory tree its root.
A new pid and net namespace is also created to isolate the process.
In "chroot" sandbox mode the program invokes chroot(2) to make the shared
directory tree its root. This mode is intended for container environments where
the container runtime has already set up the namespaces and the program does
not have permission to create namespaces itself.
Both sandbox modes prevent "file system escapes" due to symlinks and other file
system objects that might lead to files outside the shared directory.
Options
-------
@@ -69,6 +80,13 @@ Options
* readdirplus|no_readdirplus -
Enable/disable readdirplus. The default is ``readdirplus``.
* sandbox=namespace|chroot -
Sandbox mode:
- namespace: Create mount, pid, and net namespaces and pivot_root(2) into
the shared directory.
- chroot: chroot(2) into shared directory (use in containers).
The default is "namespace".
* source=PATH -
Share host directory tree located at PATH. This option is required.
@@ -109,6 +127,167 @@ Options
timeout. ``always`` sets a long cache lifetime at the expense of coherency.
The default is ``auto``.
xattr-mapping
-------------
By default the name of xattr's used by the client are passed through to the server
file system. This can be a problem where either those xattr names are used
by something on the server (e.g. selinux client/server confusion) or if the
virtiofsd is running in a container with restricted privileges where it cannot
access some attributes.
A mapping of xattr names can be made using -o xattrmap=mapping where the ``mapping``
string consists of a series of rules.
The first matching rule terminates the mapping.
The set of rules must include a terminating rule to match any remaining attributes
at the end.
Each rule consists of a number of fields separated with a separator that is the
first non-white space character in the rule. This separator must then be used
for the whole rule.
White space may be added before and after each rule.
Using ':' as the separator a rule is of the form:
``:type:scope:key:prepend:``
**scope** is:
- 'client' - match 'key' against a xattr name from the client for
setxattr/getxattr/removexattr
- 'server' - match 'prepend' against a xattr name from the server
for listxattr
- 'all' - can be used to make a single rule where both the server
and client matches are triggered.
**type** is one of:
- 'prefix' - is designed to prepend and strip a prefix; the modified
attributes then being passed on to the client/server.
- 'ok' - Causes the rule set to be terminated when a match is found
while allowing matching xattr's through unchanged.
It is intended both as a way of explicitly terminating
the list of rules, and to allow some xattr's to skip following rules.
- 'bad' - If a client tries to use a name matching 'key' it's
denied using EPERM; when the server passes an attribute
name matching 'prepend' it's hidden. In many ways it's use is very like
'ok' as either an explict terminator or for special handling of certain
patterns.
**key** is a string tested as a prefix on an attribute name originating
on the client. It maybe empty in which case a 'client' rule
will always match on client names.
**prepend** is a string tested as a prefix on an attribute name originating
on the server, and used as a new prefix. It may be empty
in which case a 'server' rule will always match on all names from
the server.
e.g.:
``:prefix:client:trusted.:user.virtiofs.:``
will match 'trusted.' attributes in client calls and prefix them before
passing them to the server.
``:prefix:server::user.virtiofs.:``
will strip 'user.virtiofs.' from all server replies.
``:prefix:all:trusted.:user.virtiofs.:``
combines the previous two cases into a single rule.
``:ok:client:user.::``
will allow get/set xattr for 'user.' xattr's and ignore
following rules.
``:ok:server::security.:``
will pass 'securty.' xattr's in listxattr from the server
and ignore following rules.
``:ok:all:::``
will terminate the rule search passing any remaining attributes
in both directions.
``:bad:server::security.:``
would hide 'security.' xattr's in listxattr from the server.
A simpler 'map' type provides a shorter syntax for the common case:
``:map:key:prepend:``
The 'map' type adds a number of separate rules to add **prepend** as a prefix
to the matched **key** (or all attributes if **key** is empty).
There may be at most one 'map' rule and it must be the last rule in the set.
xattr-mapping Examples
----------------------
1) Prefix all attributes with 'user.virtiofs.'
::
-o xattrmap=":prefix:all::user.virtiofs.::bad:all:::"
This uses two rules, using : as the field separator;
the first rule prefixes and strips 'user.virtiofs.',
the second rule hides any non-prefixed attributes that
the host set.
This is equivalent to the 'map' rule:
::
-o xattrmap=":map::user.virtiofs.:"
2) Prefix 'trusted.' attributes, allow others through
::
"/prefix/all/trusted./user.virtiofs./
/bad/server//trusted./
/bad/client/user.virtiofs.//
/ok/all///"
Here there are four rules, using / as the field
separator, and also demonstrating that new lines can
be included between rules.
The first rule is the prefixing of 'trusted.' and
stripping of 'user.virtiofs.'.
The second rule hides unprefixed 'trusted.' attributes
on the host.
The third rule stops a guest from explicitly setting
the 'user.virtiofs.' path directly.
Finally, the fourth rule lets all remaining attributes
through.
This is equivalent to the 'map' rule:
::
-o xattrmap="/map/trusted./user.virtiofs./"
3) Hide 'security.' attributes, and allow everything else
::
"/bad/all/security./security./
/ok/all///'
The first rule combines what could be separate client and server
rules into a single 'all' rule, matching 'security.' in either
client arguments or lists returned from the host. This stops
the client seeing any 'security.' attributes on the server and
stops it setting any.
Examples
--------
+10 -1
View File
@@ -227,7 +227,7 @@ struct fuse_attr {
uint32_t gid;
uint32_t rdev;
uint32_t blksize;
uint32_t padding;
uint32_t flags;
};
struct fuse_kstatfs {
@@ -310,6 +310,7 @@ 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)
@@ -338,6 +339,7 @@ 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
@@ -413,6 +415,13 @@ 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 */
+1
View File
@@ -2045,6 +2045,7 @@ summary_info += {'Audio drivers': config_host['CONFIG_AUDIO_DRIVERS']}
summary_info += {'Block whitelist (rw)': config_host['CONFIG_BDRV_RW_WHITELIST']}
summary_info += {'Block whitelist (ro)': config_host['CONFIG_BDRV_RO_WHITELIST']}
summary_info += {'VirtFS support': config_host.has_key('CONFIG_VIRTFS')}
summary_info += {'build virtiofs daemon': have_virtiofsd}
summary_info += {'Multipath support': mpathpersist.found()}
summary_info += {'VNC support': vnc.found()}
if vnc.found()
+2
View File
@@ -62,6 +62,8 @@ option('vnc_sasl', type : 'feature', value : 'auto',
description: 'SASL authentication for VNC server')
option('xkbcommon', type : 'feature', value : 'auto',
description: 'xkbcommon support')
option('virtiofsd', type: 'feature', value: 'auto',
description: 'build virtiofs daemon (virtiofsd)')
option('capstone', type: 'combo', value: 'auto',
choices: ['disabled', 'enabled', 'auto', 'system', 'internal'],
+7 -6
View File
@@ -57,7 +57,7 @@ class BootLinuxBase(Test):
self.cancel('Failed to download/prepare boot image')
return boot.path
def download_cloudinit(self):
def download_cloudinit(self, ssh_pubkey=None):
self.log.info('Preparing cloudinit image')
try:
cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
@@ -67,7 +67,8 @@ 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)
phone_home_port=self.phone_home_port,
authorized_key=ssh_pubkey)
except Exception:
self.cancel('Failed to prepared cloudinit image')
return cloudinit_iso
@@ -80,19 +81,19 @@ class BootLinux(BootLinuxBase):
timeout = 900
chksum = None
def setUp(self):
def setUp(self, ssh_pubkey=None):
super(BootLinux, self).setUp()
self.vm.add_args('-smp', '2')
self.vm.add_args('-m', '1024')
self.prepare_boot()
self.prepare_cloudinit()
self.prepare_cloudinit(ssh_pubkey)
def prepare_boot(self):
path = self.download_boot()
self.vm.add_args('-drive', 'file=%s' % path)
def prepare_cloudinit(self):
cloudinit_iso = self.download_cloudinit()
def prepare_cloudinit(self, ssh_pubkey=None):
cloudinit_iso = self.download_cloudinit(ssh_pubkey)
self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
def launch_and_wait(self):
+289
View File
@@ -0,0 +1,289 @@
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()
@@ -0,0 +1,46 @@
#!/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.'
@@ -0,0 +1,30 @@
#!/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.'
@@ -0,0 +1,138 @@
#!/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."
@@ -0,0 +1,127 @@
#!/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.'
+15 -2
View File
@@ -1,10 +1,23 @@
have_virtiofsd = (have_system and
have_virtiofsd = (targetos == 'linux' and
have_tools and
'CONFIG_LINUX' in config_host and
'CONFIG_SECCOMP' in config_host and
'CONFIG_LIBCAP_NG' in config_host and
'CONFIG_VHOST_USER' in config_host)
if get_option('virtiofsd').enabled()
if not have_virtiofsd
if targetos != 'linux'
error('virtiofsd requires Linux')
elif 'CONFIG_SECCOMP' not in config_host or 'CONFIG_LIBCAP_NG' not in config_host
error('virtiofsd requires libcap-ng-devel and seccomp-devel')
elif not have_tools or 'CONFIG_VHOST_USER' not in config_host
error('virtiofsd needs tools and vhost-user support')
endif
endif
elif get_option('virtiofsd').disabled() or not have_system
have_virtiofsd = false
endif
if have_virtiofsd
subdir('virtiofsd')
endif
+8
View File
@@ -352,6 +352,14 @@ 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
*
+28 -6
View File
@@ -329,7 +329,8 @@ static unsigned int calc_timeout_nsec(double t)
}
}
static void fill_entry(struct fuse_entry_out *arg,
static void fill_entry(struct fuse_session *se,
struct fuse_entry_out *arg,
const struct fuse_entry_param *e)
{
*arg = (struct fuse_entry_out){
@@ -341,6 +342,10 @@ static void fill_entry(struct fuse_entry_out *arg,
.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;
}
}
/*
@@ -365,7 +370,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(&dp->entry_out, e);
fill_entry(req->se, &dp->entry_out, e);
struct fuse_dirent *dirent = &dp->dirent;
*dirent = (struct fuse_dirent){
@@ -403,7 +408,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(&arg, e);
fill_entry(req->se, &arg, e);
return send_reply_ok(req, &arg, size);
}
@@ -416,13 +421,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(earg, e);
fill_entry(req->se, earg, e);
fill_open(oarg, f);
return send_reply_ok(req, buf, entrysize + sizeof(struct fuse_open_out));
}
int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
double attr_timeout)
int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
double attr_timeout, uint32_t attr_flags)
{
struct fuse_attr_out arg;
size_t size = sizeof(arg);
@@ -432,9 +437,19 @@ int fuse_reply_attr(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));
@@ -1988,6 +2003,9 @@ 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;
@@ -2014,6 +2032,7 @@ 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);
@@ -2103,6 +2122,9 @@ 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)) {
+20
View File
@@ -102,6 +102,11 @@ struct fuse_entry_param {
* large value.
*/
double entry_timeout;
/**
* Flags for fuse_attr.flags that do not fit into attr.
*/
uint32_t attr_flags;
};
/**
@@ -1308,6 +1313,21 @@ 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
*
+9
View File
@@ -166,10 +166,19 @@ void fuse_cmdline_help(void)
" enable/disable readirplus\n"
" default: readdirplus except with "
"cache=none\n"
" -o sandbox=namespace|chroot\n"
" sandboxing mode:\n"
" - namespace: mount, pid, and net\n"
" namespaces with pivot_root(2)\n"
" into shared directory\n"
" - chroot: chroot(2) into shared\n"
" directory (use in containers)\n"
" default: namespace\n"
" -o timeout=<number> I/O timeout (seconds)\n"
" 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"
File diff suppressed because it is too large Load Diff