mirror of
https://github.com/qemu/qemu.git
synced 2025-08-14 20:31:47 +00:00
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> -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEERfXHG0oMt/uXep+pBRYzHrxb/ecFAl+XGGAACgkQBRYzHrxb /efeDw/8Dz+yKjdV0mqzdOJ/2lg4etlDRqG7P65W9G79ZIthAOZVIH5p3yv+qzdN a+QTGWquCA/gCCfl29QHU2zC78PcBjP/ugm6icqW126MmJmHR/rOZx+RYC+0W4+0 2YA2HzwtKpolrMuzKoddHwsYoIF2Uw6l1oZK1QOl1hjqL2q47VRDvCs6H7vpvGn1 dGd+xkXMSYCVL4Lq+zAaIg6ZfTtCIlwJ+LMCoT/Wy7eZeB338T9Bz5iLl7BTqF2x GBv2eDw0xibYw+3d8zX9k76irKdLYPgJiaskjsGNWxLgtSYEOmCrDPzIMDbe34lS u4JlqRdmc62YoE5X1oI6tF8XSaD+O/PS1CT9O9IttDuHNVctg0zCqjTxONJn9IQk CmszvoGScnaH4PqWueR47wDjKdFq8p5nryODtmuYILjvAXPYJp0Vt6JDJ6ZZcS+t YwRYltCK7ToKiteTuosusQ/Vzk2kq4U6znWZsZH1LcyNEVaJNUIoIjYZrxKugG/F yuAeWinoG37N6gwx5GfUoIO/eRd8UyBmKEaeY7RpJfo+UnFpErg4+uEfZ5gbD1/0 YtQmBXHrXnsZB//wTw0gUob0sDKoII21H5EcA4QiDpgci9Q5saL/XG55TDLNZ62d uUH7PbIna8KpoKRBjEzz5e71FBTF0sKshzrZFHjfmhZ9HiM6XeE= =dTCm -----END PGP SIGNATURE----- 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:
commit
725ca3313a
8
configure
vendored
8
configure
vendored
@ -302,6 +302,7 @@ fdt="auto"
|
|||||||
netmap="no"
|
netmap="no"
|
||||||
sdl="auto"
|
sdl="auto"
|
||||||
sdl_image="auto"
|
sdl_image="auto"
|
||||||
|
virtiofsd="auto"
|
||||||
virtfs=""
|
virtfs=""
|
||||||
libudev="auto"
|
libudev="auto"
|
||||||
mpath="auto"
|
mpath="auto"
|
||||||
@ -999,6 +1000,10 @@ for opt do
|
|||||||
;;
|
;;
|
||||||
--enable-libudev) libudev="enabled"
|
--enable-libudev) libudev="enabled"
|
||||||
;;
|
;;
|
||||||
|
--disable-virtiofsd) virtiofsd="disabled"
|
||||||
|
;;
|
||||||
|
--enable-virtiofsd) virtiofsd="enabled"
|
||||||
|
;;
|
||||||
--disable-mpath) mpath="disabled"
|
--disable-mpath) mpath="disabled"
|
||||||
;;
|
;;
|
||||||
--enable-mpath) mpath="enabled"
|
--enable-mpath) mpath="enabled"
|
||||||
@ -1758,6 +1763,7 @@ disabled with --disable-FEATURE, default is enabled if available:
|
|||||||
vnc-png PNG compression for VNC server
|
vnc-png PNG compression for VNC server
|
||||||
cocoa Cocoa UI (Mac OS X only)
|
cocoa Cocoa UI (Mac OS X only)
|
||||||
virtfs VirtFS
|
virtfs VirtFS
|
||||||
|
virtiofsd build virtiofs daemon (virtiofsd)
|
||||||
libudev Use libudev to enumerate host devices
|
libudev Use libudev to enumerate host devices
|
||||||
mpath Multipath persistent reservation passthrough
|
mpath Multipath persistent reservation passthrough
|
||||||
xen xen backend driver support
|
xen xen backend driver support
|
||||||
@ -6972,7 +6978,7 @@ NINJA=$ninja $meson setup \
|
|||||||
-Dxen=$xen -Dxen_pci_passthrough=$xen_pci_passthrough -Dtcg=$tcg \
|
-Dxen=$xen -Dxen_pci_passthrough=$xen_pci_passthrough -Dtcg=$tcg \
|
||||||
-Dcocoa=$cocoa -Dmpath=$mpath -Dsdl=$sdl -Dsdl_image=$sdl_image \
|
-Dcocoa=$cocoa -Dmpath=$mpath -Dsdl=$sdl -Dsdl_image=$sdl_image \
|
||||||
-Dvnc=$vnc -Dvnc_sasl=$vnc_sasl -Dvnc_jpeg=$vnc_jpeg -Dvnc_png=$vnc_png \
|
-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 \
|
-Dcapstone=$capstone -Dslirp=$slirp -Dfdt=$fdt \
|
||||||
-Diconv=$iconv -Dcurses=$curses -Dlibudev=$libudev\
|
-Diconv=$iconv -Dcurses=$curses -Dlibudev=$libudev\
|
||||||
-Ddocs=$docs -Dsphinx_build=$sphinx_build -Dinstall_blobs=$blobs \
|
-Ddocs=$docs -Dsphinx_build=$sphinx_build -Dinstall_blobs=$blobs \
|
||||||
|
@ -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
|
but should work with any virtual machine monitor (VMM) that supports
|
||||||
vhost-user. See the Examples section below.
|
vhost-user. See the Examples section below.
|
||||||
|
|
||||||
This program must be run as the root user. Upon startup the program will
|
This program must be run as the root user. The program drops privileges where
|
||||||
switch into a new file system namespace with the shared directory tree as its
|
possible during startup although it must be able to create and access files
|
||||||
root. This prevents "file system escapes" due to symlinks and other file
|
with any uid/gid:
|
||||||
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
|
* The ability to invoke syscalls is limited using seccomp(2).
|
||||||
vectors that could allow an attacker to compromise the system after gaining
|
* Linux capabilities(7) are dropped.
|
||||||
control of the virtiofsd process.
|
|
||||||
|
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
|
Options
|
||||||
-------
|
-------
|
||||||
@ -69,6 +80,13 @@ Options
|
|||||||
* readdirplus|no_readdirplus -
|
* readdirplus|no_readdirplus -
|
||||||
Enable/disable readdirplus. The default is ``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 -
|
* source=PATH -
|
||||||
Share host directory tree located at PATH. This option is required.
|
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.
|
timeout. ``always`` sets a long cache lifetime at the expense of coherency.
|
||||||
The default is ``auto``.
|
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
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ struct fuse_attr {
|
|||||||
uint32_t gid;
|
uint32_t gid;
|
||||||
uint32_t rdev;
|
uint32_t rdev;
|
||||||
uint32_t blksize;
|
uint32_t blksize;
|
||||||
uint32_t padding;
|
uint32_t flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fuse_kstatfs {
|
struct fuse_kstatfs {
|
||||||
@ -310,6 +310,7 @@ struct fuse_file_lock {
|
|||||||
* FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir
|
* FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir
|
||||||
* FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request
|
* FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request
|
||||||
* FUSE_MAP_ALIGNMENT: map_alignment field is valid
|
* 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_ASYNC_READ (1 << 0)
|
||||||
#define FUSE_POSIX_LOCKS (1 << 1)
|
#define FUSE_POSIX_LOCKS (1 << 1)
|
||||||
@ -338,6 +339,7 @@ struct fuse_file_lock {
|
|||||||
#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
|
#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
|
||||||
#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
|
#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
|
||||||
#define FUSE_MAP_ALIGNMENT (1 << 26)
|
#define FUSE_MAP_ALIGNMENT (1 << 26)
|
||||||
|
#define FUSE_ATTR_FLAGS (1 << 27)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CUSE INIT request/reply flags
|
* CUSE INIT request/reply flags
|
||||||
@ -413,6 +415,13 @@ struct fuse_file_lock {
|
|||||||
*/
|
*/
|
||||||
#define FUSE_FSYNC_FDATASYNC (1 << 0)
|
#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 {
|
enum fuse_opcode {
|
||||||
FUSE_LOOKUP = 1,
|
FUSE_LOOKUP = 1,
|
||||||
FUSE_FORGET = 2, /* no reply */
|
FUSE_FORGET = 2, /* no reply */
|
||||||
|
@ -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 (rw)': config_host['CONFIG_BDRV_RW_WHITELIST']}
|
||||||
summary_info += {'Block whitelist (ro)': config_host['CONFIG_BDRV_RO_WHITELIST']}
|
summary_info += {'Block whitelist (ro)': config_host['CONFIG_BDRV_RO_WHITELIST']}
|
||||||
summary_info += {'VirtFS support': config_host.has_key('CONFIG_VIRTFS')}
|
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 += {'Multipath support': mpathpersist.found()}
|
||||||
summary_info += {'VNC support': vnc.found()}
|
summary_info += {'VNC support': vnc.found()}
|
||||||
if vnc.found()
|
if vnc.found()
|
||||||
|
@ -62,6 +62,8 @@ option('vnc_sasl', type : 'feature', value : 'auto',
|
|||||||
description: 'SASL authentication for VNC server')
|
description: 'SASL authentication for VNC server')
|
||||||
option('xkbcommon', type : 'feature', value : 'auto',
|
option('xkbcommon', type : 'feature', value : 'auto',
|
||||||
description: 'xkbcommon support')
|
description: 'xkbcommon support')
|
||||||
|
option('virtiofsd', type: 'feature', value: 'auto',
|
||||||
|
description: 'build virtiofs daemon (virtiofsd)')
|
||||||
|
|
||||||
option('capstone', type: 'combo', value: 'auto',
|
option('capstone', type: 'combo', value: 'auto',
|
||||||
choices: ['disabled', 'enabled', 'auto', 'system', 'internal'],
|
choices: ['disabled', 'enabled', 'auto', 'system', 'internal'],
|
||||||
|
@ -57,7 +57,7 @@ def download_boot(self):
|
|||||||
self.cancel('Failed to download/prepare boot image')
|
self.cancel('Failed to download/prepare boot image')
|
||||||
return boot.path
|
return boot.path
|
||||||
|
|
||||||
def download_cloudinit(self):
|
def download_cloudinit(self, ssh_pubkey=None):
|
||||||
self.log.info('Preparing cloudinit image')
|
self.log.info('Preparing cloudinit image')
|
||||||
try:
|
try:
|
||||||
cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
|
cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
|
||||||
@ -67,7 +67,8 @@ def download_cloudinit(self):
|
|||||||
password='password',
|
password='password',
|
||||||
# QEMU's hard coded usermode router address
|
# QEMU's hard coded usermode router address
|
||||||
phone_home_host='10.0.2.2',
|
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:
|
except Exception:
|
||||||
self.cancel('Failed to prepared cloudinit image')
|
self.cancel('Failed to prepared cloudinit image')
|
||||||
return cloudinit_iso
|
return cloudinit_iso
|
||||||
@ -80,19 +81,19 @@ class BootLinux(BootLinuxBase):
|
|||||||
timeout = 900
|
timeout = 900
|
||||||
chksum = None
|
chksum = None
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self, ssh_pubkey=None):
|
||||||
super(BootLinux, self).setUp()
|
super(BootLinux, self).setUp()
|
||||||
self.vm.add_args('-smp', '2')
|
self.vm.add_args('-smp', '2')
|
||||||
self.vm.add_args('-m', '1024')
|
self.vm.add_args('-m', '1024')
|
||||||
self.prepare_boot()
|
self.prepare_boot()
|
||||||
self.prepare_cloudinit()
|
self.prepare_cloudinit(ssh_pubkey)
|
||||||
|
|
||||||
def prepare_boot(self):
|
def prepare_boot(self):
|
||||||
path = self.download_boot()
|
path = self.download_boot()
|
||||||
self.vm.add_args('-drive', 'file=%s' % path)
|
self.vm.add_args('-drive', 'file=%s' % path)
|
||||||
|
|
||||||
def prepare_cloudinit(self):
|
def prepare_cloudinit(self, ssh_pubkey=None):
|
||||||
cloudinit_iso = self.download_cloudinit()
|
cloudinit_iso = self.download_cloudinit(ssh_pubkey)
|
||||||
self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
|
self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
|
||||||
|
|
||||||
def launch_and_wait(self):
|
def launch_and_wait(self):
|
||||||
|
289
tests/acceptance/virtiofs_submounts.py
Normal file
289
tests/acceptance/virtiofs_submounts.py
Normal 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()
|
46
tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
Normal file
46
tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
Normal file
@ -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.'
|
30
tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
Normal file
30
tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
Normal file
@ -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.'
|
138
tests/acceptance/virtiofs_submounts.py.data/guest.sh
Normal file
138
tests/acceptance/virtiofs_submounts.py.data/guest.sh
Normal file
@ -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."
|
127
tests/acceptance/virtiofs_submounts.py.data/host.sh
Normal file
127
tests/acceptance/virtiofs_submounts.py.data/host.sh
Normal file
@ -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.'
|
@ -1,10 +1,23 @@
|
|||||||
have_virtiofsd = (have_system and
|
have_virtiofsd = (targetos == 'linux' and
|
||||||
have_tools and
|
have_tools and
|
||||||
'CONFIG_LINUX' in config_host and
|
|
||||||
'CONFIG_SECCOMP' in config_host and
|
'CONFIG_SECCOMP' in config_host and
|
||||||
'CONFIG_LIBCAP_NG' in config_host and
|
'CONFIG_LIBCAP_NG' in config_host and
|
||||||
'CONFIG_VHOST_USER' in config_host)
|
'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
|
if have_virtiofsd
|
||||||
subdir('virtiofsd')
|
subdir('virtiofsd')
|
||||||
endif
|
endif
|
||||||
|
@ -352,6 +352,14 @@ struct fuse_file_info {
|
|||||||
*/
|
*/
|
||||||
#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
|
#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
|
* Ioctl flags
|
||||||
*
|
*
|
||||||
|
@ -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)
|
const struct fuse_entry_param *e)
|
||||||
{
|
{
|
||||||
*arg = (struct fuse_entry_out){
|
*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),
|
.attr_valid_nsec = calc_timeout_nsec(e->attr_timeout),
|
||||||
};
|
};
|
||||||
convert_stat(&e->attr, &arg->attr);
|
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;
|
struct fuse_direntplus *dp = (struct fuse_direntplus *)buf;
|
||||||
memset(&dp->entry_out, 0, sizeof(dp->entry_out));
|
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;
|
struct fuse_dirent *dirent = &dp->dirent;
|
||||||
*dirent = (struct fuse_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);
|
size_t size = sizeof(arg);
|
||||||
|
|
||||||
memset(&arg, 0, sizeof(arg));
|
memset(&arg, 0, sizeof(arg));
|
||||||
fill_entry(&arg, e);
|
fill_entry(req->se, &arg, e);
|
||||||
return send_reply_ok(req, &arg, size);
|
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);
|
struct fuse_open_out *oarg = (struct fuse_open_out *)(buf + entrysize);
|
||||||
|
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
fill_entry(earg, e);
|
fill_entry(req->se, earg, e);
|
||||||
fill_open(oarg, f);
|
fill_open(oarg, f);
|
||||||
return send_reply_ok(req, buf, entrysize + sizeof(struct fuse_open_out));
|
return send_reply_ok(req, buf, entrysize + sizeof(struct fuse_open_out));
|
||||||
}
|
}
|
||||||
|
|
||||||
int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
|
int fuse_reply_attr_with_flags(fuse_req_t req, const struct stat *attr,
|
||||||
double attr_timeout)
|
double attr_timeout, uint32_t attr_flags)
|
||||||
{
|
{
|
||||||
struct fuse_attr_out arg;
|
struct fuse_attr_out arg;
|
||||||
size_t size = sizeof(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);
|
arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
|
||||||
convert_stat(attr, &arg.attr);
|
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);
|
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)
|
int fuse_reply_readlink(fuse_req_t req, const char *linkname)
|
||||||
{
|
{
|
||||||
return send_reply_ok(req, linkname, strlen(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;
|
bufsize = max_bufsize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (arg->flags & FUSE_ATTR_FLAGS) {
|
||||||
|
se->conn.capable |= FUSE_CAP_ATTR_FLAGS;
|
||||||
|
}
|
||||||
#ifdef HAVE_SPLICE
|
#ifdef HAVE_SPLICE
|
||||||
#ifdef HAVE_VMSPLICE
|
#ifdef HAVE_VMSPLICE
|
||||||
se->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
|
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_ASYNC_DIO);
|
||||||
LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
|
LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
|
||||||
LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
|
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.write_buf, FUSE_CAP_SPLICE_READ);
|
||||||
LL_SET_DEFAULT(se->op.getlk && se->op.setlk, FUSE_CAP_POSIX_LOCKS);
|
LL_SET_DEFAULT(se->op.getlk && se->op.setlk, FUSE_CAP_POSIX_LOCKS);
|
||||||
LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_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) {
|
if (se->conn.want & FUSE_CAP_POSIX_ACL) {
|
||||||
outarg.flags |= FUSE_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_readahead = se->conn.max_readahead;
|
||||||
outarg.max_write = se->conn.max_write;
|
outarg.max_write = se->conn.max_write;
|
||||||
if (se->conn.max_background >= (1 << 16)) {
|
if (se->conn.max_background >= (1 << 16)) {
|
||||||
|
@ -102,6 +102,11 @@ struct fuse_entry_param {
|
|||||||
* large value.
|
* large value.
|
||||||
*/
|
*/
|
||||||
double entry_timeout;
|
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,
|
int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
|
||||||
double attr_timeout);
|
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
|
* Reply with the contents of a symbolic link
|
||||||
*
|
*
|
||||||
|
@ -166,10 +166,19 @@ void fuse_cmdline_help(void)
|
|||||||
" enable/disable readirplus\n"
|
" enable/disable readirplus\n"
|
||||||
" default: readdirplus except with "
|
" default: readdirplus except with "
|
||||||
"cache=none\n"
|
"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"
|
" -o timeout=<number> I/O timeout (seconds)\n"
|
||||||
" default: depends on cache= option.\n"
|
" default: depends on cache= option.\n"
|
||||||
" -o writeback|no_writeback enable/disable writeback cache\n"
|
" -o writeback|no_writeback enable/disable writeback cache\n"
|
||||||
" default: no_writeback\n"
|
" default: no_writeback\n"
|
||||||
|
" -o announce_submounts Announce sub-mount points to the guest\n"
|
||||||
" -o xattr|no_xattr enable/disable xattr\n"
|
" -o xattr|no_xattr enable/disable xattr\n"
|
||||||
" default: no_xattr\n"
|
" default: no_xattr\n"
|
||||||
" -o modcaps=CAPLIST Modify the list of capabilities\n"
|
" -o modcaps=CAPLIST Modify the list of capabilities\n"
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include "fuse_virtio.h"
|
#include "fuse_virtio.h"
|
||||||
#include "fuse_log.h"
|
#include "fuse_log.h"
|
||||||
#include "fuse_lowlevel.h"
|
#include "fuse_lowlevel.h"
|
||||||
|
#include "standard-headers/linux/fuse.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <cap-ng.h>
|
#include <cap-ng.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
@ -64,6 +65,7 @@
|
|||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "qemu/cutils.h"
|
||||||
#include "passthrough_helpers.h"
|
#include "passthrough_helpers.h"
|
||||||
#include "passthrough_seccomp.h"
|
#include "passthrough_seccomp.h"
|
||||||
|
|
||||||
@ -124,6 +126,14 @@ struct lo_inode {
|
|||||||
GHashTable *posix_locks; /* protected by lo_inode->plock_mutex */
|
GHashTable *posix_locks; /* protected by lo_inode->plock_mutex */
|
||||||
|
|
||||||
mode_t filetype;
|
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 {
|
struct lo_cred {
|
||||||
@ -137,13 +147,26 @@ enum {
|
|||||||
CACHE_ALWAYS,
|
CACHE_ALWAYS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SANDBOX_NAMESPACE,
|
||||||
|
SANDBOX_CHROOT,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct xattr_map_entry {
|
||||||
|
char *key;
|
||||||
|
char *prepend;
|
||||||
|
unsigned int flags;
|
||||||
|
} XattrMapEntry;
|
||||||
|
|
||||||
struct lo_data {
|
struct lo_data {
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
int sandbox;
|
||||||
int debug;
|
int debug;
|
||||||
int writeback;
|
int writeback;
|
||||||
int flock;
|
int flock;
|
||||||
int posix_lock;
|
int posix_lock;
|
||||||
int xattr;
|
int xattr;
|
||||||
|
char *xattrmap;
|
||||||
char *source;
|
char *source;
|
||||||
char *modcaps;
|
char *modcaps;
|
||||||
double timeout;
|
double timeout;
|
||||||
@ -151,18 +174,27 @@ struct lo_data {
|
|||||||
int timeout_set;
|
int timeout_set;
|
||||||
int readdirplus_set;
|
int readdirplus_set;
|
||||||
int readdirplus_clear;
|
int readdirplus_clear;
|
||||||
|
int announce_submounts;
|
||||||
int allow_direct_io;
|
int allow_direct_io;
|
||||||
struct lo_inode root;
|
struct lo_inode root;
|
||||||
GHashTable *inodes; /* protected by lo->mutex */
|
GHashTable *inodes; /* protected by lo->mutex */
|
||||||
struct lo_map ino_map; /* protected by lo->mutex */
|
struct lo_map ino_map; /* protected by lo->mutex */
|
||||||
struct lo_map dirp_map; /* protected by lo->mutex */
|
struct lo_map dirp_map; /* protected by lo->mutex */
|
||||||
struct lo_map fd_map; /* protected by lo->mutex */
|
struct lo_map fd_map; /* protected by lo->mutex */
|
||||||
|
XattrMapEntry *xattr_map_list;
|
||||||
|
size_t xattr_map_nentries;
|
||||||
|
|
||||||
/* An O_PATH file descriptor to /proc/self/fd/ */
|
/* An O_PATH file descriptor to /proc/self/fd/ */
|
||||||
int proc_self_fd;
|
int proc_self_fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct fuse_opt lo_opts[] = {
|
static const struct fuse_opt lo_opts[] = {
|
||||||
|
{ "sandbox=namespace",
|
||||||
|
offsetof(struct lo_data, sandbox),
|
||||||
|
SANDBOX_NAMESPACE },
|
||||||
|
{ "sandbox=chroot",
|
||||||
|
offsetof(struct lo_data, sandbox),
|
||||||
|
SANDBOX_CHROOT },
|
||||||
{ "writeback", offsetof(struct lo_data, writeback), 1 },
|
{ "writeback", offsetof(struct lo_data, writeback), 1 },
|
||||||
{ "no_writeback", offsetof(struct lo_data, writeback), 0 },
|
{ "no_writeback", offsetof(struct lo_data, writeback), 0 },
|
||||||
{ "source=%s", offsetof(struct lo_data, source), 0 },
|
{ "source=%s", offsetof(struct lo_data, source), 0 },
|
||||||
@ -172,6 +204,7 @@ static const struct fuse_opt lo_opts[] = {
|
|||||||
{ "no_posix_lock", offsetof(struct lo_data, posix_lock), 0 },
|
{ "no_posix_lock", offsetof(struct lo_data, posix_lock), 0 },
|
||||||
{ "xattr", offsetof(struct lo_data, xattr), 1 },
|
{ "xattr", offsetof(struct lo_data, xattr), 1 },
|
||||||
{ "no_xattr", offsetof(struct lo_data, xattr), 0 },
|
{ "no_xattr", offsetof(struct lo_data, xattr), 0 },
|
||||||
|
{ "xattrmap=%s", offsetof(struct lo_data, xattrmap), 0 },
|
||||||
{ "modcaps=%s", offsetof(struct lo_data, modcaps), 0 },
|
{ "modcaps=%s", offsetof(struct lo_data, modcaps), 0 },
|
||||||
{ "timeout=%lf", offsetof(struct lo_data, timeout), 0 },
|
{ "timeout=%lf", offsetof(struct lo_data, timeout), 0 },
|
||||||
{ "timeout=", offsetof(struct lo_data, timeout_set), 1 },
|
{ "timeout=", offsetof(struct lo_data, timeout_set), 1 },
|
||||||
@ -180,6 +213,7 @@ static const struct fuse_opt lo_opts[] = {
|
|||||||
{ "cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS },
|
{ "cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS },
|
||||||
{ "readdirplus", offsetof(struct lo_data, readdirplus_set), 1 },
|
{ "readdirplus", offsetof(struct lo_data, readdirplus_set), 1 },
|
||||||
{ "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 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 },
|
{ "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
|
||||||
{ "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
|
{ "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
|
||||||
FUSE_OPT_END
|
FUSE_OPT_END
|
||||||
@ -577,22 +611,52 @@ 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,
|
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
|
||||||
struct fuse_file_info *fi)
|
struct fuse_file_info *fi)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
struct stat buf;
|
struct stat buf;
|
||||||
struct lo_data *lo = lo_data(req);
|
struct lo_data *lo = lo_data(req);
|
||||||
|
struct lo_inode *inode = lo_inode(req, ino);
|
||||||
|
uint32_t fuse_attr_flags = 0;
|
||||||
|
|
||||||
(void)fi;
|
(void)fi;
|
||||||
|
|
||||||
res =
|
res = do_fstatat(inode->fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
|
||||||
fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
inode->parent_dev, &fuse_attr_flags);
|
||||||
|
lo_inode_put(lo, &inode);
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
return (void)fuse_reply_err(req, errno);
|
return (void)fuse_reply_err(req, errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
fuse_reply_attr(req, &buf, lo->timeout);
|
if (!lo->announce_submounts) {
|
||||||
|
fuse_attr_flags &= ~FUSE_ATTR_SUBMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fuse_reply_attr_with_flags(req, &buf, lo->timeout, fuse_attr_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lo_fi_fd(fuse_req_t req, struct fuse_file_info *fi)
|
static int lo_fi_fd(fuse_req_t req, struct fuse_file_info *fi)
|
||||||
@ -788,11 +852,16 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
|
|||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
res = do_fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
|
||||||
|
dir->key.dev, &e->attr_flags);
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!lo->announce_submounts) {
|
||||||
|
e->attr_flags &= ~FUSE_ATTR_SUBMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
inode = lo_find(lo, &e->attr);
|
inode = lo_find(lo, &e->attr);
|
||||||
if (inode) {
|
if (inode) {
|
||||||
close(newfd);
|
close(newfd);
|
||||||
@ -824,6 +893,7 @@ 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);
|
g_hash_table_insert(lo->inodes, &inode->key, inode);
|
||||||
pthread_mutex_unlock(&lo->mutex);
|
pthread_mutex_unlock(&lo->mutex);
|
||||||
}
|
}
|
||||||
|
inode->parent_dev = dir->key.dev;
|
||||||
e->ino = inode->fuse_ino;
|
e->ino = inode->fuse_ino;
|
||||||
lo_inode_put(lo, &inode);
|
lo_inode_put(lo, &inode);
|
||||||
lo_inode_put(lo, &dir);
|
lo_inode_put(lo, &dir);
|
||||||
@ -1037,11 +1107,17 @@ static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
|
|||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
res = do_fstatat(inode->fd, "", &e.attr,
|
||||||
|
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
|
||||||
|
parent_inode->key.dev, &e.attr_flags);
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!lo->announce_submounts) {
|
||||||
|
e.attr_flags &= ~FUSE_ATTR_SUBMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&lo->mutex);
|
pthread_mutex_lock(&lo->mutex);
|
||||||
inode->nlookup++;
|
inode->nlookup++;
|
||||||
pthread_mutex_unlock(&lo->mutex);
|
pthread_mutex_unlock(&lo->mutex);
|
||||||
@ -1050,6 +1126,14 @@ 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,
|
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", (unsigned long long)parent,
|
||||||
name, (unsigned long long)e.ino);
|
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);
|
fuse_reply_entry(req, &e);
|
||||||
lo_inode_put(lo, &parent_inode);
|
lo_inode_put(lo, &parent_inode);
|
||||||
lo_inode_put(lo, &inode);
|
lo_inode_put(lo, &inode);
|
||||||
@ -1068,14 +1152,21 @@ static struct lo_inode *lookup_name(fuse_req_t req, fuse_ino_t parent,
|
|||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
struct stat attr;
|
struct stat attr;
|
||||||
|
struct lo_data *lo = lo_data(req);
|
||||||
|
struct lo_inode *dir = lo_inode(req, parent);
|
||||||
|
|
||||||
res = fstatat(lo_fd(req, parent), name, &attr,
|
if (!dir) {
|
||||||
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = do_fstatat(dir->fd, name, &attr,
|
||||||
|
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, dir->key.dev, NULL);
|
||||||
|
lo_inode_put(lo, &dir);
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lo_find(lo_data(req), &attr);
|
return lo_find(lo, &attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
|
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
|
||||||
@ -2010,20 +2101,383 @@ static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
|
|||||||
fuse_reply_err(req, res == -1 ? errno : 0);
|
fuse_reply_err(req, res == -1 ? errno : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
|
/* types */
|
||||||
|
/*
|
||||||
|
* Exit; process attribute unmodified if matched.
|
||||||
|
* An empty key applies to all.
|
||||||
|
*/
|
||||||
|
#define XATTR_MAP_FLAG_OK (1 << 0)
|
||||||
|
/*
|
||||||
|
* The attribute is unwanted;
|
||||||
|
* EPERM on write, hidden on read.
|
||||||
|
*/
|
||||||
|
#define XATTR_MAP_FLAG_BAD (1 << 1)
|
||||||
|
/*
|
||||||
|
* For attr that start with 'key' prepend 'prepend'
|
||||||
|
* 'key' may be empty to prepend for all attrs
|
||||||
|
* key is defined from set/remove point of view.
|
||||||
|
* Automatically reversed on read
|
||||||
|
*/
|
||||||
|
#define XATTR_MAP_FLAG_PREFIX (1 << 2)
|
||||||
|
|
||||||
|
/* scopes */
|
||||||
|
/* Apply rule to get/set/remove */
|
||||||
|
#define XATTR_MAP_FLAG_CLIENT (1 << 16)
|
||||||
|
/* Apply rule to list */
|
||||||
|
#define XATTR_MAP_FLAG_SERVER (1 << 17)
|
||||||
|
/* Apply rule to all */
|
||||||
|
#define XATTR_MAP_FLAG_ALL (XATTR_MAP_FLAG_SERVER | XATTR_MAP_FLAG_CLIENT)
|
||||||
|
|
||||||
|
static void add_xattrmap_entry(struct lo_data *lo,
|
||||||
|
const XattrMapEntry *new_entry)
|
||||||
|
{
|
||||||
|
XattrMapEntry *res = g_realloc_n(lo->xattr_map_list,
|
||||||
|
lo->xattr_map_nentries + 1,
|
||||||
|
sizeof(XattrMapEntry));
|
||||||
|
res[lo->xattr_map_nentries++] = *new_entry;
|
||||||
|
|
||||||
|
lo->xattr_map_list = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_xattrmap(struct lo_data *lo)
|
||||||
|
{
|
||||||
|
XattrMapEntry *map = lo->xattr_map_list;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (!map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < lo->xattr_map_nentries; i++) {
|
||||||
|
g_free(map[i].key);
|
||||||
|
g_free(map[i].prepend);
|
||||||
|
};
|
||||||
|
|
||||||
|
g_free(map);
|
||||||
|
lo->xattr_map_list = NULL;
|
||||||
|
lo->xattr_map_nentries = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle the 'map' type, which is sugar for a set of commands
|
||||||
|
* for the common case of prefixing a subset or everything,
|
||||||
|
* and allowing anything not prefixed through.
|
||||||
|
* It must be the last entry in the stream, although there
|
||||||
|
* can be other entries before it.
|
||||||
|
* The form is:
|
||||||
|
* :map:key:prefix:
|
||||||
|
*
|
||||||
|
* key maybe empty in which case all entries are prefixed.
|
||||||
|
*/
|
||||||
|
static void parse_xattrmap_map(struct lo_data *lo,
|
||||||
|
const char *rule, char sep)
|
||||||
|
{
|
||||||
|
const char *tmp;
|
||||||
|
char *key;
|
||||||
|
char *prefix;
|
||||||
|
XattrMapEntry tmp_entry;
|
||||||
|
|
||||||
|
if (*rule != sep) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Expecting '%c' after 'map' keyword, found '%c'\n",
|
||||||
|
__func__, sep, *rule);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rule++;
|
||||||
|
|
||||||
|
/* At start of 'key' field */
|
||||||
|
tmp = strchr(rule, sep);
|
||||||
|
if (!tmp) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Missing '%c' at end of key field in map rule\n",
|
||||||
|
__func__, sep);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
key = g_strndup(rule, tmp - rule);
|
||||||
|
rule = tmp + 1;
|
||||||
|
|
||||||
|
/* At start of prefix field */
|
||||||
|
tmp = strchr(rule, sep);
|
||||||
|
if (!tmp) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Missing '%c' at end of prefix field in map rule\n",
|
||||||
|
__func__, sep);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = g_strndup(rule, tmp - rule);
|
||||||
|
rule = tmp + 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This should be the end of the string, we don't allow
|
||||||
|
* any more commands after 'map'.
|
||||||
|
*/
|
||||||
|
if (*rule) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Expecting end of command after map, found '%c'\n",
|
||||||
|
__func__, *rule);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1st: Prefix matches/everything */
|
||||||
|
tmp_entry.flags = XATTR_MAP_FLAG_PREFIX | XATTR_MAP_FLAG_ALL;
|
||||||
|
tmp_entry.key = g_strdup(key);
|
||||||
|
tmp_entry.prepend = g_strdup(prefix);
|
||||||
|
add_xattrmap_entry(lo, &tmp_entry);
|
||||||
|
|
||||||
|
if (!*key) {
|
||||||
|
/* Prefix all case */
|
||||||
|
|
||||||
|
/* 2nd: Hide any non-prefixed entries on the host */
|
||||||
|
tmp_entry.flags = XATTR_MAP_FLAG_BAD | XATTR_MAP_FLAG_ALL;
|
||||||
|
tmp_entry.key = g_strdup("");
|
||||||
|
tmp_entry.prepend = g_strdup("");
|
||||||
|
add_xattrmap_entry(lo, &tmp_entry);
|
||||||
|
} else {
|
||||||
|
/* Prefix matching case */
|
||||||
|
|
||||||
|
/* 2nd: Hide non-prefixed but matching entries on the host */
|
||||||
|
tmp_entry.flags = XATTR_MAP_FLAG_BAD | XATTR_MAP_FLAG_SERVER;
|
||||||
|
tmp_entry.key = g_strdup(""); /* Not used */
|
||||||
|
tmp_entry.prepend = g_strdup(key);
|
||||||
|
add_xattrmap_entry(lo, &tmp_entry);
|
||||||
|
|
||||||
|
/* 3rd: Stop the client accessing prefixed attributes directly */
|
||||||
|
tmp_entry.flags = XATTR_MAP_FLAG_BAD | XATTR_MAP_FLAG_CLIENT;
|
||||||
|
tmp_entry.key = g_strdup(prefix);
|
||||||
|
tmp_entry.prepend = g_strdup(""); /* Not used */
|
||||||
|
add_xattrmap_entry(lo, &tmp_entry);
|
||||||
|
|
||||||
|
/* 4th: Everything else is OK */
|
||||||
|
tmp_entry.flags = XATTR_MAP_FLAG_OK | XATTR_MAP_FLAG_ALL;
|
||||||
|
tmp_entry.key = g_strdup("");
|
||||||
|
tmp_entry.prepend = g_strdup("");
|
||||||
|
add_xattrmap_entry(lo, &tmp_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(key);
|
||||||
|
g_free(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_xattrmap(struct lo_data *lo)
|
||||||
|
{
|
||||||
|
const char *map = lo->xattrmap;
|
||||||
|
const char *tmp;
|
||||||
|
|
||||||
|
lo->xattr_map_nentries = 0;
|
||||||
|
while (*map) {
|
||||||
|
XattrMapEntry tmp_entry;
|
||||||
|
char sep;
|
||||||
|
|
||||||
|
if (isspace(*map)) {
|
||||||
|
map++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* The separator is the first non-space of the rule */
|
||||||
|
sep = *map++;
|
||||||
|
if (!sep) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp_entry.flags = 0;
|
||||||
|
/* Start of 'type' */
|
||||||
|
if (strstart(map, "prefix", &map)) {
|
||||||
|
tmp_entry.flags |= XATTR_MAP_FLAG_PREFIX;
|
||||||
|
} else if (strstart(map, "ok", &map)) {
|
||||||
|
tmp_entry.flags |= XATTR_MAP_FLAG_OK;
|
||||||
|
} else if (strstart(map, "bad", &map)) {
|
||||||
|
tmp_entry.flags |= XATTR_MAP_FLAG_BAD;
|
||||||
|
} else if (strstart(map, "map", &map)) {
|
||||||
|
/*
|
||||||
|
* map is sugar that adds a number of rules, and must be
|
||||||
|
* the last entry.
|
||||||
|
*/
|
||||||
|
parse_xattrmap_map(lo, map, sep);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Unexpected type;"
|
||||||
|
"Expecting 'prefix', 'ok', 'bad' or 'map' in rule %zu\n",
|
||||||
|
__func__, lo->xattr_map_nentries);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*map++ != sep) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Missing '%c' at end of type field of rule %zu\n",
|
||||||
|
__func__, sep, lo->xattr_map_nentries);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start of 'scope' */
|
||||||
|
if (strstart(map, "client", &map)) {
|
||||||
|
tmp_entry.flags |= XATTR_MAP_FLAG_CLIENT;
|
||||||
|
} else if (strstart(map, "server", &map)) {
|
||||||
|
tmp_entry.flags |= XATTR_MAP_FLAG_SERVER;
|
||||||
|
} else if (strstart(map, "all", &map)) {
|
||||||
|
tmp_entry.flags |= XATTR_MAP_FLAG_ALL;
|
||||||
|
} else {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Unexpected scope;"
|
||||||
|
" Expecting 'client', 'server', or 'all', in rule %zu\n",
|
||||||
|
__func__, lo->xattr_map_nentries);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*map++ != sep) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Expecting '%c' found '%c'"
|
||||||
|
" after scope in rule %zu\n",
|
||||||
|
__func__, sep, *map, lo->xattr_map_nentries);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* At start of 'key' field */
|
||||||
|
tmp = strchr(map, sep);
|
||||||
|
if (!tmp) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Missing '%c' at end of key field of rule %zu",
|
||||||
|
__func__, sep, lo->xattr_map_nentries);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
tmp_entry.key = g_strndup(map, tmp - map);
|
||||||
|
map = tmp + 1;
|
||||||
|
|
||||||
|
/* At start of 'prepend' field */
|
||||||
|
tmp = strchr(map, sep);
|
||||||
|
if (!tmp) {
|
||||||
|
fuse_log(FUSE_LOG_ERR,
|
||||||
|
"%s: Missing '%c' at end of prepend field of rule %zu",
|
||||||
|
__func__, sep, lo->xattr_map_nentries);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
tmp_entry.prepend = g_strndup(map, tmp - map);
|
||||||
|
map = tmp + 1;
|
||||||
|
|
||||||
|
add_xattrmap_entry(lo, &tmp_entry);
|
||||||
|
/* End of rule - go around again for another rule */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lo->xattr_map_nentries) {
|
||||||
|
fuse_log(FUSE_LOG_ERR, "Empty xattr map\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For use with getxattr/setxattr/removexattr, where the client
|
||||||
|
* gives us a name and we may need to choose a different one.
|
||||||
|
* Allocates a buffer for the result placing it in *out_name.
|
||||||
|
* If there's no change then *out_name is not set.
|
||||||
|
* Returns 0 on success
|
||||||
|
* Can return -EPERM to indicate we block a given attribute
|
||||||
|
* (in which case out_name is not allocated)
|
||||||
|
* Can return -ENOMEM to indicate out_name couldn't be allocated.
|
||||||
|
*/
|
||||||
|
static int xattr_map_client(const struct lo_data *lo, const char *client_name,
|
||||||
|
char **out_name)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < lo->xattr_map_nentries; i++) {
|
||||||
|
const XattrMapEntry *cur_entry = lo->xattr_map_list + i;
|
||||||
|
|
||||||
|
if ((cur_entry->flags & XATTR_MAP_FLAG_CLIENT) &&
|
||||||
|
(strstart(client_name, cur_entry->key, NULL))) {
|
||||||
|
if (cur_entry->flags & XATTR_MAP_FLAG_BAD) {
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
if (cur_entry->flags & XATTR_MAP_FLAG_OK) {
|
||||||
|
/* Unmodified name */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (cur_entry->flags & XATTR_MAP_FLAG_PREFIX) {
|
||||||
|
*out_name = g_try_malloc(strlen(client_name) +
|
||||||
|
strlen(cur_entry->prepend) + 1);
|
||||||
|
if (!*out_name) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
sprintf(*out_name, "%s%s", cur_entry->prepend, client_name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For use with listxattr where the server fs gives us a name and we may need
|
||||||
|
* to sanitize this for the client.
|
||||||
|
* Returns a pointer to the result in *out_name
|
||||||
|
* This is always the original string or the current string with some prefix
|
||||||
|
* removed; no reallocation is done.
|
||||||
|
* Returns 0 on success
|
||||||
|
* Can return -ENODATA to indicate the name should be dropped from the list.
|
||||||
|
*/
|
||||||
|
static int xattr_map_server(const struct lo_data *lo, const char *server_name,
|
||||||
|
const char **out_name)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
const char *end;
|
||||||
|
|
||||||
|
for (i = 0; i < lo->xattr_map_nentries; i++) {
|
||||||
|
const XattrMapEntry *cur_entry = lo->xattr_map_list + i;
|
||||||
|
|
||||||
|
if ((cur_entry->flags & XATTR_MAP_FLAG_SERVER) &&
|
||||||
|
(strstart(server_name, cur_entry->prepend, &end))) {
|
||||||
|
if (cur_entry->flags & XATTR_MAP_FLAG_BAD) {
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
if (cur_entry->flags & XATTR_MAP_FLAG_OK) {
|
||||||
|
*out_name = server_name;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (cur_entry->flags & XATTR_MAP_FLAG_PREFIX) {
|
||||||
|
/* Remove prefix */
|
||||||
|
*out_name = end;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *in_name,
|
||||||
size_t size)
|
size_t size)
|
||||||
{
|
{
|
||||||
struct lo_data *lo = lo_data(req);
|
struct lo_data *lo = lo_data(req);
|
||||||
char *value = NULL;
|
char *value = NULL;
|
||||||
char procname[64];
|
char procname[64];
|
||||||
|
const char *name;
|
||||||
|
char *mapped_name;
|
||||||
struct lo_inode *inode;
|
struct lo_inode *inode;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
int saverr;
|
int saverr;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
|
||||||
|
mapped_name = NULL;
|
||||||
|
name = in_name;
|
||||||
|
if (lo->xattrmap) {
|
||||||
|
ret = xattr_map_client(lo, in_name, &mapped_name);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (ret == -EPERM) {
|
||||||
|
ret = -ENODATA;
|
||||||
|
}
|
||||||
|
fuse_reply_err(req, -ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mapped_name) {
|
||||||
|
name = mapped_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inode = lo_inode(req, ino);
|
inode = lo_inode(req, ino);
|
||||||
if (!inode) {
|
if (!inode) {
|
||||||
fuse_reply_err(req, EBADF);
|
fuse_reply_err(req, EBADF);
|
||||||
|
g_free(mapped_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2088,6 +2542,7 @@ out_err:
|
|||||||
saverr = errno;
|
saverr = errno;
|
||||||
out:
|
out:
|
||||||
fuse_reply_err(req, saverr);
|
fuse_reply_err(req, saverr);
|
||||||
|
g_free(mapped_name);
|
||||||
goto out_free;
|
goto out_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2144,8 +2599,60 @@ static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
|
|||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lo->xattr_map_list) {
|
||||||
|
/*
|
||||||
|
* Map the names back, some attributes might be dropped,
|
||||||
|
* some shortened, but not increased, so we shouldn't
|
||||||
|
* run out of room.
|
||||||
|
*/
|
||||||
|
size_t out_index, in_index;
|
||||||
|
out_index = 0;
|
||||||
|
in_index = 0;
|
||||||
|
while (in_index < ret) {
|
||||||
|
const char *map_out;
|
||||||
|
char *in_ptr = value + in_index;
|
||||||
|
/* Length of current attribute name */
|
||||||
|
size_t in_len = strlen(value + in_index) + 1;
|
||||||
|
|
||||||
|
int mapret = xattr_map_server(lo, in_ptr, &map_out);
|
||||||
|
if (mapret != -ENODATA && mapret != 0) {
|
||||||
|
/* Shouldn't happen */
|
||||||
|
saverr = -mapret;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (mapret == 0) {
|
||||||
|
/* Either unchanged, or truncated */
|
||||||
|
size_t out_len;
|
||||||
|
if (map_out != in_ptr) {
|
||||||
|
/* +1 copies the NIL */
|
||||||
|
out_len = strlen(map_out) + 1;
|
||||||
|
} else {
|
||||||
|
/* No change */
|
||||||
|
out_len = in_len;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Move result along, may still be needed for an unchanged
|
||||||
|
* entry if a previous entry was changed.
|
||||||
|
*/
|
||||||
|
memmove(value + out_index, map_out, out_len);
|
||||||
|
|
||||||
|
out_index += out_len;
|
||||||
|
}
|
||||||
|
in_index += in_len;
|
||||||
|
}
|
||||||
|
ret = out_index;
|
||||||
|
if (ret == 0) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
fuse_reply_buf(req, value, ret);
|
fuse_reply_buf(req, value, ret);
|
||||||
} else {
|
} else {
|
||||||
|
/*
|
||||||
|
* xattrmap only ever shortens the result,
|
||||||
|
* so we don't need to do anything clever with the
|
||||||
|
* allocation length here.
|
||||||
|
*/
|
||||||
fuse_reply_xattr(req, ret);
|
fuse_reply_xattr(req, ret);
|
||||||
}
|
}
|
||||||
out_free:
|
out_free:
|
||||||
@ -2165,19 +2672,35 @@ out:
|
|||||||
goto out_free;
|
goto out_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
|
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *in_name,
|
||||||
const char *value, size_t size, int flags)
|
const char *value, size_t size, int flags)
|
||||||
{
|
{
|
||||||
char procname[64];
|
char procname[64];
|
||||||
|
const char *name;
|
||||||
|
char *mapped_name;
|
||||||
struct lo_data *lo = lo_data(req);
|
struct lo_data *lo = lo_data(req);
|
||||||
struct lo_inode *inode;
|
struct lo_inode *inode;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
int saverr;
|
int saverr;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
|
||||||
|
mapped_name = NULL;
|
||||||
|
name = in_name;
|
||||||
|
if (lo->xattrmap) {
|
||||||
|
ret = xattr_map_client(lo, in_name, &mapped_name);
|
||||||
|
if (ret < 0) {
|
||||||
|
fuse_reply_err(req, -ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mapped_name) {
|
||||||
|
name = mapped_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inode = lo_inode(req, ino);
|
inode = lo_inode(req, ino);
|
||||||
if (!inode) {
|
if (!inode) {
|
||||||
fuse_reply_err(req, EBADF);
|
fuse_reply_err(req, EBADF);
|
||||||
|
g_free(mapped_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2212,21 +2735,38 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
lo_inode_put(lo, &inode);
|
lo_inode_put(lo, &inode);
|
||||||
|
g_free(mapped_name);
|
||||||
fuse_reply_err(req, saverr);
|
fuse_reply_err(req, saverr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
|
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *in_name)
|
||||||
{
|
{
|
||||||
char procname[64];
|
char procname[64];
|
||||||
|
const char *name;
|
||||||
|
char *mapped_name;
|
||||||
struct lo_data *lo = lo_data(req);
|
struct lo_data *lo = lo_data(req);
|
||||||
struct lo_inode *inode;
|
struct lo_inode *inode;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
int saverr;
|
int saverr;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
|
||||||
|
mapped_name = NULL;
|
||||||
|
name = in_name;
|
||||||
|
if (lo->xattrmap) {
|
||||||
|
ret = xattr_map_client(lo, in_name, &mapped_name);
|
||||||
|
if (ret < 0) {
|
||||||
|
fuse_reply_err(req, -ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mapped_name) {
|
||||||
|
name = mapped_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inode = lo_inode(req, ino);
|
inode = lo_inode(req, ino);
|
||||||
if (!inode) {
|
if (!inode) {
|
||||||
fuse_reply_err(req, EBADF);
|
fuse_reply_err(req, EBADF);
|
||||||
|
g_free(mapped_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2261,6 +2801,7 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
lo_inode_put(lo, &inode);
|
lo_inode_put(lo, &inode);
|
||||||
|
g_free(mapped_name);
|
||||||
fuse_reply_err(req, saverr);
|
fuse_reply_err(req, saverr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2660,6 +3201,41 @@ static void setup_capabilities(char *modcaps_in)
|
|||||||
pthread_mutex_unlock(&cap.mutex);
|
pthread_mutex_unlock(&cap.mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use chroot as a weaker sandbox for environments where the process is
|
||||||
|
* launched without CAP_SYS_ADMIN.
|
||||||
|
*/
|
||||||
|
static void setup_chroot(struct lo_data *lo)
|
||||||
|
{
|
||||||
|
lo->proc_self_fd = open("/proc/self/fd", O_PATH);
|
||||||
|
if (lo->proc_self_fd == -1) {
|
||||||
|
fuse_log(FUSE_LOG_ERR, "open(\"/proc/self/fd\", O_PATH): %m\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make the shared directory the file system root so that FUSE_OPEN
|
||||||
|
* (lo_open()) cannot escape the shared directory by opening a symlink.
|
||||||
|
*
|
||||||
|
* The chroot(2) syscall is later disabled by seccomp and the
|
||||||
|
* CAP_SYS_CHROOT capability is dropped so that tampering with the chroot
|
||||||
|
* is not possible.
|
||||||
|
*
|
||||||
|
* However, it's still possible to escape the chroot via lo->proc_self_fd
|
||||||
|
* but that requires first gaining control of the process.
|
||||||
|
*/
|
||||||
|
if (chroot(lo->source) != 0) {
|
||||||
|
fuse_log(FUSE_LOG_ERR, "chroot(\"%s\"): %m\n", lo->source);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move into the chroot */
|
||||||
|
if (chdir("/") != 0) {
|
||||||
|
fuse_log(FUSE_LOG_ERR, "chdir(\"/\"): %m\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lock down this process to prevent access to other processes or files outside
|
* Lock down this process to prevent access to other processes or files outside
|
||||||
* source directory. This reduces the impact of arbitrary code execution bugs.
|
* source directory. This reduces the impact of arbitrary code execution bugs.
|
||||||
@ -2667,8 +3243,13 @@ static void setup_capabilities(char *modcaps_in)
|
|||||||
static void setup_sandbox(struct lo_data *lo, struct fuse_session *se,
|
static void setup_sandbox(struct lo_data *lo, struct fuse_session *se,
|
||||||
bool enable_syslog)
|
bool enable_syslog)
|
||||||
{
|
{
|
||||||
|
if (lo->sandbox == SANDBOX_NAMESPACE) {
|
||||||
setup_namespaces(lo, se);
|
setup_namespaces(lo, se);
|
||||||
setup_mounts(lo->source);
|
setup_mounts(lo->source);
|
||||||
|
} else {
|
||||||
|
setup_chroot(lo);
|
||||||
|
}
|
||||||
|
|
||||||
setup_seccomp(enable_syslog);
|
setup_seccomp(enable_syslog);
|
||||||
setup_capabilities(g_strdup(lo->modcaps));
|
setup_capabilities(g_strdup(lo->modcaps));
|
||||||
}
|
}
|
||||||
@ -2806,6 +3387,8 @@ static void fuse_lo_data_cleanup(struct lo_data *lo)
|
|||||||
close(lo->root.fd);
|
close(lo->root.fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(lo->xattrmap);
|
||||||
|
free_xattrmap(lo);
|
||||||
free(lo->source);
|
free(lo->source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2815,6 +3398,7 @@ int main(int argc, char *argv[])
|
|||||||
struct fuse_session *se;
|
struct fuse_session *se;
|
||||||
struct fuse_cmdline_opts opts;
|
struct fuse_cmdline_opts opts;
|
||||||
struct lo_data lo = {
|
struct lo_data lo = {
|
||||||
|
.sandbox = SANDBOX_NAMESPACE,
|
||||||
.debug = 0,
|
.debug = 0,
|
||||||
.writeback = 0,
|
.writeback = 0,
|
||||||
.posix_lock = 0,
|
.posix_lock = 0,
|
||||||
@ -2878,12 +3462,11 @@ int main(int argc, char *argv[])
|
|||||||
goto err_out1;
|
goto err_out1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* log_level is 0 if not configured via cmd options (0 is LOG_EMERG,
|
|
||||||
* and we don't use this log level).
|
|
||||||
*/
|
|
||||||
if (opts.log_level != 0) {
|
if (opts.log_level != 0) {
|
||||||
current_log_level = opts.log_level;
|
current_log_level = opts.log_level;
|
||||||
|
} else {
|
||||||
|
/* default log level is INFO */
|
||||||
|
current_log_level = FUSE_LOG_INFO;
|
||||||
}
|
}
|
||||||
lo.debug = opts.debug;
|
lo.debug = opts.debug;
|
||||||
if (lo.debug) {
|
if (lo.debug) {
|
||||||
@ -2906,6 +3489,11 @@ int main(int argc, char *argv[])
|
|||||||
} else {
|
} else {
|
||||||
lo.source = strdup("/");
|
lo.source = strdup("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lo.xattrmap) {
|
||||||
|
parse_xattrmap(&lo);
|
||||||
|
}
|
||||||
|
|
||||||
if (!lo.timeout_set) {
|
if (!lo.timeout_set) {
|
||||||
switch (lo.cache) {
|
switch (lo.cache) {
|
||||||
case CACHE_NONE:
|
case CACHE_NONE:
|
||||||
|
Loading…
Reference in New Issue
Block a user