diff options
65 files changed, 2190 insertions, 504 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 81a2960b1a..900437dd2a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -40,7 +40,7 @@ macos_xcode_task: script: - mkdir build - cd build - - ../configure --extra-cflags='-Wno-error=deprecated-declarations' + - ../configure --extra-cflags='-Wno-error=deprecated-declarations' --enable-modules --enable-werror --cc=clang || { cat config.log meson-logs/meson-log.txt; exit 1; } - gmake -j$(sysctl -n hw.ncpu) - gmake check V=1 @@ -92,39 +92,58 @@ endif ifeq ($(NINJA),) .PHONY: config-host.mak x := $(shell rm -rf meson-private meson-info meson-logs) +else +export NINJA endif ifeq ($(wildcard build.ninja),) .PHONY: config-host.mak x := $(shell rm -rf meson-private meson-info meson-logs) endif +ifeq ($(origin prefix),file) +.PHONY: config-host.mak +x := $(shell rm -rf meson-private meson-info meson-logs) +endif # 1. ensure config-host.mak is up-to-date config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios $(SRC_PATH)/VERSION - @echo $@ is out-of-date, running configure + @echo config-host.mak is out-of-date, running configure @if test -f meson-private/coredata.dat; then \ ./config.status --skip-meson; \ else \ - ./config.status; \ + ./config.status && touch build.ninja.stamp; \ fi -# 2. ensure generated build files are up-to-date +# 2. meson.stamp exists if meson has run at least once (so ninja reconfigure +# works), but otherwise never needs to be updated +meson-private/coredata.dat: meson.stamp +meson.stamp: config-host.mak + @touch meson.stamp + +# 3. ensure generated build files are up-to-date ifneq ($(NINJA),) -# A separate rule is needed for Makefile dependencies to avoid -n -export NINJA Makefile.ninja: build.ninja - $(quiet-@){ echo 'ninja-targets = \'; $(NINJA) -t targets all | sed 's/:.*//; $$!s/$$/ \\/'; } > $@ + $(quiet-@){ \ + echo 'ninja-targets = \'; \ + $(NINJA) -t targets all | sed 's/:.*//; $$!s/$$/ \\/'; \ + echo 'build-files = \'; \ + $(NINJA) -t query build.ninja | sed -n '1,/^ input:/d; /^ outputs:/q; s/$$/ \\/p'; \ + } > $@.tmp && mv $@.tmp $@ -include Makefile.ninja + +# A separate rule is needed for Makefile dependencies to avoid -n +build.ninja: build.ninja.stamp +build.ninja.stamp: meson.stamp $(build-files) + $(NINJA) $(if $V,-v,) build.ninja && touch $@ endif ifneq ($(MESON),) -# The dependency on config-host.mak ensures that meson has run -Makefile.mtest: build.ninja scripts/mtest2make.py config-host.mak +Makefile.mtest: build.ninja scripts/mtest2make.py $(MESON) introspect --targets --tests --benchmarks | $(PYTHON) scripts/mtest2make.py > $@ -include Makefile.mtest endif -# 3. Rules to bridge to other makefiles +# 4. Rules to bridge to other makefiles ifneq ($(NINJA),) NINJAFLAGS = $(if $V,-v,) \ @@ -135,7 +154,10 @@ ninja-cmd-goals = $(or $(MAKECMDGOALS), all) ninja-cmd-goals += $(foreach t, $(.tests), $(.test.deps.$t)) makefile-targets := build.ninja ctags TAGS cscope dist clean uninstall -ninja-targets := $(filter-out $(makefile-targets), $(ninja-targets)) +# "ninja -t targets" also lists all prerequisites. If build system +# files are marked as PHONY, however, Make will always try to execute +# "ninja build.ninja". +ninja-targets := $(filter-out $(build-files) $(makefile-targets), $(ninja-targets)) .PHONY: $(ninja-targets) run-ninja $(ninja-targets): run-ninja @@ -214,7 +236,7 @@ distclean: clean rm -f qemu-plugins-ld.symbols qemu-plugins-ld64.symbols rm -f *-config-target.h *-config-devices.mak *-config-devices.h rm -rf meson-private meson-logs meson-info compile_commands.json - rm -f Makefile.ninja Makefile.mtest + rm -f Makefile.ninja Makefile.mtest build.ninja.stamp meson.stamp rm -f config.log rm -f linux-headers/asm rm -Rf .sdk diff --git a/block/meson.build b/block/meson.build index 78e8b25232..5dcc1e5cce 100644 --- a/block/meson.build +++ b/block/meson.build @@ -7,7 +7,6 @@ block_ss.add(files( 'backup-top.c', 'blkdebug.c', 'blklogwrites.c', - 'blkreplay.c', 'blkverify.c', 'block-backend.c', 'block-copy.c', @@ -42,6 +41,8 @@ block_ss.add(files( 'write-threshold.c', ), zstd, zlib) +softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + block_ss.add(when: 'CONFIG_QCOW1', if_true: files('qcow.c')) block_ss.add(when: 'CONFIG_VDI', if_true: files('vdi.c')) block_ss.add(when: 'CONFIG_CLOOP', if_true: files('cloop.c')) @@ -302,6 +302,7 @@ fdt="auto" netmap="no" sdl="auto" sdl_image="auto" +virtiofsd="auto" virtfs="" libudev="auto" mpath="auto" @@ -362,8 +363,7 @@ cocoa="auto" softmmu="yes" linux_user="no" bsd_user="no" -blobs="yes" -edk2_blobs="no" +blobs="true" pkgversion="" pie="" qom_cast_debug="yes" @@ -962,6 +962,8 @@ for opt do ;; --docdir=*) docdir="$optarg" ;; + --localedir=*) localedir="$optarg" + ;; --sysconfdir=*) sysconfdir="$optarg" ;; --localstatedir=*) local_statedir="$optarg" @@ -971,7 +973,7 @@ for opt do --host=*|--build=*|\ --disable-dependency-tracking|\ --sbindir=*|--sharedstatedir=*|\ - --oldincludedir=*|--datarootdir=*|--infodir=*|--localedir=*|\ + --oldincludedir=*|--datarootdir=*|--infodir=*|\ --htmldir=*|--dvidir=*|--pdfdir=*|--psdir=*) # These switches are silently ignored, for compatibility with # autoconf-generated configure scripts. This allows QEMU's @@ -998,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" @@ -1203,7 +1209,7 @@ for opt do ;; --enable-membarrier) membarrier="yes" ;; - --disable-blobs) blobs="no" + --disable-blobs) blobs="false" ;; --with-pkgversion=*) pkgversion="$optarg" ;; @@ -1521,7 +1527,6 @@ for opt do esac done -firmwarepath="${firmwarepath:-$prefix/share/qemu-firmware}" libdir="${libdir:-$prefix/lib}" libexecdir="${libexecdir:-$prefix/libexec}" includedir="${includedir:-$prefix/include}" @@ -1532,7 +1537,7 @@ if test "$mingw32" = "yes" ; then docdir="$prefix" bindir="$prefix" sysconfdir="$prefix" - local_statedir= + local_statedir="$prefix" else mandir="${mandir:-$prefix/share/man}" datadir="${datadir:-$prefix/share}" @@ -1541,6 +1546,8 @@ else sysconfdir="${sysconfdir:-$prefix/etc}" local_statedir="${local_statedir:-$prefix/var}" fi +firmwarepath="${firmwarepath:-$datadir/qemu-firmware}" +localedir="${localedir:-$datadir/locale}" case "$cpu" in ppc) @@ -1670,6 +1677,7 @@ Advanced options (experts only): --static enable static build [$static] --mandir=PATH install man pages in PATH --datadir=PATH install firmware in PATH/$qemu_suffix + --localedir=PATH install translation in PATH/$qemu_suffix --docdir=PATH install documentation in PATH/$qemu_suffix --bindir=PATH install binaries in PATH --libdir=PATH install libraries in PATH @@ -1755,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 @@ -2217,18 +2226,6 @@ case " $target_list " in ;; esac -for target in $target_list; do - case "$target" in - arm-softmmu | aarch64-softmmu | i386-softmmu | x86_64-softmmu) - edk2_blobs="yes" - ;; - esac -done -# The EDK2 binaries are compressed with bzip2 -if test "$edk2_blobs" = "yes" && ! has bzip2; then - error_exit "The bzip2 program is required for building QEMU" -fi - feature_not_found() { feature=$1 remedy=$2 @@ -3608,7 +3605,6 @@ else if test "$found" = "no"; then LIBS="$pthread_lib $LIBS" fi - PTHREAD_LIB="$pthread_lib" break fi done @@ -5725,13 +5721,6 @@ if test "$mingw32" = "yes" ; then done fi -qemu_confdir="$sysconfdir/$qemu_suffix" -qemu_moddir="$libdir/$qemu_suffix" -qemu_datadir="$datadir/$qemu_suffix" -qemu_localedir="$datadir/locale" -qemu_icondir="$datadir/icons" -qemu_desktopdir="$datadir/applications" - # We can only support ivshmem if we have eventfd if [ "$eventfd" = "yes" ]; then ivshmem=yes @@ -5898,23 +5887,6 @@ echo "# Automatically generated by configure - do not modify" > $config_host_mak echo >> $config_host_mak echo all: >> $config_host_mak -echo "prefix=$prefix" >> $config_host_mak -echo "bindir=$bindir" >> $config_host_mak -echo "libdir=$libdir" >> $config_host_mak -echo "libexecdir=$libexecdir" >> $config_host_mak -echo "includedir=$includedir" >> $config_host_mak -echo "sysconfdir=$sysconfdir" >> $config_host_mak -echo "qemu_confdir=$qemu_confdir" >> $config_host_mak -echo "qemu_datadir=$qemu_datadir" >> $config_host_mak -echo "qemu_firmwarepath=$firmwarepath" >> $config_host_mak -echo "qemu_moddir=$qemu_moddir" >> $config_host_mak -if test "$mingw32" = "no" ; then - echo "qemu_localstatedir=$local_statedir" >> $config_host_mak -fi -echo "qemu_helperdir=$libexecdir" >> $config_host_mak -echo "qemu_localedir=$qemu_localedir" >> $config_host_mak -echo "qemu_icondir=$qemu_icondir" >> $config_host_mak -echo "qemu_desktopdir=$qemu_desktopdir" >> $config_host_mak echo "GIT=$git" >> $config_host_mak echo "GIT_SUBMODULES=$git_submodules" >> $config_host_mak echo "GIT_UPDATE=$git_update" >> $config_host_mak @@ -6297,9 +6269,6 @@ fi if test "$vhost_user_fs" = "yes" ; then echo "CONFIG_VHOST_USER_FS=y" >> $config_host_mak fi -if test "$blobs" = "yes" ; then - echo "INSTALL_BLOBS=yes" >> $config_host_mak -fi if test "$iovec" = "yes" ; then echo "CONFIG_IOVEC=y" >> $config_host_mak fi @@ -6762,7 +6731,6 @@ echo "GLIB_LIBS=$glib_libs" >> $config_host_mak echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak echo "LDFLAGS_NOPIE=$LDFLAGS_NOPIE" >> $config_host_mak echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak -echo "PTHREAD_LIB=$PTHREAD_LIB" >> $config_host_mak echo "EXESUF=$EXESUF" >> $config_host_mak echo "HOST_DSOSUF=$HOST_DSOSUF" >> $config_host_mak echo "LIBS_QGA=$libs_qga" >> $config_host_mak @@ -6777,10 +6745,6 @@ if test "$fuzzing" != "no"; then fi echo "FUZZ_EXE_LDFLAGS=$FUZZ_EXE_LDFLAGS" >> $config_host_mak -if test "$edk2_blobs" = "yes" ; then - echo "DECOMPRESS_EDK2_BLOBS=y" >> $config_host_mak -fi - if test "$rng_none" = "yes"; then echo "CONFIG_RNG_NONE=y" >> $config_host_mak fi @@ -6997,8 +6961,10 @@ NINJA=$ninja $meson setup \ --datadir "$datadir" \ --mandir "$mandir" \ --sysconfdir "$sysconfdir" \ + --localedir "$localedir" \ --localstatedir "$local_statedir" \ -Ddocdir="$docdir" \ + -Dqemu_firmwarepath="$firmwarepath" \ -Dqemu_suffix="$qemu_suffix" \ -Doptimization=$(if test "$debug" = yes; then echo 0; else echo 2; fi) \ -Ddebug=$(if test "$debug_info" = yes; then echo true; else echo false; fi) \ @@ -7012,10 +6978,10 @@ 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 \ + -Ddocs=$docs -Dsphinx_build=$sphinx_build -Dinstall_blobs=$blobs \ $cross_arg \ "$PWD" "$source_path" diff --git a/contrib/vhost-user-gpu/meson.build b/contrib/vhost-user-gpu/meson.build index 7d9b29da8b..37ecca13ca 100644 --- a/contrib/vhost-user-gpu/meson.build +++ b/contrib/vhost-user-gpu/meson.build @@ -9,6 +9,6 @@ if 'CONFIG_TOOLS' in config_host and 'CONFIG_VIRGL' in config_host \ configure_file(input: '50-qemu-gpu.json.in', output: '50-qemu-gpu.json', - configuration: config_host, + configuration: { 'libexecdir' : get_option('libexecdir') }, install_dir: qemu_datadir / 'vhost-user') endif diff --git a/docs/system/deprecated.rst b/docs/system/deprecated.rst index 905628f3a0..0ebce37a19 100644 --- a/docs/system/deprecated.rst +++ b/docs/system/deprecated.rst @@ -21,17 +21,6 @@ deprecated. System emulator command line arguments -------------------------------------- -``-machine enforce-config-section=on|off`` (since 3.1) -'''''''''''''''''''''''''''''''''''''''''''''''''''''' - -The ``enforce-config-section`` parameter is replaced by the -``-global migration.send-configuration={on|off}`` option. - -``-no-kvm`` (since 1.3.0) -''''''''''''''''''''''''' - -The ``-no-kvm`` argument is now a synonym for setting ``-accel tcg``. - ``-usbdevice`` (since 2.10.0) ''''''''''''''''''''''''''''' @@ -504,6 +493,12 @@ System emulator command line arguments The ``name`` parameter of the ``-net`` option was a synonym for the ``id`` parameter, which should now be used instead. +``-no-kvm`` (removed in 5.2) +'''''''''''''''''''''''''''' + +The ``-no-kvm`` argument was a synonym for setting ``-machine accel=tcg``. + + QEMU Machine Protocol (QMP) commands ------------------------------------ @@ -688,6 +683,12 @@ Support for invalid topologies is removed, the user must ensure topologies described with -smp include all possible cpus, i.e. *sockets* * *cores* * *threads* = *maxcpus*. +``-machine enforce-config-section=on|off`` (removed 5.2) +'''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``enforce-config-section`` property was replaced by the +``-global migration.send-configuration={on|off}`` option. + Block devices ------------- diff --git a/docs/tools/virtiofsd.rst b/docs/tools/virtiofsd.rst index 7ecee49834..34a9e40146 100644 --- a/docs/tools/virtiofsd.rst +++ b/docs/tools/virtiofsd.rst @@ -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 -------- diff --git a/hw/core/machine.c b/hw/core/machine.c index d740a7e963..c5e0e79e6d 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -411,24 +411,6 @@ static bool machine_get_suppress_vmdesc(Object *obj, Error **errp) return ms->suppress_vmdesc; } -static void machine_set_enforce_config_section(Object *obj, bool value, - Error **errp) -{ - MachineState *ms = MACHINE(obj); - - warn_report("enforce-config-section is deprecated, please use " - "-global migration.send-configuration=on|off instead"); - - ms->enforce_config_section = value; -} - -static bool machine_get_enforce_config_section(Object *obj, Error **errp) -{ - MachineState *ms = MACHINE(obj); - - return ms->enforce_config_section; -} - static char *machine_get_memory_encryption(Object *obj, Error **errp) { MachineState *ms = MACHINE(obj); @@ -857,11 +839,6 @@ static void machine_class_init(ObjectClass *oc, void *data) object_class_property_set_description(oc, "suppress-vmdesc", "Set on to disable self-describing migration"); - object_class_property_add_bool(oc, "enforce-config-section", - machine_get_enforce_config_section, machine_set_enforce_config_section); - object_class_property_set_description(oc, "enforce-config-section", - "Set on to enforce configuration section migration"); - object_class_property_add_str(oc, "memory-encryption", machine_get_memory_encryption, machine_set_memory_encryption); object_class_property_set_description(oc, "memory-encryption", @@ -876,8 +853,12 @@ static void machine_class_init(ObjectClass *oc, void *data) static void machine_class_base_init(ObjectClass *oc, void *data) { + MachineClass *mc = MACHINE_CLASS(oc); + mc->max_cpus = mc->max_cpus ?: 1; + mc->min_cpus = mc->min_cpus ?: 1; + mc->default_cpus = mc->default_cpus ?: 1; + if (!object_class_is_abstract(oc)) { - MachineClass *mc = MACHINE_CLASS(oc); const char *cname = object_class_get_name(oc); assert(g_str_has_suffix(cname, TYPE_MACHINE_SUFFIX)); mc->name = g_strndup(cname, @@ -926,6 +907,13 @@ static void machine_initfn(Object *obj) /* Register notifier when init is done for sysbus sanity checks */ ms->sysbus_notifier.notify = machine_init_notify; qemu_add_machine_init_done_notifier(&ms->sysbus_notifier); + + /* default to mc->default_cpus */ + ms->smp.cpus = mc->default_cpus; + ms->smp.max_cpus = mc->default_cpus; + ms->smp.cores = 1; + ms->smp.threads = 1; + ms->smp.sockets = 1; } static void machine_finalize(Object *obj) diff --git a/hw/core/qdev-clock.c b/hw/core/qdev-clock.c index 6a9a340d0f..eb05f2a13c 100644 --- a/hw/core/qdev-clock.c +++ b/hw/core/qdev-clock.c @@ -61,6 +61,14 @@ static NamedClockList *qdev_init_clocklist(DeviceState *dev, const char *name, object_get_typename(OBJECT(clk)), (Object **) &ncl->clock, NULL, OBJ_PROP_LINK_STRONG); + /* + * Since the link property has the OBJ_PROP_LINK_STRONG flag, the clk + * object reference count gets decremented on property deletion. + * However object_property_add_link does not increment it since it + * doesn't know the linked object. Increment it here to ensure the + * aliased clock stays alive during this device life-time. + */ + object_ref(OBJECT(clk)); } ncl->clock = clk; diff --git a/hw/sd/sd.c b/hw/sd/sd.c index c3febed243..3091382614 100644 --- a/hw/sd/sd.c +++ b/hw/sd/sd.c @@ -102,11 +102,14 @@ struct SDState { uint32_t card_status; uint8_t sd_status[64]; - /* Configurable properties */ + /* Static properties */ + uint8_t spec_version; BlockBackend *blk; bool spi; + /* Runtime changeables */ + uint32_t mode; /* current card mode, one of SDCardModes */ int32_t state; /* current card state, one of SDCardStates */ uint32_t vhs; @@ -251,11 +254,11 @@ static const int sd_cmd_class[SDMMC_CMD_MAX] = { 7, 7, 10, 7, 9, 9, 9, 8, 8, 10, 8, 8, 8, 8, 8, 8, }; -static uint8_t sd_crc7(void *message, size_t width) +static uint8_t sd_crc7(const void *message, size_t width) { int i, bit; uint8_t shift_reg = 0x00; - uint8_t *msg = (uint8_t *) message; + const uint8_t *msg = (const uint8_t *)message; for (i = 0; i < width; i ++, msg ++) for (bit = 7; bit >= 0; bit --) { @@ -267,11 +270,11 @@ static uint8_t sd_crc7(void *message, size_t width) return shift_reg; } -static uint16_t sd_crc16(void *message, size_t width) +static uint16_t sd_crc16(const void *message, size_t width) { int i, bit; uint16_t shift_reg = 0x0000; - uint16_t *msg = (uint16_t *) message; + const uint16_t *msg = (const uint16_t *)message; width <<= 1; for (i = 0; i < width; i ++, msg ++) @@ -824,6 +827,7 @@ static void sd_function_switch(SDState *sd, uint32_t arg) sd->data[12] = 0x80; /* Supported group 1 functions */ sd->data[13] = 0x03; + memset(&sd->data[14], 0, 3); for (i = 0; i < 6; i ++) { new_func = (arg >> (i * 4)) & 0x0f; if (mode && new_func != 0x0f) @@ -1676,7 +1680,7 @@ static sd_rsp_type_t sd_app_command(SDState *sd, return sd_illegal; } -static int cmd_valid_while_locked(SDState *sd, SDRequest *req) +static int cmd_valid_while_locked(SDState *sd, const uint8_t cmd) { /* Valid commands in locked state: * basic class (0) @@ -1687,13 +1691,12 @@ static int cmd_valid_while_locked(SDState *sd, SDRequest *req) * Anything else provokes an "illegal command" response. */ if (sd->expecting_acmd) { - return req->cmd == 41 || req->cmd == 42; + return cmd == 41 || cmd == 42; } - if (req->cmd == 16 || req->cmd == 55) { + if (cmd == 16 || cmd == 55) { return 1; } - return sd_cmd_class[req->cmd] == 0 - || sd_cmd_class[req->cmd] == 7; + return sd_cmd_class[cmd] == 0 || sd_cmd_class[cmd] == 7; } int sd_do_command(SDState *sd, SDRequest *req, @@ -1719,7 +1722,7 @@ int sd_do_command(SDState *sd, SDRequest *req, } if (sd->card_status & CARD_IS_LOCKED) { - if (!cmd_valid_while_locked(sd, req)) { + if (!cmd_valid_while_locked(sd, req->cmd)) { sd->card_status |= ILLEGAL_COMMAND; sd->expecting_acmd = false; qemu_log_mask(LOG_GUEST_ERROR, "SD: Card is locked\n"); @@ -1980,7 +1983,7 @@ uint8_t sd_read_byte(SDState *sd) { /* TODO: Append CRCs */ uint8_t ret; - int io_len; + uint32_t io_len; if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable) return 0x00; diff --git a/hw/sd/trace-events b/hw/sd/trace-events index 96c7ea5e52..4140e48540 100644 --- a/hw/sd/trace-events +++ b/hw/sd/trace-events @@ -52,7 +52,7 @@ sdcard_unlock(void) "" sdcard_read_block(uint64_t addr, uint32_t len) "addr 0x%" PRIx64 " size 0x%x" sdcard_write_block(uint64_t addr, uint32_t len) "addr 0x%" PRIx64 " size 0x%x" sdcard_write_data(const char *proto, const char *cmd_desc, uint8_t cmd, uint8_t value) "%s %20s/ CMD%02d value 0x%02x" -sdcard_read_data(const char *proto, const char *cmd_desc, uint8_t cmd, int length) "%s %20s/ CMD%02d len %d" +sdcard_read_data(const char *proto, const char *cmd_desc, uint8_t cmd, uint32_t length) "%s %20s/ CMD%02d len %" PRIu32 sdcard_set_voltage(uint16_t millivolts) "%u mV" # milkymist-memcard.c diff --git a/include/hw/boards.h b/include/hw/boards.h index bf53e8a16e..a49e3a6b44 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -268,7 +268,6 @@ struct MachineState { char *firmware; bool iommu; bool suppress_vmdesc; - bool enforce_config_section; bool enable_graphics; char *memory_encryption; char *ram_memdev_id; diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index f68ed7db13..4d71dc8fba 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -219,7 +219,6 @@ extern const VMStateInfo vmstate_info_uint64; #define VMS_NULLPTR_MARKER (0x30U) /* '0' */ extern const VMStateInfo vmstate_info_nullptr; -extern const VMStateInfo vmstate_info_float64; extern const VMStateInfo vmstate_info_cpudouble; extern const VMStateInfo vmstate_info_timer; @@ -997,12 +996,6 @@ extern const VMStateInfo vmstate_info_qlist; VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_info_uint64, uint64_t) -#define VMSTATE_FLOAT64_V(_f, _s, _v) \ - VMSTATE_SINGLE(_f, _s, _v, vmstate_info_float64, float64) - -#define VMSTATE_FLOAT64(_f, _s) \ - VMSTATE_FLOAT64_V(_f, _s, 0) - #define VMSTATE_TIMER_PTR_TEST(_f, _s, _test) \ VMSTATE_POINTER_TEST(_f, _s, _test, vmstate_info_timer, QEMUTimer *) @@ -1114,12 +1107,6 @@ extern const VMStateInfo vmstate_info_qlist; #define VMSTATE_INT64_ARRAY(_f, _s, _n) \ VMSTATE_INT64_ARRAY_V(_f, _s, _n, 0) -#define VMSTATE_FLOAT64_ARRAY_V(_f, _s, _n, _v) \ - VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_float64, float64) - -#define VMSTATE_FLOAT64_ARRAY(_f, _s, _n) \ - VMSTATE_FLOAT64_ARRAY_V(_f, _s, _n, 0) - #define VMSTATE_CPUDOUBLE_ARRAY_V(_f, _s, _n, _v) \ VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_cpudouble, CPU_DoubleU) diff --git a/include/standard-headers/linux/fuse.h b/include/standard-headers/linux/fuse.h index f4df0a40f6..7dd7a3b992 100644 --- a/include/standard-headers/linux/fuse.h +++ b/include/standard-headers/linux/fuse.h @@ -227,7 +227,7 @@ struct fuse_attr { uint32_t gid; uint32_t rdev; uint32_t blksize; - uint32_t 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 */ diff --git a/meson.build b/meson.build index b349c9bda8..47e32e1fcb 100644 --- a/meson.build +++ b/meson.build @@ -29,8 +29,14 @@ if get_option('qemu_suffix').startswith('/') error('qemu_suffix cannot start with a /') endif +qemu_confdir = get_option('sysconfdir') / get_option('qemu_suffix') qemu_datadir = get_option('datadir') / get_option('qemu_suffix') qemu_docdir = get_option('docdir') / get_option('qemu_suffix') +qemu_moddir = get_option('libdir') / get_option('qemu_suffix') + +qemu_desktopdir = get_option('datadir') / 'applications' +qemu_icondir = get_option('datadir') / 'icons' + config_host_data = configuration_data() genh = [] @@ -198,7 +204,7 @@ else have_xen_pci_passthrough = false endif if not get_option('whpx').disabled() and targetos == 'windows' - if get_option('whpx').enabled() and cpu != 'x86_64' + if get_option('whpx').enabled() and host_machine.cpu() != 'x86_64' error('WHPX requires 64-bit host') elif cc.has_header('WinHvPlatform.h', required: get_option('whpx')) and \ cc.has_header('WinHvEmulation.h', required: get_option('whpx')) @@ -465,70 +471,89 @@ endif iconv = not_found curses = not_found if have_system and not get_option('curses').disabled() - if not get_option('iconv').disabled() - libiconv = cc.find_library('iconv', - required: false, - static: enable_static) - if cc.links(''' - #include <iconv.h> - int main(void) { - iconv_t conv = iconv_open("WCHAR_T", "UCS-2"); - return conv != (iconv_t) -1; - }''', dependencies: [libiconv]) - iconv = declare_dependency(dependencies: [libiconv]) + curses_test = ''' + #include <locale.h> + #include <curses.h> + #include <wchar.h> + int main(void) { + wchar_t wch = L'w'; + setlocale(LC_ALL, ""); + resize_term(0, 0); + addwstr(L"wide chars\n"); + addnwstr(&wch, 1); + add_wch(WACS_DEGREE); + return 0; + }''' + + curses = dependency((targetos == 'windows' ? 'ncurses' : 'ncursesw'), + required: false, + method: 'pkg-config', + static: enable_static) + msg = get_option('curses').enabled() ? 'curses library not found' : '' + if curses.found() + if cc.links(curses_test, dependencies: [curses]) + curses = declare_dependency(compile_args: '-DNCURSES_WIDECHAR', dependencies: [curses]) + else + msg = 'curses package not usable' + curses = not_found endif endif - if get_option('iconv').enabled() and not iconv.found() - error('Cannot detect iconv API') - endif - if iconv.found() - curses_libname_list = ['ncursesw', 'ncurses', 'cursesw', 'pdcurses'] - curses_test = ''' - #include <locale.h> - #include <curses.h> - #include <wchar.h> - int main(void) { - wchar_t wch = L'w'; - setlocale(LC_ALL, ""); - resize_term(0, 0); - addwstr(L"wide chars\n"); - addnwstr(&wch, 1); - add_wch(WACS_DEGREE); - return 0; - }''' - foreach curses_libname : curses_libname_list - libcurses = dependency(curses_libname, - required: false, - method: 'pkg-config', - static: enable_static) - - if not libcurses.found() - dirs = ['/usr/include/ncursesw'] - if targetos == 'windows' - dirs = [] - endif + if not curses.found() + curses_compile_args = ['-DNCURSES_WIDECHAR'] + has_curses_h = cc.has_header('curses.h', args: curses_compile_args) + if targetos != 'windows' and not has_curses_h + message('Trying with /usr/include/ncursesw') + curses_compile_args += ['-I/usr/include/ncursesw'] + has_curses_h = cc.has_header('curses.h', args: curses_compile_args) + endif + if has_curses_h + curses_libname_list = (targetos == 'windows' ? ['pdcurses'] : ['ncursesw', 'cursesw']) + foreach curses_libname : curses_libname_list libcurses = cc.find_library(curses_libname, required: false, - dirs: dirs, static: enable_static) - endif - if libcurses.found() - if cc.links(curses_test, dependencies: [libcurses]) - curses = declare_dependency(compile_args: '-DNCURSES_WIDECHAR', dependencies: [libcurses]) - break + if libcurses.found() + if cc.links(curses_test, args: curses_compile_args, dependencies: libcurses) + curses = declare_dependency(compile_args: curses_compile_args, + dependencies: [libcurses]) + break + else + msg = 'curses library not usable' + endif endif + endforeach + endif + endif + if not get_option('iconv').disabled() + foreach link_args : [ ['-liconv'], [] ] + # Programs will be linked with glib and this will bring in libiconv on FreeBSD. + # We need to use libiconv if available because mixing libiconv's headers with + # the system libc does not work. + # However, without adding glib to the dependencies -L/usr/local/lib will not be + # included in the command line and libiconv will not be found. + if cc.links(''' + #include <iconv.h> + int main(void) { + iconv_t conv = iconv_open("WCHAR_T", "UCS-2"); + return conv != (iconv_t) -1; + }''', args: config_host['GLIB_CFLAGS'].split() + config_host['GLIB_LIBS'].split() + link_args) + iconv = declare_dependency(link_args: link_args, dependencies: glib) + break endif endforeach endif - if not curses.found() - if iconv.found() - if get_option('curses').enabled() - error('Cannot find curses') - endif - elif get_option('curses').enabled() - error('iconv required for curses UI but not available') + if curses.found() and not iconv.found() + if get_option('iconv').enabled() + error('iconv not available') + endif + msg = 'iconv required for curses UI but not available' + curses = not_found + endif + if not curses.found() and msg != '' + if get_option('curses').enabled() + error(msg) else - warning('iconv required for curses UI but not available, disabling') + warning(msg + ', disabling') endif endif endif @@ -715,6 +740,19 @@ endif # config-host.h # ################# +config_host_data.set_quoted('CONFIG_BINDIR', get_option('prefix') / get_option('bindir')) +config_host_data.set_quoted('CONFIG_PREFIX', get_option('prefix')) +config_host_data.set_quoted('CONFIG_QEMU_CONFDIR', get_option('prefix') / qemu_confdir) +config_host_data.set_quoted('CONFIG_QEMU_DATADIR', get_option('prefix') / qemu_datadir) +config_host_data.set_quoted('CONFIG_QEMU_DESKTOPDIR', get_option('prefix') / qemu_desktopdir) +config_host_data.set_quoted('CONFIG_QEMU_FIRMWAREPATH', get_option('qemu_firmwarepath')) +config_host_data.set_quoted('CONFIG_QEMU_HELPERDIR', get_option('prefix') / get_option('libexecdir')) +config_host_data.set_quoted('CONFIG_QEMU_ICONDIR', get_option('prefix') / qemu_icondir) +config_host_data.set_quoted('CONFIG_QEMU_LOCALEDIR', get_option('prefix') / get_option('localedir')) +config_host_data.set_quoted('CONFIG_QEMU_LOCALSTATEDIR', get_option('prefix') / get_option('localstatedir')) +config_host_data.set_quoted('CONFIG_QEMU_MODDIR', get_option('prefix') / qemu_moddir) +config_host_data.set_quoted('CONFIG_SYSCONFDIR', get_option('prefix') / get_option('sysconfdir')) + config_host_data.set('CONFIG_COCOA', cocoa.found()) config_host_data.set('CONFIG_LIBUDEV', libudev.found()) config_host_data.set('CONFIG_MPATH', mpathpersist.found()) @@ -737,9 +775,7 @@ config_host_data.set('QEMU_VERSION_MICRO', meson.project_version().split('.')[2] ignored = ['CONFIG_QEMU_INTERP_PREFIX'] # actually per-target arrays = ['CONFIG_AUDIO_DRIVERS', 'CONFIG_BDRV_RW_WHITELIST', 'CONFIG_BDRV_RO_WHITELIST'] -strings = ['HOST_DSOSUF', 'CONFIG_IASL', 'bindir', 'prefix', 'qemu_confdir', 'qemu_datadir', - 'qemu_moddir', 'qemu_localstatedir', 'qemu_helperdir', 'qemu_localedir', - 'qemu_icondir', 'qemu_desktopdir', 'qemu_firmwarepath', 'sysconfdir'] +strings = ['HOST_DSOSUF', 'CONFIG_IASL'] foreach k, v: config_host if ignored.contains(k) # do nothing @@ -1633,7 +1669,7 @@ foreach m : block_mods + softmmu_mods name_prefix: '', link_whole: m, install: true, - install_dir: config_host['qemu_moddir']) + install_dir: qemu_moddir) endforeach softmmu_ss.add(authz, blockdev, chardev, crypto, io, qmp) @@ -1794,7 +1830,7 @@ foreach target : target_dirs output: exe['name'] + stp['ext'], capture: true, install: stp['install'], - install_dir: qemu_datadir / '../systemtap/tapset', + install_dir: get_option('datadir') / 'systemtap/tapset', command: [ tracetool, '--group=all', '--format=' + stp['fmt'], '--binary=' + stp['bin'], @@ -1906,17 +1942,17 @@ endif ######################### summary_info = {} -summary_info += {'Install prefix': config_host['prefix']} -summary_info += {'BIOS directory': config_host['qemu_datadir']} -summary_info += {'firmware path': config_host['qemu_firmwarepath']} -summary_info += {'binary directory': config_host['bindir']} -summary_info += {'library directory': config_host['libdir']} -summary_info += {'module directory': config_host['qemu_moddir']} -summary_info += {'libexec directory': config_host['libexecdir']} -summary_info += {'include directory': config_host['includedir']} -summary_info += {'config directory': config_host['sysconfdir']} +summary_info += {'Install prefix': get_option('prefix')} +summary_info += {'BIOS directory': qemu_datadir} +summary_info += {'firmware path': get_option('qemu_firmwarepath')} +summary_info += {'binary directory': get_option('bindir')} +summary_info += {'library directory': get_option('libdir')} +summary_info += {'module directory': qemu_moddir} +summary_info += {'libexec directory': get_option('libexecdir')} +summary_info += {'include directory': get_option('includedir')} +summary_info += {'config directory': get_option('sysconfdir')} if targetos != 'windows' - summary_info += {'local state directory': config_host['qemu_localstatedir']} + summary_info += {'local state directory': get_option('localstatedir')} summary_info += {'Manual directory': get_option('mandir')} else summary_info += {'local state directory': 'queried at runtime'} @@ -2009,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() @@ -2028,7 +2065,7 @@ summary_info += {'netmap support': config_host.has_key('CONFIG_NETMAP')} summary_info += {'Linux AIO support': config_host.has_key('CONFIG_LINUX_AIO')} summary_info += {'Linux io_uring support': config_host.has_key('CONFIG_LINUX_IO_URING')} summary_info += {'ATTR/XATTR support': config_host.has_key('CONFIG_ATTR')} -summary_info += {'Install blobs': config_host.has_key('INSTALL_BLOBS')} +summary_info += {'Install blobs': get_option('install_blobs')} summary_info += {'KVM support': config_all.has_key('CONFIG_KVM')} summary_info += {'HAX support': config_all.has_key('CONFIG_HAX')} summary_info += {'HVF support': config_all.has_key('CONFIG_HVF')} diff --git a/meson_options.txt b/meson_options.txt index 967229b66e..b4f1801875 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,6 +2,8 @@ option('qemu_suffix', type : 'string', value: 'qemu', description: 'Suffix for QEMU data/modules/config directories (can be empty)') option('docdir', type : 'string', value : 'doc', description: 'Base directory for documentation installation (can be empty)') +option('qemu_firmwarepath', type : 'string', value : '', + description: 'search PATH for firmware files') option('sphinx_build', type : 'string', value : '', description: 'Use specified sphinx-build [$sphinx_build] for building document (default to be empty)') @@ -9,6 +11,8 @@ option('docs', type : 'feature', value : 'auto', description: 'Documentations build support') option('gettext', type : 'boolean', value : true, description: 'Localization of the GTK+ user interface') +option('install_blobs', type : 'boolean', value : true, + description: 'install provided firmware blobs') option('sparse', type : 'feature', value : 'auto', description: 'sparse checker') @@ -58,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'], diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c index 5bef793ac0..c61d382be8 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c @@ -562,8 +562,9 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs, dbms->bitmap_alias = g_strdup(bitmap_alias); dbms->bitmap = bitmap; dbms->total_sectors = bdrv_nb_sectors(bs); - dbms->sectors_per_chunk = CHUNK_SIZE * 8 * - bdrv_dirty_bitmap_granularity(bitmap) >> BDRV_SECTOR_BITS; + dbms->sectors_per_chunk = CHUNK_SIZE * 8LLU * + (bdrv_dirty_bitmap_granularity(bitmap) >> BDRV_SECTOR_BITS); + assert(dbms->sectors_per_chunk != 0); if (bdrv_dirty_bitmap_enabled(bitmap)) { dbms->flags |= DIRTY_BITMAP_MIG_START_FLAG_ENABLED; } @@ -1071,18 +1072,15 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s, return -EINVAL; } - if (!s->cancelled) { - if (bitmap_alias_map) { - bitmap_name = g_hash_table_lookup(bitmap_alias_map, - s->bitmap_alias); - if (!bitmap_name) { - error_report("Error: Unknown bitmap alias '%s' on node " - "'%s' (alias '%s')", s->bitmap_alias, - s->bs->node_name, s->node_alias); - cancel_incoming_locked(s); - } - } else { - bitmap_name = s->bitmap_alias; + bitmap_name = s->bitmap_alias; + if (!s->cancelled && bitmap_alias_map) { + bitmap_name = g_hash_table_lookup(bitmap_alias_map, + s->bitmap_alias); + if (!bitmap_name) { + error_report("Error: Unknown bitmap alias '%s' on node " + "'%s' (alias '%s')", s->bitmap_alias, + s->bs->node_name, s->node_alias); + cancel_incoming_locked(s); } } diff --git a/migration/block.c b/migration/block.c index 737b6499f9..a950977855 100644 --- a/migration/block.c +++ b/migration/block.c @@ -26,6 +26,7 @@ #include "qemu-file.h" #include "migration/vmstate.h" #include "sysemu/block-backend.h" +#include "trace.h" #define BLK_MIG_BLOCK_SIZE (1 << 20) #define BDRV_SECTORS_PER_DIRTY_CHUNK (BLK_MIG_BLOCK_SIZE >> BDRV_SECTOR_BITS) @@ -40,7 +41,7 @@ #define MAX_IO_BUFFERS 512 #define MAX_PARALLEL_IO 16 -//#define DEBUG_BLK_MIGRATION +/* #define DEBUG_BLK_MIGRATION */ #ifdef DEBUG_BLK_MIGRATION #define DPRINTF(fmt, ...) \ @@ -434,10 +435,9 @@ static int init_blk_migration(QEMUFile *f) block_mig_state.total_sector_sum += sectors; if (bmds->shared_base) { - DPRINTF("Start migration for %s with shared base image\n", - bdrv_get_device_name(bs)); + trace_migration_block_init_shared(bdrv_get_device_name(bs)); } else { - DPRINTF("Start full migration for %s\n", bdrv_get_device_name(bs)); + trace_migration_block_init_full(bdrv_get_device_name(bs)); } QSIMPLEQ_INSERT_TAIL(&block_mig_state.bmds_list, bmds, entry); @@ -592,7 +592,7 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds, return (bmds->cur_dirty >= bmds->total_sectors); error: - DPRINTF("Error reading sector %" PRId64 "\n", sector); + trace_migration_block_save_device_dirty(sector); g_free(blk->buf); g_free(blk); return ret; @@ -628,9 +628,9 @@ static int flush_blks(QEMUFile *f) BlkMigBlock *blk; int ret = 0; - DPRINTF("%s Enter submitted %d read_done %d transferred %d\n", - __func__, block_mig_state.submitted, block_mig_state.read_done, - block_mig_state.transferred); + trace_migration_block_flush_blks("Enter", block_mig_state.submitted, + block_mig_state.read_done, + block_mig_state.transferred); blk_mig_lock(); while ((blk = QSIMPLEQ_FIRST(&block_mig_state.blk_list)) != NULL) { @@ -656,9 +656,9 @@ static int flush_blks(QEMUFile *f) } blk_mig_unlock(); - DPRINTF("%s Exit submitted %d read_done %d transferred %d\n", __func__, - block_mig_state.submitted, block_mig_state.read_done, - block_mig_state.transferred); + trace_migration_block_flush_blks("Exit", block_mig_state.submitted, + block_mig_state.read_done, + block_mig_state.transferred); return ret; } @@ -727,8 +727,8 @@ static int block_save_setup(QEMUFile *f, void *opaque) { int ret; - DPRINTF("Enter save live setup submitted %d transferred %d\n", - block_mig_state.submitted, block_mig_state.transferred); + trace_migration_block_save("setup", block_mig_state.submitted, + block_mig_state.transferred); qemu_mutex_lock_iothread(); ret = init_blk_migration(f); @@ -759,8 +759,8 @@ static int block_save_iterate(QEMUFile *f, void *opaque) int64_t last_ftell = qemu_ftell(f); int64_t delta_ftell; - DPRINTF("Enter save live iterate submitted %d transferred %d\n", - block_mig_state.submitted, block_mig_state.transferred); + trace_migration_block_save("iterate", block_mig_state.submitted, + block_mig_state.transferred); ret = flush_blks(f); if (ret) { @@ -825,8 +825,8 @@ static int block_save_complete(QEMUFile *f, void *opaque) { int ret; - DPRINTF("Enter save live complete submitted %d transferred %d\n", - block_mig_state.submitted, block_mig_state.transferred); + trace_migration_block_save("complete", block_mig_state.submitted, + block_mig_state.transferred); ret = flush_blks(f); if (ret) { @@ -851,7 +851,7 @@ static int block_save_complete(QEMUFile *f, void *opaque) /* report completion */ qemu_put_be64(f, (100 << BDRV_SECTOR_BITS) | BLK_MIG_FLAG_PROGRESS); - DPRINTF("Block migration completed\n"); + trace_migration_block_save_complete(); qemu_put_be64(f, BLK_MIG_FLAG_EOS); @@ -884,7 +884,7 @@ static void block_save_pending(QEMUFile *f, void *opaque, uint64_t max_size, pending = max_size + BLK_MIG_BLOCK_SIZE; } - DPRINTF("Enter save live pending %" PRIu64 "\n", pending); + trace_migration_block_save_pending(pending); /* We don't do postcopy */ *res_precopy_only += pending; } @@ -998,7 +998,7 @@ static int block_load(QEMUFile *f, void *opaque, int version_id) (addr == 100) ? '\n' : '\r'); fflush(stdout); } else if (!(flags & BLK_MIG_FLAG_EOS)) { - fprintf(stderr, "Unknown block migration flags: %#x\n", flags); + fprintf(stderr, "Unknown block migration flags: 0x%x\n", flags); return -EINVAL; } ret = qemu_file_get_error(f); diff --git a/migration/migration.c b/migration/migration.c index 0575ecb379..9bb4fee5ac 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -143,9 +143,15 @@ static int migration_maybe_pause(MigrationState *s, int new_state); static void migrate_fd_cancel(MigrationState *s); +static gint page_request_addr_cmp(gconstpointer ap, gconstpointer bp) +{ + uintptr_t a = (uintptr_t) ap, b = (uintptr_t) bp; + + return (a > b) - (a < b); +} + void migration_object_init(void) { - MachineState *ms = MACHINE(qdev_get_machine()); Error *err = NULL; /* This can only be called once. */ @@ -165,20 +171,13 @@ void migration_object_init(void) qemu_event_init(¤t_incoming->main_thread_load_event, false); qemu_sem_init(¤t_incoming->postcopy_pause_sem_dst, 0); qemu_sem_init(¤t_incoming->postcopy_pause_sem_fault, 0); + qemu_mutex_init(¤t_incoming->page_request_mutex); + current_incoming->page_requested = g_tree_new(page_request_addr_cmp); if (!migration_object_check(current_migration, &err)) { error_report_err(err); exit(1); } - - /* - * We cannot really do this in migration_instance_init() since at - * that time global properties are not yet applied, then this - * value will be definitely replaced by something else. - */ - if (ms->enforce_config_section) { - current_migration->send_configuration = true; - } } void migration_shutdown(void) @@ -240,6 +239,11 @@ void migration_incoming_state_destroy(void) qemu_event_reset(&mis->main_thread_load_event); + if (mis->page_requested) { + g_tree_destroy(mis->page_requested); + mis->page_requested = NULL; + } + if (mis->socket_address_list) { qapi_free_SocketAddressList(mis->socket_address_list); mis->socket_address_list = NULL; @@ -316,8 +320,8 @@ error: * Start: Address offset within the RB * Len: Length in bytes required - must be a multiple of pagesize */ -int migrate_send_rp_req_pages(MigrationIncomingState *mis, RAMBlock *rb, - ram_addr_t start) +int migrate_send_rp_message_req_pages(MigrationIncomingState *mis, + RAMBlock *rb, ram_addr_t start) { uint8_t bufc[12 + 1 + 255]; /* start (8), len (4), rbname up to 256 */ size_t msglen = 12; /* start + len */ @@ -353,6 +357,37 @@ int migrate_send_rp_req_pages(MigrationIncomingState *mis, RAMBlock *rb, return migrate_send_rp_message(mis, msg_type, msglen, bufc); } +int migrate_send_rp_req_pages(MigrationIncomingState *mis, + RAMBlock *rb, ram_addr_t start, uint64_t haddr) +{ + void *aligned = (void *)(uintptr_t)(haddr & (-qemu_ram_pagesize(rb))); + bool received; + + WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) { + received = ramblock_recv_bitmap_test_byte_offset(rb, start); + if (!received && !g_tree_lookup(mis->page_requested, aligned)) { + /* + * The page has not been received, and it's not yet in the page + * request list. Queue it. Set the value of element to 1, so that + * things like g_tree_lookup() will return TRUE (1) when found. + */ + g_tree_insert(mis->page_requested, aligned, (gpointer)1); + mis->page_requested_count++; + trace_postcopy_page_req_add(aligned, mis->page_requested_count); + } + } + + /* + * If the page is there, skip sending the message. We don't even need the + * lock because as long as the page arrived, it'll be there forever. + */ + if (received) { + return 0; + } + + return migrate_send_rp_message_req_pages(mis, rb, start); +} + static bool migration_colo_enabled; bool migration_incoming_colo_enabled(void) { @@ -2478,8 +2513,8 @@ static void migrate_handle_rp_req_pages(MigrationState *ms, const char* rbname, * Since we currently insist on matching page sizes, just sanity check * we're being asked for whole host pages. */ - if (start & (our_host_ps-1) || - (len & (our_host_ps-1))) { + if (start & (our_host_ps - 1) || + (len & (our_host_ps - 1))) { error_report("%s: Misaligned page request, start: " RAM_ADDR_FMT " len: %zd", __func__, start, len); mark_source_rp_bad(ms); @@ -3133,9 +3168,6 @@ static MigThrError postcopy_pause(MigrationState *s) while (true) { QEMUFile *file; - migrate_set_state(&s->state, s->state, - MIGRATION_STATUS_POSTCOPY_PAUSED); - /* Current channel is possibly broken. Release it. */ assert(s->to_dst_file); qemu_mutex_lock(&s->qemu_file_lock); @@ -3146,6 +3178,9 @@ static MigThrError postcopy_pause(MigrationState *s) qemu_file_shutdown(file); qemu_fclose(file); + migrate_set_state(&s->state, s->state, + MIGRATION_STATUS_POSTCOPY_PAUSED); + error_report("Detected IO failure for postcopy. " "Migration paused."); diff --git a/migration/migration.h b/migration/migration.h index deb411aaad..d096b77f74 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -104,6 +104,23 @@ struct MigrationIncomingState { /* List of listening socket addresses */ SocketAddressList *socket_address_list; + + /* A tree of pages that we requested to the source VM */ + GTree *page_requested; + /* For debugging purpose only, but would be nice to keep */ + int page_requested_count; + /* + * The mutex helps to maintain the requested pages that we sent to the + * source, IOW, to guarantee coherent between the page_requests tree and + * the per-ramblock receivedmap. Note! This does not guarantee consistency + * of the real page copy procedures (using UFFDIO_[ZERO]COPY). E.g., even + * if one bit in receivedmap is cleared, UFFDIO_COPY could have happened + * for that page already. This is intended so that the mutex won't + * serialize and blocked by slow operations like UFFDIO_* ioctls. However + * this should be enough to make sure the page_requested tree always + * contains valid information. + */ + QemuMutex page_request_mutex; }; MigrationIncomingState *migration_incoming_get_current(void); @@ -124,8 +141,7 @@ struct MigrationClass { DeviceClass parent_class; }; -struct MigrationState -{ +struct MigrationState { /*< private >*/ DeviceState parent_obj; @@ -332,7 +348,9 @@ void migrate_send_rp_shut(MigrationIncomingState *mis, void migrate_send_rp_pong(MigrationIncomingState *mis, uint32_t value); int migrate_send_rp_req_pages(MigrationIncomingState *mis, RAMBlock *rb, - ram_addr_t start); + ram_addr_t start, uint64_t haddr); +int migrate_send_rp_message_req_pages(MigrationIncomingState *mis, + RAMBlock *rb, ram_addr_t start); void migrate_send_rp_recv_bitmap(MigrationIncomingState *mis, char *block_name); void migrate_send_rp_resume_ack(MigrationIncomingState *mis, uint32_t value); diff --git a/migration/page_cache.c b/migration/page_cache.c index 775582f453..098b436223 100644 --- a/migration/page_cache.c +++ b/migration/page_cache.c @@ -18,14 +18,7 @@ #include "qapi/error.h" #include "qemu/host-utils.h" #include "page_cache.h" - -#ifdef DEBUG_CACHE -#define DPRINTF(fmt, ...) \ - do { fprintf(stdout, "cache: " fmt, ## __VA_ARGS__); } while (0) -#else -#define DPRINTF(fmt, ...) \ - do { } while (0) -#endif +#include "trace.h" /* the page in cache will not be replaced in two cycles */ #define CACHED_PAGE_LIFETIME 2 @@ -75,7 +68,7 @@ PageCache *cache_init(int64_t new_size, size_t page_size, Error **errp) cache->num_items = 0; cache->max_num_items = num_pages; - DPRINTF("Setting cache buckets to %" PRId64 "\n", cache->max_num_items); + trace_migration_pagecache_init(cache->max_num_items); /* We prefer not to abort if there is no memory */ cache->page_cache = g_try_malloc((cache->max_num_items) * @@ -169,7 +162,7 @@ int cache_insert(PageCache *cache, uint64_t addr, const uint8_t *pdata, if (!it->it_data) { it->it_data = g_try_malloc(cache->page_size); if (!it->it_data) { - DPRINTF("Error allocating page\n"); + trace_migration_pagecache_insert(); return -1; } cache->num_items++; diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c index 0a2f88a87d..d3bb3a744b 100644 --- a/migration/postcopy-ram.c +++ b/migration/postcopy-ram.c @@ -403,7 +403,7 @@ bool postcopy_ram_supported_by_host(MigrationIncomingState *mis) strerror(errno)); goto out; } - g_assert(((size_t)testarea & (pagesize-1)) == 0); + g_assert(((size_t)testarea & (pagesize - 1)) == 0); reg_struct.range.start = (uintptr_t)testarea; reg_struct.range.len = pagesize; @@ -684,7 +684,7 @@ int postcopy_request_shared_page(struct PostCopyFD *pcfd, RAMBlock *rb, qemu_ram_get_idstr(rb), rb_offset); return postcopy_wake_shared(pcfd, client_addr, rb); } - migrate_send_rp_req_pages(mis, rb, aligned_rbo); + migrate_send_rp_req_pages(mis, rb, aligned_rbo, client_addr); return 0; } @@ -979,7 +979,8 @@ retry: * Send the request to the source - we want to request one * of our host page sizes (which is >= TPS) */ - ret = migrate_send_rp_req_pages(mis, rb, rb_offset); + ret = migrate_send_rp_req_pages(mis, rb, rb_offset, + msg.arg.pagefault.address); if (ret) { /* May be network failure, try to wait for recovery */ if (ret == -EIO && postcopy_pause_fault_thread(mis)) { @@ -1128,10 +1129,12 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis) return 0; } -static int qemu_ufd_copy_ioctl(int userfault_fd, void *host_addr, +static int qemu_ufd_copy_ioctl(MigrationIncomingState *mis, void *host_addr, void *from_addr, uint64_t pagesize, RAMBlock *rb) { + int userfault_fd = mis->userfault_fd; int ret; + if (from_addr) { struct uffdio_copy copy_struct; copy_struct.dst = (uint64_t)(uintptr_t)host_addr; @@ -1147,10 +1150,20 @@ static int qemu_ufd_copy_ioctl(int userfault_fd, void *host_addr, ret = ioctl(userfault_fd, UFFDIO_ZEROPAGE, &zero_struct); } if (!ret) { + qemu_mutex_lock(&mis->page_request_mutex); ramblock_recv_bitmap_set_range(rb, host_addr, pagesize / qemu_target_page_size()); + /* + * If this page resolves a page fault for a previous recorded faulted + * address, take a special note to maintain the requested page list. + */ + if (g_tree_lookup(mis->page_requested, host_addr)) { + g_tree_remove(mis->page_requested, host_addr); + mis->page_requested_count--; + trace_postcopy_page_req_del(host_addr, mis->page_requested_count); + } + qemu_mutex_unlock(&mis->page_request_mutex); mark_postcopy_blocktime_end((uintptr_t)host_addr); - } return ret; } @@ -1185,7 +1198,7 @@ int postcopy_place_page(MigrationIncomingState *mis, void *host, void *from, * which would be slightly cheaper, but we'd have to be careful * of the order of updating our page state. */ - if (qemu_ufd_copy_ioctl(mis->userfault_fd, host, from, pagesize, rb)) { + if (qemu_ufd_copy_ioctl(mis, host, from, pagesize, rb)) { int e = errno; error_report("%s: %s copy host: %p from: %p (size: %zd)", __func__, strerror(e), host, from, pagesize); @@ -1212,7 +1225,7 @@ int postcopy_place_page_zero(MigrationIncomingState *mis, void *host, * but it's not available for everything (e.g. hugetlbpages) */ if (qemu_ram_is_uf_zeroable(rb)) { - if (qemu_ufd_copy_ioctl(mis->userfault_fd, host, NULL, pagesize, rb)) { + if (qemu_ufd_copy_ioctl(mis, host, NULL, pagesize, rb)) { int e = errno; error_report("%s: %s zero host: %p", __func__, strerror(e), host); diff --git a/migration/ram.c b/migration/ram.c index 433489d633..2da2b622ab 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -101,14 +101,16 @@ static struct { static void XBZRLE_cache_lock(void) { - if (migrate_use_xbzrle()) + if (migrate_use_xbzrle()) { qemu_mutex_lock(&XBZRLE.lock); + } } static void XBZRLE_cache_unlock(void) { - if (migrate_use_xbzrle()) + if (migrate_use_xbzrle()) { qemu_mutex_unlock(&XBZRLE.lock); + } } /** @@ -1563,7 +1565,7 @@ int ram_save_queue_pages(const char *rbname, ram_addr_t start, ram_addr_t len) rs->last_req_rb = ramblock; } trace_ram_save_queue_pages(ramblock->idstr, start, len); - if (start+len > ramblock->used_length) { + if (start + len > ramblock->used_length) { error_report("%s request overrun start=" RAM_ADDR_FMT " len=" RAM_ADDR_FMT " blocklen=" RAM_ADDR_FMT, __func__, start, len, ramblock->used_length); @@ -2741,7 +2743,7 @@ static int load_xbzrle(QEMUFile *f, ram_addr_t addr, void *host) */ static inline RAMBlock *ram_block_from_stream(QEMUFile *f, int flags) { - static RAMBlock *block = NULL; + static RAMBlock *block; char id[256]; uint8_t len; @@ -3298,7 +3300,7 @@ static int ram_load_postcopy(QEMUFile *f) multifd_recv_sync_main(); break; default: - error_report("Unknown combination of migration flags: %#x" + error_report("Unknown combination of migration flags: 0x%x" " (postcopy mode)", flags); ret = -EINVAL; break; @@ -3576,7 +3578,7 @@ static int ram_load_precopy(QEMUFile *f) if (flags & RAM_SAVE_FLAG_HOOK) { ram_control_load_hook(f, RAM_CONTROL_HOOK, NULL); } else { - error_report("Unknown combination of migration flags: %#x", + error_report("Unknown combination of migration flags: 0x%x", flags); ret = -EINVAL; } diff --git a/migration/rdma.c b/migration/rdma.c index 0340841fad..00eac34232 100644 --- a/migration/rdma.c +++ b/migration/rdma.c @@ -273,7 +273,8 @@ static uint64_t htonll(uint64_t v) return u.llv; } -static uint64_t ntohll(uint64_t v) { +static uint64_t ntohll(uint64_t v) +{ union { uint32_t lv[2]; uint64_t llv; } u; u.llv = v; return ((uint64_t)ntohl(u.lv[0]) << 32) | (uint64_t) ntohl(u.lv[1]); @@ -854,7 +855,7 @@ static int qemu_rdma_broken_ipv6_kernel(struct ibv_context *verbs, Error **errp) */ if (!verbs) { int num_devices, x; - struct ibv_device ** dev_list = ibv_get_device_list(&num_devices); + struct ibv_device **dev_list = ibv_get_device_list(&num_devices); bool roce_found = false; bool ib_found = false; @@ -1288,7 +1289,7 @@ const char *print_wrid(int wrid) * workload information or LRU information is available, do not attempt to use * this feature except for basic testing. */ -//#define RDMA_UNREGISTRATION_EXAMPLE +/* #define RDMA_UNREGISTRATION_EXAMPLE */ /* * Perform a non-optimized memory unregistration after every transfer diff --git a/migration/savevm.c b/migration/savevm.c index ff33e210eb..21ccba9fb3 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -63,7 +63,7 @@ #include "qemu/bitmap.h" #include "net/announce.h" -const unsigned int postcopy_ram_discard_version = 0; +const unsigned int postcopy_ram_discard_version; /* Subcommands for QEMU_VM_COMMAND */ enum qemu_vm_cmd { @@ -520,7 +520,7 @@ static const VMStateDescription vmstate_configuration = { VMSTATE_VBUFFER_ALLOC_UINT32(name, SaveState, 0, NULL, len), VMSTATE_END_OF_LIST() }, - .subsections = (const VMStateDescription*[]) { + .subsections = (const VMStateDescription *[]) { &vmstate_target_page_bits, &vmstate_capabilites, &vmstate_uuid, @@ -2010,6 +2010,49 @@ static int loadvm_postcopy_handle_run(MigrationIncomingState *mis) return LOADVM_QUIT; } +/* We must be with page_request_mutex held */ +static gboolean postcopy_sync_page_req(gpointer key, gpointer value, + gpointer data) +{ + MigrationIncomingState *mis = data; + void *host_addr = (void *) key; + ram_addr_t rb_offset; + RAMBlock *rb; + int ret; + + rb = qemu_ram_block_from_host(host_addr, true, &rb_offset); + if (!rb) { + /* + * This should _never_ happen. However be nice for a migrating VM to + * not crash/assert. Post an error (note: intended to not use *_once + * because we do want to see all the illegal addresses; and this can + * never be triggered by the guest so we're safe) and move on next. + */ + error_report("%s: illegal host addr %p", __func__, host_addr); + /* Try the next entry */ + return FALSE; + } + + ret = migrate_send_rp_message_req_pages(mis, rb, rb_offset); + if (ret) { + /* Please refer to above comment. */ + error_report("%s: send rp message failed for addr %p", + __func__, host_addr); + return FALSE; + } + + trace_postcopy_page_req_sync(host_addr); + + return FALSE; +} + +static void migrate_send_rp_req_pages_pending(MigrationIncomingState *mis) +{ + WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) { + g_tree_foreach(mis->page_requested, postcopy_sync_page_req, mis); + } +} + static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis) { if (mis->state != MIGRATION_STATUS_POSTCOPY_RECOVER) { @@ -2032,6 +2075,20 @@ static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis) /* Tell source that "we are ready" */ migrate_send_rp_resume_ack(mis, MIGRATION_RESUME_ACK_VALUE); + /* + * After a postcopy recovery, the source should have lost the postcopy + * queue, or potentially the requested pages could have been lost during + * the network down phase. Let's re-sync with the source VM by re-sending + * all the pending pages that we eagerly need, so these threads won't get + * blocked too long due to the recovery. + * + * Without this procedure, the faulted destination VM threads (waiting for + * page requests right before the postcopy is interrupted) can keep hanging + * until the pages are sent by the source during the background copying of + * pages, or another thread faulted on the same address accidentally. + */ + migrate_send_rp_req_pages_pending(mis); + return 0; } diff --git a/migration/trace-events b/migration/trace-events index 338f38b3dd..75de5004ac 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -49,6 +49,7 @@ vmstate_save(const char *idstr, const char *vmsd_name) "%s, %s" vmstate_load(const char *idstr, const char *vmsd_name) "%s, %s" postcopy_pause_incoming(void) "" postcopy_pause_incoming_continued(void) "" +postcopy_page_req_sync(void *host_addr) "sync page req %p" # vmstate.c vmstate_load_field_error(const char *field, int ret) "field \"%s\" load failed, ret = %d" @@ -162,6 +163,7 @@ postcopy_pause_return_path(void) "" postcopy_pause_return_path_continued(void) "" postcopy_pause_continued(void) "" postcopy_start_set_run(void) "" +postcopy_page_req_add(void *addr, int count) "new page req %p total %d" source_return_path_thread_bad_end(void) "" source_return_path_thread_end(void) "" source_return_path_thread_entry(void) "" @@ -272,6 +274,7 @@ postcopy_ram_incoming_cleanup_blocktime(uint64_t total) "total blocktime %" PRIu postcopy_request_shared_page(const char *sharer, const char *rb, uint64_t rb_offset) "for %s in %s offset 0x%"PRIx64 postcopy_request_shared_page_present(const char *sharer, const char *rb, uint64_t rb_offset) "%s already %s offset 0x%"PRIx64 postcopy_wake_shared(uint64_t client_addr, const char *rb) "at 0x%"PRIx64" in %s" +postcopy_page_req_del(void *addr, int count) "resolved page req %p total %d" get_mem_fault_cpu_index(int cpu, uint32_t pid) "cpu: %d, pid: %u" @@ -325,3 +328,16 @@ get_ramblock_vfn_hash(const char *idstr, uint64_t vfn, uint32_t crc) "ramblock n calc_page_dirty_rate(const char *idstr, uint32_t new_crc, uint32_t old_crc) "ramblock name: %s, new crc: %" PRIu32 ", old crc: %" PRIu32 skip_sample_ramblock(const char *idstr, uint64_t ramblock_size) "ramblock name: %s, ramblock size: %" PRIu64 find_page_matched(const char *idstr) "ramblock %s addr or size changed" + +# block.c +migration_block_init_shared(const char *blk_device_name) "Start migration for %s with shared base image" +migration_block_init_full(const char *blk_device_name) "Start full migration for %s" +migration_block_save_device_dirty(int64_t sector) "Error reading sector %" PRId64 +migration_block_flush_blks(const char *action, int submitted, int read_done, int transferred) "%s submitted %d read_done %d transferred %d" +migration_block_save(const char *mig_stage, int submitted, int transferred) "Enter save live %s submitted %d transferred %d" +migration_block_save_complete(void) "Block migration completed" +migration_block_save_pending(uint64_t pending) "Enter save live pending %" PRIu64 + +# page_cache.c +migration_pagecache_init(int64_t max_num_items) "Setting cache buckets to %" PRId64 +migration_pagecache_insert(void) "Error allocating page" diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c index 35e784c9d9..e22d41d73d 100644 --- a/migration/vmstate-types.c +++ b/migration/vmstate-types.c @@ -420,32 +420,6 @@ const VMStateInfo vmstate_info_uint16_equal = { .put = put_uint16, }; -/* floating point */ - -static int get_float64(QEMUFile *f, void *pv, size_t size, - const VMStateField *field) -{ - float64 *v = pv; - - *v = make_float64(qemu_get_be64(f)); - return 0; -} - -static int put_float64(QEMUFile *f, void *pv, size_t size, - const VMStateField *field, QJSON *vmdesc) -{ - uint64_t *v = pv; - - qemu_put_be64(f, float64_val(*v)); - return 0; -} - -const VMStateInfo vmstate_info_float64 = { - .name = "float64", - .get = get_float64, - .put = put_float64, -}; - /* CPU_DoubleU type */ static int get_cpudouble(QEMUFile *f, void *pv, size_t size, diff --git a/migration/vmstate.c b/migration/vmstate.c index bafa890384..e9d2aef66b 100644 --- a/migration/vmstate.c +++ b/migration/vmstate.c @@ -32,13 +32,13 @@ static int vmstate_n_elems(void *opaque, const VMStateField *field) if (field->flags & VMS_ARRAY) { n_elems = field->num; } else if (field->flags & VMS_VARRAY_INT32) { - n_elems = *(int32_t *)(opaque+field->num_offset); + n_elems = *(int32_t *)(opaque + field->num_offset); } else if (field->flags & VMS_VARRAY_UINT32) { - n_elems = *(uint32_t *)(opaque+field->num_offset); + n_elems = *(uint32_t *)(opaque + field->num_offset); } else if (field->flags & VMS_VARRAY_UINT16) { - n_elems = *(uint16_t *)(opaque+field->num_offset); + n_elems = *(uint16_t *)(opaque + field->num_offset); } else if (field->flags & VMS_VARRAY_UINT8) { - n_elems = *(uint8_t *)(opaque+field->num_offset); + n_elems = *(uint8_t *)(opaque + field->num_offset); } if (field->flags & VMS_MULTIPLY_ELEMENTS) { @@ -54,7 +54,7 @@ static int vmstate_size(void *opaque, const VMStateField *field) int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); + size = *(int32_t *)(opaque + field->size_offset); if (field->flags & VMS_MULTIPLY) { size *= field->size; } diff --git a/net/meson.build b/net/meson.build index 1c7e3a3cb9..1076b0a7ab 100644 --- a/net/meson.build +++ b/net/meson.build @@ -7,7 +7,6 @@ softmmu_ss.add(files( 'eth.c', 'filter-buffer.c', 'filter-mirror.c', - 'filter-replay.c', 'filter-rewriter.c', 'filter.c', 'hub.c', @@ -17,6 +16,8 @@ softmmu_ss.add(files( 'util.c', )) +softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('filter-replay.c')) + softmmu_ss.add(when: 'CONFIG_L2TPV3', if_true: files('l2tpv3.c')) softmmu_ss.add(when: slirp, if_true: files('slirp.c')) softmmu_ss.add(when: ['CONFIG_VDE', vde], if_true: files('vde.c')) diff --git a/pc-bios/descriptors/meson.build b/pc-bios/descriptors/meson.build index 3798d32372..7040834573 100644 --- a/pc-bios/descriptors/meson.build +++ b/pc-bios/descriptors/meson.build @@ -8,7 +8,7 @@ foreach f: [ ] configure_file(input: files(f), output: f, - configuration: {'DATADIR': config_host['qemu_datadir']}, - install: install_blobs, + configuration: {'DATADIR': qemu_datadir}, + install: get_option('install_blobs'), install_dir: qemu_datadir / 'firmware') endforeach diff --git a/pc-bios/meson.build b/pc-bios/meson.build index a0d21be432..fab323af84 100644 --- a/pc-bios/meson.build +++ b/pc-bios/meson.build @@ -1,7 +1,8 @@ -bzip2 = find_program('bzip2') - -install_blobs = 'INSTALL_BLOBS' in config_host -if 'DECOMPRESS_EDK2_BLOBS' in config_host +if 'arm-softmmu' in target_dirs or \ + 'aarch64-softmmu' in target_dirs or \ + 'i386-softmmu' in target_dirs or \ + 'x86_64-softmmu' in target_dirs + bzip2 = find_program('bzip2', required: true) fds = [ 'edk2-aarch64-code.fd', 'edk2-arm-code.fd', @@ -18,7 +19,7 @@ if 'DECOMPRESS_EDK2_BLOBS' in config_host output: f, input: '@0@.bz2'.format(f), capture: true, - install: install_blobs, + install: get_option('install_blobs'), install_dir: qemu_datadir, command: [ bzip2, '-dc', '@INPUT0@' ]) endforeach @@ -85,8 +86,8 @@ blobs = files( 'npcm7xx_bootrom.bin', ) -if install_blobs - install_data(blobs, install_dir: config_host['qemu_datadir']) +if get_option('install_blobs') + install_data(blobs, install_dir: qemu_datadir) endif subdir('descriptors') diff --git a/qemu-options.hx b/qemu-options.hx index 1da52a269c..2c83390504 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -34,7 +34,6 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " dea-key-wrap=on|off controls support for DEA key wrapping (default=on)\n" " suppress-vmdesc=on|off disables self-describing migration (default=off)\n" " nvdimm=on|off controls NVDIMM support (default=off)\n" - " enforce-config-section=on|off enforce configuration section migration (default=off)\n" " memory-encryption=@var{} memory encryption object to use (default=none)\n" " hmat=on|off controls ACPI HMAT support (default=off)\n", QEMU_ARCH_ALL) @@ -91,13 +90,6 @@ SRST ``nvdimm=on|off`` Enables or disables NVDIMM support. The default is off. - ``enforce-config-section=on|off`` - If ``enforce-config-section`` is set to on, force migration code - to send configuration section even if the machine-type sets the - ``migration.send-configuration`` property to off. NOTE: this - parameter is deprecated. Please use ``-global`` - ``migration.send-configuration``\ =on\|off instead. - ``memory-encryption=`` Memory encryption object to use. The default is none. @@ -4351,9 +4343,6 @@ SRST Enable FIPS 140-2 compliance mode. ERST -HXCOMM Deprecated by -accel tcg -DEF("no-kvm", 0, QEMU_OPTION_no_kvm, "", QEMU_ARCH_I386) - DEF("msg", HAS_ARG, QEMU_OPTION_msg, "-msg [timestamp[=on|off]][,guest-name=[on|off]]\n" " control error message format\n" diff --git a/replay/meson.build b/replay/meson.build index f91163fb1e..21aefad220 100644 --- a/replay/meson.build +++ b/replay/meson.build @@ -1,4 +1,4 @@ -softmmu_ss.add(files( +softmmu_ss.add(when: 'CONFIG_TCG', if_true: files( 'replay.c', 'replay-internal.c', 'replay-events.c', @@ -10,4 +10,4 @@ softmmu_ss.add(files( 'replay-audio.c', 'replay-random.c', 'replay-debugging.c', -)) +), if_false: files('stubs-system.c')) diff --git a/replay/stubs-system.c b/replay/stubs-system.c new file mode 100644 index 0000000000..5c262b08f1 --- /dev/null +++ b/replay/stubs-system.c @@ -0,0 +1,96 @@ +#include "qemu/osdep.h" +#include "sysemu/replay.h" +#include "ui/input.h" + +void replay_input_event(QemuConsole *src, InputEvent *evt) +{ + qemu_input_event_send_impl(src, evt); +} + +void replay_input_sync_event(void) +{ + qemu_input_event_sync_impl(); +} + +void replay_add_blocker(Error *reason) +{ +} +void replay_audio_in(size_t *recorded, void *samples, size_t *wpos, size_t size) +{ +} +void replay_audio_out(size_t *played) +{ +} +void replay_breakpoint(void) +{ +} +bool replay_can_snapshot(void) +{ + return true; +} +void replay_configure(struct QemuOpts *opts) +{ +} +void replay_flush_events(void) +{ +} +void replay_gdb_attached(void) +{ +} +bool replay_running_debug(void) +{ + return false; +} +void replay_shutdown_request(ShutdownCause cause) +{ +} +void replay_start(void) +{ +} +void replay_vmstate_init(void) +{ +} + +#include "monitor/monitor.h" +#include "monitor/hmp.h" +#include "qapi/qapi-commands-replay.h" +#include "qapi/error.h" +#include "qemu/error-report.h" + +void hmp_info_replay(Monitor *mon, const QDict *qdict) +{ + error_report("replay support not available"); +} +void hmp_replay_break(Monitor *mon, const QDict *qdict) +{ + error_report("replay support not available"); +} +void hmp_replay_delete_break(Monitor *mon, const QDict *qdict) +{ + error_report("replay support not available"); +} +void hmp_replay_seek(Monitor *mon, const QDict *qdict) +{ + error_report("replay support not available"); +} +ReplayInfo *qmp_query_replay(Error **errp) +{ + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + "replay support not available"); + return NULL; +} +void qmp_replay_break(int64_t icount, Error **errp) +{ + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + "replay support not available"); +} +void qmp_replay_delete_break(Error **errp) +{ + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + "replay support not available"); +} +void qmp_replay_seek(int64_t icount, Error **errp) +{ + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + "replay support not available"); +} diff --git a/scripts/tracetool.py b/scripts/tracetool.py index 31146242b7..31146242b7 100644..100755 --- a/scripts/tracetool.py +++ b/scripts/tracetool.py diff --git a/scripts/undefsym.py b/scripts/undefsym.py index 69a895cd26..4b6a72d95f 100644 --- a/scripts/undefsym.py +++ b/scripts/undefsym.py @@ -15,12 +15,11 @@ def filter_lines_set(stdout, from_staticlib): linesSet = set() for line in stdout.splitlines(): tokens = line.split(b' ') - if len(tokens) >= 1: - if len(tokens) > 1: - if from_staticlib and tokens[1] == b'U': - continue - if not from_staticlib and tokens[1] != b'U': - continue + if len(tokens) >= 2: + if from_staticlib and tokens[1] == b'U': + continue + if not from_staticlib and tokens[1] != b'U': + continue new_line = b'-Wl,-u,' + tokens[0] if not new_line in linesSet: linesSet.add(new_line) diff --git a/softmmu/vl.c b/softmmu/vl.c index e86d20334b..7c1c6d37ef 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -3506,10 +3506,6 @@ void qemu_init(int argc, char **argv, char **envp) exit(1); } break; - case QEMU_OPTION_no_kvm: - olist = qemu_find_opts("machine"); - qemu_opts_parse_noisily(olist, "accel=tcg", false); - break; case QEMU_OPTION_accel: accel_opts = qemu_opts_parse_noisily(qemu_find_opts("accel"), optarg, true); @@ -3978,18 +3974,6 @@ void qemu_init(int argc, char **argv, char **envp) exit(0); } - /* machine_class: default to UP */ - machine_class->max_cpus = machine_class->max_cpus ?: 1; - machine_class->min_cpus = machine_class->min_cpus ?: 1; - machine_class->default_cpus = machine_class->default_cpus ?: 1; - - /* default to machine_class->default_cpus */ - current_machine->smp.cpus = machine_class->default_cpus; - current_machine->smp.max_cpus = machine_class->default_cpus; - current_machine->smp.cores = 1; - current_machine->smp.threads = 1; - current_machine->smp.sockets = 1; - machine_class->smp_parse(current_machine, qemu_opts_find(qemu_find_opts("smp-opts"), NULL)); diff --git a/stubs/meson.build b/stubs/meson.build index 7b733fadb7..82b7ba60ab 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -33,7 +33,6 @@ stub_ss.add(files('qtest.c')) stub_ss.add(files('ram-block.c')) stub_ss.add(files('ramfb.c')) stub_ss.add(files('replay.c')) -stub_ss.add(files('replay-user.c')) stub_ss.add(files('runstate-check.c')) stub_ss.add(files('set-fd-handler.c')) stub_ss.add(files('sysbus.c')) @@ -47,6 +46,9 @@ stub_ss.add(files('vmstate.c')) stub_ss.add(files('vm-stop.c')) stub_ss.add(files('win32-kbd-hook.c')) stub_ss.add(files('cpu-synchronize-state.c')) +if have_block + stub_ss.add(files('replay-tools.c')) +endif if have_system stub_ss.add(files('semihost.c')) stub_ss.add(files('xen-hw-stub.c')) diff --git a/stubs/replay-tools.c b/stubs/replay-tools.c new file mode 100644 index 0000000000..c06b360e22 --- /dev/null +++ b/stubs/replay-tools.c @@ -0,0 +1,83 @@ +#include "qemu/osdep.h" +#include "sysemu/replay.h" +#include "block/aio.h" + +bool replay_events_enabled(void) +{ + return false; +} + +int64_t replay_save_clock(unsigned int kind, int64_t clock, int64_t raw_icount) +{ + abort(); + return 0; +} + +int64_t replay_read_clock(unsigned int kind) +{ + abort(); + return 0; +} + +uint64_t replay_get_current_icount(void) +{ + return 0; +} + +void replay_bh_schedule_event(QEMUBH *bh) +{ + qemu_bh_schedule(bh); +} + +void replay_bh_schedule_oneshot_event(AioContext *ctx, + QEMUBHFunc *cb, void *opaque) +{ + aio_bh_schedule_oneshot(ctx, cb, opaque); +} + +bool replay_checkpoint(ReplayCheckpoint checkpoint) +{ + return true; +} + +void replay_mutex_lock(void) +{ +} + +void replay_mutex_unlock(void) +{ +} + +void replay_register_char_driver(Chardev *chr) +{ +} + +void replay_chr_be_write(Chardev *s, uint8_t *buf, int len) +{ + abort(); +} + +void replay_char_write_event_save(int res, int offset) +{ + abort(); +} + +void replay_char_write_event_load(int *res, int *offset) +{ + abort(); +} + +int replay_char_read_all_load(uint8_t *buf) +{ + abort(); +} + +void replay_char_read_all_save_error(int res) +{ + abort(); +} + +void replay_char_read_all_save_buf(uint8_t *buf, int offset) +{ + abort(); +} diff --git a/stubs/replay-user.c b/stubs/replay-user.c deleted file mode 100644 index 2ad9e27203..0000000000 --- a/stubs/replay-user.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "qemu/osdep.h" -#include "sysemu/replay.h" -#include "sysemu/sysemu.h" - -void replay_bh_schedule_oneshot_event(AioContext *ctx, - QEMUBHFunc *cb, void *opaque) -{ - aio_bh_schedule_oneshot(ctx, cb, opaque); -} diff --git a/stubs/replay.c b/stubs/replay.c index 45ebe77fb9..9d5b4be339 100644 --- a/stubs/replay.c +++ b/stubs/replay.c @@ -3,83 +3,10 @@ ReplayMode replay_mode; -int64_t replay_save_clock(unsigned int kind, int64_t clock, int64_t raw_icount) -{ - abort(); - return 0; -} - -int64_t replay_read_clock(unsigned int kind) -{ - abort(); - return 0; -} - -bool replay_checkpoint(ReplayCheckpoint checkpoint) -{ - return true; -} - -bool replay_events_enabled(void) -{ - return false; -} - void replay_finish(void) { } -void replay_register_char_driver(Chardev *chr) -{ -} - -void replay_chr_be_write(Chardev *s, uint8_t *buf, int len) -{ - abort(); -} - -void replay_char_write_event_save(int res, int offset) -{ - abort(); -} - -void replay_char_write_event_load(int *res, int *offset) -{ - abort(); -} - -int replay_char_read_all_load(uint8_t *buf) -{ - abort(); -} - -void replay_char_read_all_save_error(int res) -{ - abort(); -} - -void replay_char_read_all_save_buf(uint8_t *buf, int offset) -{ - abort(); -} - -void replay_block_event(QEMUBH *bh, uint64_t id) -{ -} - -uint64_t blkreplay_next_id(void) -{ - return 0; -} - -void replay_mutex_lock(void) -{ -} - -void replay_mutex_unlock(void) -{ -} - void replay_save_random(int ret, void *buf, size_t len) { } @@ -89,11 +16,6 @@ int replay_read_random(void *buf, size_t len) return 0; } -uint64_t replay_get_current_icount(void) -{ - return 0; -} - bool replay_reverse_step(void) { return false; diff --git a/tests/acceptance/boot_linux.py b/tests/acceptance/boot_linux.py index c743e231f4..1da4a53d6a 100644 --- a/tests/acceptance/boot_linux.py +++ b/tests/acceptance/boot_linux.py @@ -57,7 +57,7 @@ class BootLinuxBase(Test): self.cancel('Failed to download/prepare boot image') return boot.path - def download_cloudinit(self): + 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): diff --git a/tests/acceptance/virtiofs_submounts.py b/tests/acceptance/virtiofs_submounts.py new file mode 100644 index 0000000000..8b207b3e57 --- /dev/null +++ b/tests/acceptance/virtiofs_submounts.py @@ -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() diff --git a/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh new file mode 100644 index 0000000000..2a6579a0fe --- /dev/null +++ b/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh @@ -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.' diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh new file mode 100644 index 0000000000..729cb2d1a5 --- /dev/null +++ b/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh @@ -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.' diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest.sh b/tests/acceptance/virtiofs_submounts.py.data/guest.sh new file mode 100644 index 0000000000..59ba40fde1 --- /dev/null +++ b/tests/acceptance/virtiofs_submounts.py.data/guest.sh @@ -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." diff --git a/tests/acceptance/virtiofs_submounts.py.data/host.sh b/tests/acceptance/virtiofs_submounts.py.data/host.sh new file mode 100644 index 0000000000..d8a9afebdb --- /dev/null +++ b/tests/acceptance/virtiofs_submounts.py.data/host.sh @@ -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.' diff --git a/tests/fp/meson.build b/tests/fp/meson.build index 24739ad421..3d4fb00f9d 100644 --- a/tests/fp/meson.build +++ b/tests/fp/meson.build @@ -603,7 +603,7 @@ fptest_rounding_args = ['-r', 'all'] # FIXME: i32_to_extF80 (broken), i64_to_extF80 (broken) # extF80_roundToInt (broken) foreach k, v : softfloat_conv_tests - test('fp-test:' + k, fptest, + test('fp-test-' + k, fptest, args: fptest_args + fptest_rounding_args + v.split(), suite: ['softfloat', 'softfloat-conv']) endforeach @@ -612,13 +612,13 @@ endforeach # extF80_{mulAdd} (missing) foreach k, v : softfloat_tests extF80_broken = ['lt_quiet', 'rem'].contains(k) - test('fp-test:' + k, fptest, + test('fp-test-' + k, fptest, args: fptest_args + fptest_rounding_args + ['f16_' + k, 'f32_' + k, 'f64_' + k, 'f128_' + k] + (extF80_broken ? [] : ['extF80_' + k]), suite: ['softfloat', 'softfloat-' + v]) endforeach -test('fp-test:mulAdd', fptest, +test('fp-test-mulAdd', fptest, # no fptest_rounding_args args: fptest_args + ['f16_mulAdd', 'f32_mulAdd', 'f64_mulAdd', 'f128_mulAdd'], diff --git a/tests/ptimer-test-stubs.c b/tests/ptimer-test-stubs.c index e935a1395e..7f801a4d09 100644 --- a/tests/ptimer-test-stubs.c +++ b/tests/ptimer-test-stubs.c @@ -122,8 +122,3 @@ void qemu_bh_delete(QEMUBH *bh) { g_free(bh); } - -void replay_bh_schedule_event(QEMUBH *bh) -{ - bh->cb(bh->opaque); -} diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c index 3830a40d10..f23a5335a8 100644 --- a/tests/qtest/bios-tables-test.c +++ b/tests/qtest/bios-tables-test.c @@ -127,6 +127,9 @@ static void free_test_data(test_data *data) { int i; + if (!data->tables) { + return; + } for (i = 0; i < data->tables->len; ++i) { cleanup_table_descriptor(&g_array_index(data->tables, AcpiSdtTable, i)); } @@ -656,6 +659,13 @@ static void test_acpi_one(const char *params, test_data *data) char *args; bool use_uefi = data->uefi_fl1 && data->uefi_fl2; +#ifndef CONFIG_TCG + if (data->tcg_only) { + g_test_skip("TCG disabled, skipping ACPI tcg_only test"); + return; + } +#endif /* CONFIG_TCG */ + if (use_uefi) { /* * TODO: convert '-drive if=pflash' to new syntax (see e33763be7cd3) diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 7e0ecaa2c5..ba8ebeead6 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -252,7 +252,7 @@ foreach dir : target_dirs } endif # FIXME: missing dependency on the emulator binary and qemu-img - test('qtest-@0@: @1@'.format(target_base, test), + test('qtest-@0@/@1@'.format(target_base, test), qtest_executables[test], depends: [test_deps, qtest_emulator], env: qtest_env, diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index f410ec5996..f2142fbd3c 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -464,6 +464,10 @@ static void migrate_postcopy_start(QTestState *from, QTestState *to) } typedef struct { + /* + * QTEST_LOG=1 may override this. When QTEST_LOG=1, we always dump errors + * unconditionally, because it means the user would like to be verbose. + */ bool hide_stderr; bool use_shmem; /* only launch the target process */ @@ -557,7 +561,7 @@ static int test_migrate_start(QTestState **from, QTestState **to, g_free(bootpath); - if (args->hide_stderr) { + if (!getenv("QTEST_LOG") && args->hide_stderr) { ignore_stderr = "2>/dev/null"; } else { ignore_stderr = ""; diff --git a/tests/qtest/qmp-cmd-test.c b/tests/qtest/qmp-cmd-test.c index 8a4c570e83..1c7186e53c 100644 --- a/tests/qtest/qmp-cmd-test.c +++ b/tests/qtest/qmp-cmd-test.c @@ -31,6 +31,9 @@ static int query_error_class(const char *cmd) #ifndef CONFIG_SPICE { "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND }, #endif +#ifndef CONFIG_TCG + { "query-replay", ERROR_CLASS_COMMAND_NOT_FOUND }, +#endif #ifndef CONFIG_VNC { "query-vnc", ERROR_CLASS_GENERIC_ERROR }, { "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR }, diff --git a/tools/meson.build b/tools/meson.build index 513bd2ff4f..76bf84df52 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -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 diff --git a/tools/virtiofsd/fuse_common.h b/tools/virtiofsd/fuse_common.h index 686c42c0a5..870544fe13 100644 --- a/tools/virtiofsd/fuse_common.h +++ b/tools/virtiofsd/fuse_common.h @@ -353,6 +353,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 * * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine diff --git a/tools/virtiofsd/fuse_lowlevel.c b/tools/virtiofsd/fuse_lowlevel.c index 4d1ba2925d..5198f627bc 100644 --- a/tools/virtiofsd/fuse_lowlevel.c +++ b/tools/virtiofsd/fuse_lowlevel.c @@ -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)) { diff --git a/tools/virtiofsd/fuse_lowlevel.h b/tools/virtiofsd/fuse_lowlevel.h index 562fd5241e..1ff6ba1e4f 100644 --- a/tools/virtiofsd/fuse_lowlevel.h +++ b/tools/virtiofsd/fuse_lowlevel.h @@ -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; }; /** @@ -1309,6 +1314,21 @@ 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 * * Possible requests: diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c index 85770d63f1..a212f32931 100644 --- a/tools/virtiofsd/helper.c +++ b/tools/virtiofsd/helper.c @@ -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" diff --git a/tools/virtiofsd/meson.build b/tools/virtiofsd/meson.build index 50022ed89e..e1a4dc98d9 100644 --- a/tools/virtiofsd/meson.build +++ b/tools/virtiofsd/meson.build @@ -15,5 +15,5 @@ executable('virtiofsd', files( configure_file(input: '50-qemu-virtiofsd.json.in', output: '50-qemu-virtiofsd.json', - configuration: config_host, + configuration: { 'libexecdir' : get_option('libexecdir') }, install_dir: qemu_datadir / 'vhost-user') diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c index ff53df4451..4db50046d4 100644 --- a/tools/virtiofsd/passthrough_ll.c +++ b/tools/virtiofsd/passthrough_ll.c @@ -40,6 +40,7 @@ #include "fuse_virtio.h" #include "fuse_log.h" #include "fuse_lowlevel.h" +#include "standard-headers/linux/fuse.h" #include <assert.h> #include <cap-ng.h> #include <dirent.h> @@ -64,6 +65,7 @@ #include <syslog.h> #include <unistd.h> +#include "qemu/cutils.h" #include "passthrough_helpers.h" #include "passthrough_seccomp.h" @@ -124,6 +126,14 @@ struct lo_inode { GHashTable *posix_locks; /* protected by lo_inode->plock_mutex */ mode_t filetype; + + /* + * So we can detect crossmount roots + * (As such, this only needs to be valid for directories. Note + * that files can have multiple parents due to hard links, and so + * their parent_dev may fluctuate.) + */ + dev_t parent_dev; }; struct lo_cred { @@ -137,13 +147,26 @@ enum { CACHE_ALWAYS, }; +enum { + SANDBOX_NAMESPACE, + SANDBOX_CHROOT, +}; + +typedef struct xattr_map_entry { + char *key; + char *prepend; + unsigned int flags; +} XattrMapEntry; + struct lo_data { pthread_mutex_t mutex; + int sandbox; int debug; int writeback; int flock; int posix_lock; int xattr; + char *xattrmap; char *source; char *modcaps; double timeout; @@ -151,18 +174,27 @@ struct lo_data { int timeout_set; int readdirplus_set; int readdirplus_clear; + int announce_submounts; int allow_direct_io; struct lo_inode root; GHashTable *inodes; /* protected by lo->mutex */ struct lo_map ino_map; /* protected by lo->mutex */ struct lo_map dirp_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/ */ int proc_self_fd; }; 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 }, { "no_writeback", offsetof(struct lo_data, writeback), 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 }, { "xattr", offsetof(struct lo_data, xattr), 1 }, { "no_xattr", offsetof(struct lo_data, xattr), 0 }, + { "xattrmap=%s", offsetof(struct lo_data, xattrmap), 0 }, { "modcaps=%s", offsetof(struct lo_data, modcaps), 0 }, { "timeout=%lf", offsetof(struct lo_data, timeout), 0 }, { "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 }, { "readdirplus", offsetof(struct lo_data, readdirplus_set), 1 }, { "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 }, + { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 }, { "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 }, { "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 }, FUSE_OPT_END @@ -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, struct fuse_file_info *fi) { int res; struct stat buf; struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + uint32_t fuse_attr_flags = 0; (void)fi; - res = - fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + res = do_fstatat(inode->fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, + inode->parent_dev, &fuse_attr_flags); + lo_inode_put(lo, &inode); if (res == -1) { 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) @@ -788,11 +852,16 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name, 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) { goto out_err; } + if (!lo->announce_submounts) { + e->attr_flags &= ~FUSE_ATTR_SUBMOUNT; + } + inode = lo_find(lo, &e->attr); if (inode) { 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); pthread_mutex_unlock(&lo->mutex); } + inode->parent_dev = dir->key.dev; e->ino = inode->fuse_ino; lo_inode_put(lo, &inode); lo_inode_put(lo, &dir); @@ -1037,11 +1107,17 @@ static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, 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) { goto out_err; } + if (!lo->announce_submounts) { + e.attr_flags &= ~FUSE_ATTR_SUBMOUNT; + } + pthread_mutex_lock(&lo->mutex); inode->nlookup++; pthread_mutex_unlock(&lo->mutex); @@ -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, name, (unsigned long long)e.ino); + /* + * No need to update inode->parent_dev, because + * (1) We cannot, the inode now has more than one parent, + * (2) Directories cannot have more than one parent, so link() + * does not work for them; but parent_dev only needs to be + * valid for directories. + */ + fuse_reply_entry(req, &e); lo_inode_put(lo, &parent_inode); lo_inode_put(lo, &inode); @@ -1068,14 +1152,21 @@ static struct lo_inode *lookup_name(fuse_req_t req, fuse_ino_t parent, { int res; struct stat attr; + struct lo_data *lo = lo_data(req); + struct lo_inode *dir = lo_inode(req, parent); - res = fstatat(lo_fd(req, parent), name, &attr, - AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (!dir) { + return NULL; + } + + res = do_fstatat(dir->fd, name, &attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, dir->key.dev, NULL); + lo_inode_put(lo, &dir); if (res == -1) { 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) @@ -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); } -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) { struct lo_data *lo = lo_data(req); char *value = NULL; char procname[64]; + const char *name; + char *mapped_name; struct lo_inode *inode; ssize_t ret; int saverr; 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); if (!inode) { fuse_reply_err(req, EBADF); + g_free(mapped_name); return; } @@ -2088,6 +2542,7 @@ out_err: saverr = errno; out: fuse_reply_err(req, saverr); + g_free(mapped_name); 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) { 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); } 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); } out_free: @@ -2165,19 +2672,35 @@ out: 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) { char procname[64]; + const char *name; + char *mapped_name; struct lo_data *lo = lo_data(req); struct lo_inode *inode; ssize_t ret; int saverr; 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); if (!inode) { fuse_reply_err(req, EBADF); + g_free(mapped_name); return; } @@ -2212,21 +2735,38 @@ out: } lo_inode_put(lo, &inode); + g_free(mapped_name); 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]; + const char *name; + char *mapped_name; struct lo_data *lo = lo_data(req); struct lo_inode *inode; ssize_t ret; int saverr; 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); if (!inode) { fuse_reply_err(req, EBADF); + g_free(mapped_name); return; } @@ -2261,6 +2801,7 @@ out: } lo_inode_put(lo, &inode); + g_free(mapped_name); fuse_reply_err(req, saverr); } @@ -2661,14 +3202,54 @@ static void setup_capabilities(char *modcaps_in) } /* + * 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 * source directory. This reduces the impact of arbitrary code execution bugs. */ static void setup_sandbox(struct lo_data *lo, struct fuse_session *se, bool enable_syslog) { - setup_namespaces(lo, se); - setup_mounts(lo->source); + if (lo->sandbox == SANDBOX_NAMESPACE) { + setup_namespaces(lo, se); + setup_mounts(lo->source); + } else { + setup_chroot(lo); + } + setup_seccomp(enable_syslog); setup_capabilities(g_strdup(lo->modcaps)); } @@ -2806,6 +3387,8 @@ static void fuse_lo_data_cleanup(struct lo_data *lo) close(lo->root.fd); } + free(lo->xattrmap); + free_xattrmap(lo); free(lo->source); } @@ -2815,6 +3398,7 @@ int main(int argc, char *argv[]) struct fuse_session *se; struct fuse_cmdline_opts opts; struct lo_data lo = { + .sandbox = SANDBOX_NAMESPACE, .debug = 0, .writeback = 0, .posix_lock = 0, @@ -2878,12 +3462,11 @@ int main(int argc, char *argv[]) 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) { current_log_level = opts.log_level; + } else { + /* default log level is INFO */ + current_log_level = FUSE_LOG_INFO; } lo.debug = opts.debug; if (lo.debug) { @@ -2906,6 +3489,11 @@ int main(int argc, char *argv[]) } else { lo.source = strdup("/"); } + + if (lo.xattrmap) { + parse_xattrmap(&lo); + } + if (!lo.timeout_set) { switch (lo.cache) { case CACHE_NONE: diff --git a/trace/control.c b/trace/control.c index b35e512dce..5669db7eea 100644 --- a/trace/control.c +++ b/trace/control.c @@ -39,6 +39,7 @@ static TraceEventGroup *event_groups; static size_t nevent_groups; static uint32_t next_id; static uint32_t next_vcpu_id; +static bool init_trace_on_startup; QemuOptsList qemu_trace_opts = { .name = "trace", @@ -225,7 +226,9 @@ void trace_init_file(const char *file) { #ifdef CONFIG_TRACE_SIMPLE st_set_trace_file(file); - st_set_trace_file_enabled(true); + if (init_trace_on_startup) { + st_set_trace_file_enabled(true); + } #elif defined CONFIG_TRACE_LOG /* * If both the simple and the log backends are enabled, "--trace file" @@ -299,6 +302,7 @@ char *trace_opt_parse(const char *optarg) } trace_init_events(qemu_opt_get(opts, "events")); trace_file = g_strdup(qemu_opt_get(opts, "file")); + init_trace_on_startup = true; qemu_opts_del(opts); return trace_file; diff --git a/ui/icons/meson.build b/ui/icons/meson.build index b6e21f6ad7..12c52080eb 100644 --- a/ui/icons/meson.build +++ b/ui/icons/meson.build @@ -2,12 +2,12 @@ foreach s: [16, 24, 32, 48, 64, 128, 256, 512] s = '@0@x@0@'.format(s.to_string()) install_data('qemu_@0@.png'.format(s), rename: 'qemu.png', - install_dir: config_host['qemu_icondir'] / 'hicolor' / s / 'apps') + install_dir: qemu_icondir / 'hicolor' / s / 'apps') endforeach install_data('qemu_32x32.bmp', rename: 'qemu.bmp', - install_dir: config_host['qemu_icondir'] / 'hicolor' / '32x32' / 'apps') + install_dir: qemu_icondir / 'hicolor' / '32x32' / 'apps') install_data('qemu.svg', - install_dir: config_host['qemu_icondir'] / 'hicolor' / 'scalable' / 'apps') + install_dir: qemu_icondir / 'hicolor' / 'scalable' / 'apps') diff --git a/ui/meson.build b/ui/meson.build index 5d4906c023..013258a01c 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -139,7 +139,7 @@ subdir('shader') if have_system subdir('icons') - install_data('qemu.desktop', install_dir: config_host['qemu_desktopdir']) + install_data('qemu.desktop', install_dir: qemu_desktopdir) endif modules += {'ui': ui_modules} diff --git a/util/cutils.c b/util/cutils.c index be4e43a9ef..c395974fab 100644 --- a/util/cutils.c +++ b/util/cutils.c @@ -949,7 +949,7 @@ char *get_relocated_path(const char *dir) bindir += len_bindir; dir = next_component(dir, &len_dir); bindir = next_component(bindir, &len_bindir); - } while (len_dir == len_bindir && !memcmp(dir, bindir, len_dir)); + } while (len_dir && len_dir == len_bindir && !memcmp(dir, bindir, len_dir)); /* Ascend from bindir to the common prefix with dir. */ while (len_bindir) { diff --git a/version.texi.in b/version.texi.in deleted file mode 100644 index 0a723b8be6..0000000000 --- a/version.texi.in +++ /dev/null @@ -1,2 +0,0 @@ -@set VERSION @VERSION@ -@set CONFDIR @qemu_confdir@ |